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

1987 lines
53 KiB
Vue
Raw Permalink 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" :class="['category-page-wrap', 'category-template-' + value.template]"
:style="{ height: 'calc(100vh - var(--window-top, 0px) - ' + tabBarHeight + ')' }">
<!-- 高度说明: 100vh - H5顶部导航栏(--window-top, 小程序为0) - 底部导航高度(tabBarHeight),
保证底部购物车结算栏始终落在可视区内, 点击加购后无需向下翻即可看到 -->
<!-- 模板4: 顶部搜索框(图片样式) -->
<view class="search-box search-box-template-4" v-if="value.template == 4 && value.search"
@tap.stop="onClickSearch()">
<view class="search-content">
<input type="text" class="uni-input" maxlength="50" :placeholder="$lang('components-diy.diy-search.searchBtn')"
confirm-type="search" @tap.stop="onClickSearch()" disabled="true" />
<text class="iconfont icon-sousuo3"></text>
</view>
</view>
<!-- #ifdef MP-WEIXIN -->
<!-- <block v-if="value.template == 4">
<view class="search-box" v-if="value.search" @tap.stop="$util.redirectTo('/pages_tool/goods/search')" :style="navbarInnerStyle">
<view class="search-content">
<input type="text" class="uni-input font-size-tag" maxlength="50" :placeholder="$lang('components-diy.diy-search.searchBtn')" confirm-type="search" @tap.stop="onClickSearch()" disabled="true" />
<text class="iconfont icon-sousuo3"></text>
</view>
</view>
<view :style="navbarInnerStyle" v-if="!value.search">商品分类</view>
</block> -->
<block v-if="value.template != 4">
<!-- <view :style="navbarInnerStyle">商品分类</view> -->
<view class="search-box" v-if="value.search" @tap.stop="onClickSearch()"
:style="wxSearchHeight">
<view class="search-content">
<input type="text" class="uni-input" maxlength="50" :placeholder="$lang('components-diy.diy-search.searchBtn')"
confirm-type="search" @tap.stop="onClickSearch()"
disabled="true" />
<text class="iconfont icon-sousuo3"></text>
</view>
<view class="iconfont" :class="{ 'icon-apps': !isList, 'icon-list': isList }"
@tap.stop.prevent="changeListStyle()"></view>
</view>
</block>
<!-- #endif -->
<!-- #ifdef H5 || MP-HARMONY -->
<view class="search-box" v-if="value.search && value.template != 4" @tap.stop="onClickSearch()">
<view class="search-content">
<input type="text" class="uni-input" maxlength="50" :placeholder="$lang('components-diy.diy-search.searchBtn')" confirm-type="search"
@tap.stop="onClickSearch()" disabled="true" />
<text class="iconfont icon-sousuo3"></text>
</view>
<view class="iconfont" :class="{ 'icon-apps': !isList, 'icon-list': isList }"
@tap.stop.prevent="changeListStyle()"></view>
</view>
<!-- #endif -->
<view class="template-four wx" v-if="value.template == 4">
<scroll-view scroll-x="true" class="template-four-wrap" :scroll-with-animation="true"
:scroll-into-view="'category-one-' + oneCategorySelect" enable-flex="true">
<view class="category-item" :id="'category-one-' + index" v-for="(item, index) in templateFourData"
:key="index" :class="{ select: oneCategorySelect == index }" @tap.stop="templateFourOneFn(index)">
<view class="image-warp" :class="[{ 'color-base-border': oneCategorySelect == index }]">
<image :src="$util.img(item.image)" mode="aspectFill" show-menu-by-longpress/>
</view>
<view :class="['text', { 'color-base-bg': oneCategorySelect == index }]">{{ item.category_name }}
</view>
</view>
</scroll-view>
<view class="category-item-all" @tap.stop="$refs.templateFourPopup.open()">
<view class="category-item-all-wrap">
<text class="text">{{ $lang('components-diy.diy-category.expand') }}</text>
<image class="img" :src="$util.img('/public/uniapp/category/unfold.png')" mode="aspectFill" show-menu-by-longpress></image>
</view>
</view>
<uni-popup type="top" ref="templateFourPopup" :top="uniPopupTop">
<view class="template-four-popup">
<scroll-view scroll-y="true" class="template-four-scroll" enable-flex="true">
<view class="item" :class="{ selected: oneCategorySelect == index }"
@tap.stop="templateFourOneFn(index)" v-for="(item, index) in templateFourData" :key="index">
<view class="image-warp" :class="[{ 'color-base-border': oneCategorySelect == index }]">
<image :src="$util.img(item.image)" mode="aspectFill" show-menu-by-longpress></image>
</view>
<view :class="['text', { 'color-base-bg': oneCategorySelect == index }]">{{
item.category_name }}</view>
</view>
</scroll-view>
<view class="pack-up" @tap.stop="$refs.templateFourPopup.close()">
<text>{{ $lang('components-diy.diy-category.clickToCollapse') }}</text>
<text class="iconfont icon-iconangledown-copy"></text>
</view>
</view>
</uni-popup>
</view>
<!-- 全屏骨架占位 (只显示: 左分类树 + 右商品网格 + 底部空隙, 不画 navbar/TabBar,
避免与小程序/H5 顶部 navbar / diy-bottom-nav 错位)。
通过 v-if 拿到 categoryTreeLoading 时按真实布局骨架填充。
search-box / 商品 nav-bar 由外层 category.vue 渲染, 不在本组件里动它 -->
<view class="category-page-skeleton" v-if="categoryTreeLoading">
<view class="skeleton-content">
<view class="skeleton-tree">
<view class="skeleton-tree-item" v-for="n in 8" :key="n"></view>
</view>
<view class="skeleton-grid">
<view class="skeleton-card" v-for="n in 6" :key="n"></view>
</view>
</view>
</view>
<view class="content-box" v-if="categoryTree">
<block v-if="categoryTree.length">
<scroll-view scroll-y="true" class="tree-wrap">
<view class="category-item-wrap">
<!-- 模板4: 左侧显示一级分类(顶部轮播中的分类) -->
<view v-if="value.template == 4">
<view class="category-item" v-for="(item, index) in templateFourData" :key="index" :class="[
{ select: oneCategorySelect == index },
{ 'border-bottom': oneCategorySelect + 1 === index },
{ 'border-top': oneCategorySelect - 1 === index }
]" @tap.stop="templateFourOneFn(index)">
<view class="">{{ item.category_name }}</view>
</view>
</view>
<!-- 其他模板: 左侧显示二级分类 -->
<view v-else>
<view class="category-item" v-for="(item, index) in categoryTree" :key="index" :class="[
{ select: select == index },
{ 'border-bottom': value.template == 4 && select + 1 === index },
{ 'border-top': value.template == 4 && select - 1 === index },
{ 'first-category-item': index == 0 && (value.template == 99 || value.template == 1 || value.template == 5) }
]" @tap.stop="switchOneCategory(index)">
<view class="">{{ item.category_name }}</view>
</view>
</view>
</view>
</scroll-view>
<view class="right-flex-wrap">
<!-- 模板99 + 二级分类: 全局二级分类按钮(去重, 一直显示) + 商品列表 -->
<scroll-view scroll-y="true" class="content-wrap" v-if="value.template == 99 && template99Category"
:refresher-enabled="true" refresher-default-style="none"
:refresher-triggered="triggered" @refresherrefresh="onRefresh" @refresherrestore="onRestore">
<diy-category-item :category="template99Category" :value="value" ref="categoryItem" :index="0" :select="0"
:oneCategorySelect="oneCategorySelect" :isList="isList"
:currentOneCategory="categoryTree && categoryTree[select] ? categoryTree[select] : null"
:lastSecondCategoryName="lastSecondCategoryName"
@tologin="toLogin" @selectsku="selectSku($event, 0)" @addCart="addCartPoint"
@saveSecondCategory="saveSecondCategoryName"></diy-category-item>
</scroll-view>
<!-- 模板1: 仿京东/淘宝商品分类 - 按 template 2 的 part-load 逻辑, 切换一级分类时只渲染选中分类的子分类网格 -->
<scroll-view scroll-y="true" class="content-wrap" v-else-if="value.template == 1"
:refresher-enabled="true" refresher-default-style="none"
:refresher-triggered="triggered" @refresherrefresh="onRefresh" @refresherrestore="onRestore">
<view class="child-category-wrap" v-for="(item, index) in categoryTree" :key="index"
:id="'category-' + index" :style="{ display: select == index ? 'block' : 'none' }">
<diy-category-item :category="item" :value="value" ref="categoryItem" :index="index"
:select="select" :oneCategorySelect="oneCategorySelect" :isList="isList"
@tologin="toLogin" @selectsku="selectSku($event, index)" @addCart="addCartPoint"
@saveSecondCategory="saveSecondCategoryName"> </diy-category-item>
</view>
</scroll-view>
<!-- 模板5 + 二级分类(value.level==2): 单面板切换 - 右侧只显示"当前选中"的一级分类,
点左侧分类切换, 其它分类隐藏(display:none); 每个一级分类: 标题 + 商品网格 -->
<scroll-view scroll-y="true" class="content-wrap"
v-else-if="value.template == 5 && value.level == 2"
:refresher-enabled="true" refresher-default-style="none"
:refresher-triggered="triggered" @refresherrefresh="onRefresh" @refresherrestore="onRestore">
<view class="child-category-wrap" v-for="(item, index) in categoryTree" :key="index"
:id="'category-' + index" :style="{ display: select == index ? 'block' : 'none' }">
<diy-category-item :category="item" :value="value" ref="categoryItem" :index="index"
:select="select" :oneCategorySelect="oneCategorySelect" :isList="isList"
@tologin="toLogin" @selectsku="selectSku($event, index)" @addCart="addCartPoint"
@saveSecondCategory="saveSecondCategoryName"> </diy-category-item>
</view>
</scroll-view>
<!-- 模板5 + 三级分类(value.level!=2): 左侧分类树 + 右侧纵向平铺"全部一级分类",
每个一级分类: 标题 + 二级分类网格; 左侧点击分类用 scroll-into-view 定位,
滚动时由 listenScroll 反推并高亮左侧分类 -->
<scroll-view scroll-y="true" class="content-wrap" v-else-if="value.template == 5"
ref="contentWrap" :scroll-into-view="categoryId" :scroll-with-animation="true"
:refresher-enabled="true" refresher-default-style="none"
:refresher-triggered="triggered" @refresherrefresh="onRefresh" @refresherrestore="onRestore"
@scroll="listenScroll" @touchstart="touchStart">
<view class="child-category" v-for="(item, index) in categoryTree" :key="index"
:id="'category-' + index">
<diy-category-item :category="item" :value="value" ref="categoryItem" :index="index"
:select="select" :oneCategorySelect="oneCategorySelect" :isList="isList"
@tologin="toLogin" @selectsku="selectSku($event, index)" @addCart="addCartPoint"
@loadfinish="getHeightArea" @saveSecondCategory="saveSecondCategoryName"></diy-category-item>
</view>
<view class="end-tips" ref="endTips" :style="{ opacity: endTips }">{{ $lang('components-diy.diy-category.reachedBottom') }}</view>
</scroll-view>
<scroll-view scroll-y="true" class="content-wrap" v-else-if="value.template == 99 || loadType == 'all'"
ref="contentWrap" :scroll-into-view="categoryId" :scroll-with-animation="true"
@scroll="listenScroll" @touchstart="touchStart" :refresher-enabled="true"
refresher-default-style="none" :refresher-triggered="triggered" @refresherrefresh="onRefresh"
@refresherrestore="onRestore">
<view class="child-category" v-for="(item, index) in categoryTree" :key="index"
:id="'category-' + index" :style="{ display: value.template == 99 && select != index ? 'none' : 'block' }">
<diy-category-item :category="item" :value="value" ref="categoryItem" :index="index"
:select="select" :oneCategorySelect="oneCategorySelect" :isList="isList"
:lastSecondCategoryName="value.template == 99 ? lastSecondCategoryName : ''"
:currentOneCategory="categoryTree && categoryTree[select] ? categoryTree[select] : null"
@tologin="toLogin" @selectsku="selectSku($event, index)" @addCart="addCartPoint"
@loadfinish="getHeightArea" @saveSecondCategory="saveSecondCategoryName"></diy-category-item>
</view>
<view class="end-tips" ref="endTips" :style="{ opacity: endTips }">{{ $lang('components-diy.diy-category.reachedBottom') }}</view>
</scroll-view>
<view class="content-wrap"
v-if="(value.template == 2 || value.template == 3 || value.template == 4) && loadType == 'part'">
<view class="child-category-wrap" v-for="(item, index) in categoryTree" :key="index"
:id="'category-' + index" :style="{ display: select == index ? 'block' : 'none' }">
<diy-category-item :category="item" :value="value" ref="categoryItem" :index="index"
:last="index == categoryTree.length - 1 ? true : false" :select="select"
:oneCategorySelect="oneCategorySelect" :isList="isList" @tologin="toLogin"
:currentOneCategory="value.template == 4 && templateFourData[oneCategorySelect] ? templateFourData[oneCategorySelect] : null"
@selectsku="selectSku($event, index)" @addCart="addCartPoint"
@switch="switchOneCategory"></diy-category-item>
</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></image>
<view class="tips">{{ $lang('components-diy.diy-category.noCategory') }}</view>
</view>
</view>
<!-- 风格2/模板4: 底部购物车栏
1) 不用 position:fixed: 小程序/H5/鸿蒙下 fixed 受页面避让与父级 transform 影响,
会"跟在内容后面"只有滚到底才看得见。
.category-page-wrap 是固定高度(calc(100vh - tabBarHeight))的纵向 flex 容器,
content-box 为 flex:1 height:0, 把结算栏作为普通子项排在末尾,
无论右侧商品列表怎么滚动, 结算栏始终固定显示在底部
2) cartNumber > 0: 只在购物车里有商品时才出现(点加购即出现, 减到 0 即消失),
商品加购后会立即 dispatch cartCalculate 同步 cartNumber, 不依赖接口回调 -->
<view class="cart-box cart-box-template-4"
v-if="(value.template == 2 || value.template == 4) && showCartSettlement && storeToken && cartNumber > 0 && categoryTree && categoryTree.length">
<view class="left-wrap">
<view class="cart-icon" ref="cartIcon" :animation="cartAnimation"
@tap.stop="$util.redirectTo('/pages_goods/cart')">
<text class="iconfont icon-ziyuan1"></text>
<view class="num" v-if="cartNumber">{{ cartNumber < 99 ? cartNumber : '99+' }}</view>
</view>
<view class="price">
<text class="title">总计:</text>
<text class="unit font-size-tag price-font">¥</text>
<text class="money font-size-toolbar price-font">{{ cartTotalMoney[0] }}</text>
<text class="unit font-size-tag price-font">.{{ cartTotalMoney[1] ? cartTotalMoney[1] : '00' }}</text>
</view>
</view>
<view class="right-wrap"><button type="primary" class="settlement-btn"
@tap.stop="settlement">去结算</button>
</view>
</view>
<view class="cart-point" v-for="(item, index) in carIconList" :key="index"
:style="{ left: item.left + 'px', top: item.top + 'px' }"></view>
<ns-goods-sku-category ref="skuSelect" @refresh="refreshData" @addCart="addCartPoint"></ns-goods-sku-category>
</view>
</template>
<script>
// 获取系统状态栏的高度
let systemInfo = uni.getSystemInfoSync();
let menuButtonInfo = {};
// 如果是小程序,获取右上角胶囊的尺寸信息,避免导航栏右侧内容与胶囊重叠(支付宝小程序非本API,尚未兼容)
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// #endif
import DiyMinx from './minx.js'
var contentWrapHeight, query, cartPosition;
// 商品分类
export default {
name: 'diy-category',
props: {
value: {
type: Object,
default: () => {
return {};
}
}
},
mixins: [DiyMinx],
data() {
return {
oneCategorySelect: 0,
select: 0,
categoryId: 'category-0',
categoryTree: null,
scrollLock: false,
triggered: true,
heightArea: [],
isSub: false,
carIconList: {},
endTips: 0,
cartAnimation: {},
loadType: '',
templateFourData: [],
isList: false,
lang: uni.getStorageSync("lang"),
lastSecondCategoryName: '',
template99Category: null,
// 修复体验: 进入页面初期显示 "暂未分类" 然后又变成有数据的体验很差
// 用 categoryTreeLoading 区分"还在请求"和"真的没分类", 请求未完成时
// 显示骨架占位而非空态文案
categoryTreeLoading: true,
};
},
created() {
this.getCategoryTree();
this.loadType = this.value.goodsLevel == 1 && this.value.loadType == 'all' ? 'all' : 'part';
// 模板2/4: 默认单列列表布局(左图右文卡片), 点右上角图标切换为双列宫格
if (this.value.template == 2 || this.value.template == 4) {
this.isList = true;
}
},
mounted() {
query = uni.createSelectorQuery().in(this);
query
.select('.content-wrap')
.boundingClientRect(data => {
if (data) contentWrapHeight = data.height;
})
.exec();
setTimeout(() => {
query
.select('.end-tips')
.boundingClientRect(data => {
if (data && data.top > contentWrapHeight) this.endTips = 1;
})
.exec();
query
.select('.cart-icon')
.boundingClientRect(data => {
if (data) cartPosition = data;
})
.exec();
if (this.value.template == 1) this.getHeightArea(-1);
if (this.value.template == 5) this.getHeightArea(-1);
}, 500);
},
watch: {
// 组件刷新监听
componentRefresh: function (nval) { },
categoryTreeLoading: function (nval, oval) {
if (nval === false && oval === true) {
this.$nextTick(() => {
if (!this.heightArea.length) this.getHeightArea(-1);
// 分类渲染完成后重新对齐到当前选中的一级分类顶部,
// 避免首屏第一个分类的标题被顶出可视区
if (this.categoryTree && this.categoryTree.length) this.alignToCategory(this.select);
});
}
}
},
computed: {
cartTotalMoney() {
let money = parseFloat(this.cartMoney).toFixed(2);
return money.split('.');
},
// 后台开关:快捷购买(显示购物车及结算按钮) = quickBuy; 加购按钮 = cartBtn
// 后台开启哪个就显示哪个, 关闭哪个就不显示哪个; 未下发 cartBtn 时兼容旧字段 quickBuy
showCartSettlement() {
const t = this.getDiyToggle(['showCartSettlement', 'show_cart_settlement']);
if (t !== undefined) return t;
const legacy = this.getDiyToggle(['quickBuy', 'quick_buy']);
return legacy !== undefined ? legacy : false;
},
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;
},
// 转换字符数值为真正的数值
navbarHeight() {
// #ifdef APP-PLUS || H5
return 44;
// #endif
// #ifdef MP
// 小程序特别处理,让导航栏高度 = 胶囊高度 + 两倍胶囊顶部与状态栏底部的距离之差(相当于同时获得了导航栏底部与胶囊底部的距离)
// 此方法有缺陷,暂不用(会导致少了几个px),采用直接固定值的方式
// return menuButtonInfo.height + (menuButtonInfo.top - this.navbarHeight) * 2;//导航高度
// let height = systemInfo.platform == 'ios' ? 44 : 48;
let height = menuButtonInfo.top;
// height += systemInfo.navbarHeight;
return height;
// #endif
},
// 导航栏内部盒子的样式
navbarInnerStyle() {
let style = '';
// 导航栏宽度,如果在小程序下,导航栏宽度为胶囊的左边到屏幕左边的距离
// style += 'height:' + this.navbarHeight + 'px;';
// // 如果是各家小程序,导航栏内部的宽度需要减少右边胶囊的宽度
// #ifdef MP
if (this.value.template == 4 && this.value.search) {
style += 'height:' + menuButtonInfo.height + 'px;';
let rightButtonWidth = menuButtonInfo.width ? menuButtonInfo.width * 2 + 'rpx' : '70rpx';
style += 'padding-right:calc(' + rightButtonWidth + ' + 30rpx);';
style += 'padding-top:' + this.navbarHeight + 'px;';
}
// #endif
if (this.value.template != 4 || (this.value.template == 4 && !this.value.search)) {
style += 'height:' + menuButtonInfo.height * 2 + 'rpx;';
style += 'padding-top:' + this.navbarHeight + 'px;';
style += 'text-align: center;';
style += 'line-height:' + menuButtonInfo.height * 2 + 'rpx;';
style += 'font-size: 14px;';
style += 'padding-bottom: 10rpx;';
}
return style;
},
wxSearchHeight() {
let style = '';
// #ifdef MP
style += 'height: 64rpx;';
// #endif
return style;
},
uniPopupTop() {
let top = 0;
// #ifdef MP
top = this.navbarHeight + menuButtonInfo.height + 'px';
// #endif
// #ifdef H5
top = '100rpx';
// #endif
return top;
}
},
methods: {
/**
* 页面显示
*/
pageShow() {
this.$store.dispatch('getCartNumber');
if (!this.heightArea.length) this.getHeightArea(-1);
},
/**
* 获取高度区间
*
* 鸿蒙 JSVM 在 mounted() 之前触发 onShow,导致 query 还是 undefined
* 加防御性检查,避免上线后第一次进入崩溃
*/
getHeightArea(index) {
// 节流: 500ms 内的重复请求复用上次结果, 避免每次 onShow 都跑 selectAll 触发频闪
const now = Date.now();
if (this._lastHeightAreaTime && now - this._lastHeightAreaTime < 500 && this.heightArea && this.heightArea.length) {
if (index != -1 && index < this.categoryTree.length - 1 && Array.isArray(this.$refs.categoryItem) && this.$refs.categoryItem[index + 1]) this.$refs.categoryItem[index + 1].getGoodsList();
return;
}
this._lastHeightAreaTime = now;
let heightArea = [];
if (!query || typeof query.selectAll !== 'function') {
query = uni.createSelectorQuery && uni.createSelectorQuery().in(this);
}
if (!query || typeof query.selectAll !== 'function') {
// 极端兜底:鸿蒙下若 createSelectorQuery 不存在,跳过计算
this.heightArea = heightArea;
return;
}
query
.selectAll('.content-wrap .child-category')
.boundingClientRect(data => {
if (data && data.length) {
data.forEach((item, index) => {
if (index == 0) heightArea.push([0, item.height]);
else heightArea.push([heightArea[index - 1][1], heightArea[index - 1][1] +
item.height
]);
});
}
})
.exec();
this.heightArea = heightArea;
if (index != -1 && index < this.categoryTree.length - 1 && Array.isArray(this.$refs.categoryItem) && this.$refs.categoryItem[index + 1]) this.$refs.categoryItem[index + 1].getGoodsList();
},
/**
* 获取全部分类
*/
getCategoryTree() {
this.categoryTreeLoading = true;
this.$api.sendRequest({
url: '/api/goodscategory/tree',
data: {
level: 3
},
success: res => {
if (res.code == 0) {
this.categoryTree = res.data;
if (this.value.template == 4) {
this.templateFourData = JSON.parse(JSON.stringify(this.categoryTree));
this.categoryTree = this.templateFourData[0].child_list;
}
//切换英文标题
if (this.lang == 'en-us') {
this.categoryTree.map(item => {
item?.child_list?.map(items => {
items.category_name = items.en_category_name;
return items;
})
item.category_name = item.en_category_name;
return item;
});
}
// 模板99 + 二级分类: 全局查询所有有商品的二级分类(去重), 按钮一直显示
if (this.value.template == 99) {
this.loadGlobalSecondCategoryBtns();
}
}
},
fail: () => {
// 失败也保证 loading 关闭, 让骨架替换为"暂无分类"空态
this.categoryTree = this.categoryTree || [];
},
complete: () => {
// 无论成功失败都关闭 loading 骨架
this.categoryTreeLoading = false;
}
});
},
/**
* 汇总所有二级分类供 template99 渲染按钮(按名称去重)
* 注意: 保留"无商品"的二级分类也显示出来, 不再按是否有商品过滤,
* 避免一级分类下 10 个二级分类只显示 8 个(2 个被当成空分类过滤掉)的问题
*/
loadGlobalSecondCategoryBtns() {
if (!this.categoryTree || !this.categoryTree.length) {
this.template99Category = { category_name: '', child_list: [] };
return;
}
// 收集所有二级分类(保留原始顺序, 不去重, 汇总后去重)
const allSecond = [];
this.categoryTree.forEach(one => {
if (one.child_list && one.child_list.length) {
one.child_list.forEach(two => {
allSecond.push(Object.assign({ _origIndex: allSecond.length }, two));
});
}
});
if (!allSecond.length) {
this.template99Category = { category_name: '', child_list: [] };
return;
}
// 按名称去重, 同名只保留第一个, 保持原始顺序
const deduped = [];
const seenNames = {};
allSecond.forEach(item => {
if (!seenNames[item.category_name]) {
seenNames[item.category_name] = true;
deduped.push(item);
}
});
this.template99Category = {
category_name: '',
child_list: deduped
};
},
/**
* 切换一级分类
* @param {Object} index
*/
switchOneCategory(index) {
if (index >= this.categoryTree.length) return;
this.select = index;
this.scrollLock = true;
this.alignToCategory(index);
},
/**
* 把右侧滚动区定位到指定一级分类的顶部
*
* uni-app 只在 scroll-into-view 的值"发生变化"时才重新滚动,
* 而 categoryId 的初值就是 'category-0', 且只有点击左侧分类才会被改写;
* 用户在右侧手动滚动后 categoryId 仍是 'category-0', 此时再点第一个分类(index 0)
* 值时没有变化, 右侧不会回滚到该分类, 该分类的标题就被顶出可视区
* (看起来像"标题丢了 / 卡片被切了一半"), 而点其它分类因为值变了所以都正常。
* 这里先置空、下一帧再设置目标 id, 保证每次点击都能重新定位到分类顶部。
* @param {Number} index 一级分类下标
*/
alignToCategory(index) {
const target = 'category-' + index;
if (this.categoryId === target) {
this.categoryId = '';
this.$nextTick(() => {
this.categoryId = target;
});
} else {
this.categoryId = target;
}
},
saveSecondCategoryName(name) {
this.lastSecondCategoryName = name;
},
touchStart() {
this.scrollLock = false;
},
/**
* 监听滚动
* @param {Object} event
*/
listenScroll(event) {
if (this.scrollLock) return;
let scrollTop = event.detail.scrollTop;
if (this.heightArea.length) {
for (let i = 0; i < this.heightArea.length; i++) {
if (scrollTop >= this.heightArea[i][0] && scrollTop <= this.heightArea[i][1]) {
this.select = i;
break;
}
}
// 模板5 三级分类为静态分类网格平铺, 不需要懒加载商品; 这里只保留 loadType=all 的懒加载,
// 并补上 heightArea[this.select] 越界保护
if (this.value.template != 1 && this.value.loadType == 'all' &&
this.heightArea[this.select] && this.heightArea[this.select][1] -
scrollTop - contentWrapHeight < 300) {
if (Array.isArray(this.$refs.categoryItem) && this.$refs.categoryItem[this.select]) this.$refs.categoryItem[this.select].getGoodsList();
}
}
},
onRefresh() {
this.triggered = false;
},
onRestore() {
this.triggered = 'restore'; // 需要重置
},
toLogin() {
this.$emit('tologin');
},
/**
* sku选择
* @param {Object} data
* @param {Object} index
*/
selectSku(data, index) {
this.$api.sendRequest({
url: '/api/goodssku/getInfoForCategory',
data: {
sku_id: data.sku_id
},
success: res => {
if (res.code >= 0) {
let item = res.data;
item.unit = item.unit || this.$lang('components-diy.diy-category.pieceUnit');
if (item.sku_images) item.sku_images = item.sku_images.split(',');
else item.sku_images = [];
// 多规格时合并主图
if (item.goods_spec_format && item.goods_image) {
item.goods_image = item.goods_image.split(',');
item.sku_images = item.goods_image.concat(item.sku_images);
}
// 当前商品SKU规格
if (item.sku_spec_format) item.sku_spec_format = JSON.parse(item.sku_spec_format);
// 商品SKU格式
if (item.goods_spec_format) item.goods_spec_format = JSON.parse(item.goods_spec_format);
// 限时折扣
if (item.promotion_type == 1) {
item.discountTimeMachine = this.$util.countDown(item.end_time - res.timestamp);
}
if (item.promotion_type == 1 && item.discountTimeMachine) {
if (item.member_price > 0 && Number(item.member_price) <= Number(item.discount_price)) {
item.show_price = item.member_price;
} else {
item.show_price = item.discount_price;
}
} else if (item.member_price > 0) {
item.show_price = item.member_price;
} else {
item.show_price = item.price;
}
this.$refs.skuSelect.show(item);
}
}
});
},
settlement() {
if (!this.cartIds.length || this.isSub) return;
this.isSub = true;
uni.removeStorageSync('delivery');
uni.setStorage({
key: 'orderCreateData',
data: {
cart_ids: this.cartIds.toString()
},
success: () => {
this.$util.redirectTo('/pages_order/payment');
this.isSub = false;
}
});
},
/**
* 添加点
* @param {Object} left
* @param {Object} top
*/
addCartPoint(left, top) {
if (this.value.template != 2 && this.value.template != 4) return;
if (!this.showCartSettlement) return;
let key = new Date().getTime();
this.$set(this.carIconList, key, {
left: left,
top: top,
index: 0,
bezierPos: this.$util.bezier([{
x: left,
y: top
}, {
x: left - 200,
y: left - 120
},
{
x: cartPosition.left + 10,
y: cartPosition.top
}
], 6).bezier_points,
timer: null
});
this.startAnimation(key);
},
/**
* 执行动画
* @param {Object} key
*/
startAnimation(key) {
let bezierPos = this.carIconList[key].bezierPos,
index = this.carIconList[key].index;
this.carIconList[key].timer = setInterval(() => {
if (index < 6) {
this.carIconList[key].left = bezierPos[index].x;
this.carIconList[key].top = bezierPos[index].y;
index++;
} else {
clearInterval(this.carIconList[key].timer);
delete this.carIconList[key];
this.$forceUpdate();
// 购物车图标
setTimeout(() => {
this.$store.commit('setCartChange');
}, 100);
let animation = uni.createAnimation({
duration: 200,
timingFunction: 'ease'
});
animation.scale(1.2).step();
this.cartAnimation = animation.export();
setTimeout(() => {
animation.scale(1).step();
this.cartAnimation = animation.export();
}, 300);
}
}, 50);
},
// 风格四头部切换
templateFourOneFn(index) {
this.categoryTree = this.templateFourData[index].child_list || [];
this.oneCategorySelect = index;
this.select = 0;
},
// 操作多规格商品弹框后,刷新商品数据
refreshData() {
if (Array.isArray(this.$refs.categoryItem) && this.$refs.categoryItem[this.select]) this.$refs.categoryItem[this.select].loadGoodsCartNum(true);
},
changeListStyle() {
this.isList = !this.isList;
},
onClickSearch() {
this.$util.redirectTo('/pages_tool/goods/search');
}
}
};
</script>
<style lang="scss" scoped>
.category-page-wrap {
width: 100vw;
// height: calc(100vh - var(--tab-bar-height, 0));
display: flex;
flex-direction: column;
background-color: #fff;
}
.content-box {
flex: 1;
height: 0;
display: flex;
align-items: flex-start;
.tree-wrap {
width: 170rpx;
height: 100%;
background-color: #f5f5f5;
}
.right-flex-wrap {
flex: 1;
width: 0;
height: 100%;
background: #f4f6fa;
display: flex;
flex-direction: column;
transform: translateX(0px);
.content-wrap {
display: flex;
flex: 1;
height: 0;
width: 100%;
}
.child-category-wrap {
width: 100%;
height: 100%;
}
}
}
.tree-wrap .category-item-wrap {
height: auto;
background-color: #fff;
}
.tree-wrap .category-item {
line-height: 1.5;
padding: 26rpx 28rpx;
box-sizing: border-box;
position: relative;
background-color: var(--diy-category-tree-normal-bg-color, #f5f5f5);
view {
color: var(--diy-category-tree-normal-text-color, #222222);
width: 100%;
white-space: break-word;
overflow: hidden;
text-align: center;
font-size: var(--diy-category-tree-normal-size, 20rpx);
}
&.border-top {
border-bottom-right-radius: 12rpx;
}
&.border-bottom {
border-top-right-radius: 12rpx;
}
&.select {
background: var(--diy-category-tree-select-bg-color, #fff);
view {
color: var(--diy-category-tree-select-text-color, #333);
font-weight: bold;
font-size: var(--diy-category-tree-select-size, 28rpx);
}
&::before {
content: ' ';
width: 8rpx;
height: 34rpx;
background: var(--base-color);
display: block;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
}
}
}
.search-box {
position: relative;
padding: 20rpx 30rpx;
display: flex;
align-items: center;
background: #fff;
.search-content {
position: relative;
/* #ifndef MP-WEIXIN */
height: 64rpx;
/* #endif */
/* #ifdef MP-WEIXIN */
height: 100%;
/* #endif */
border-radius: 40rpx;
flex: 1;
background-color: #f5f5f5;
input {
box-sizing: border-box;
display: block;
height: 100%;
width: 100%;
padding: 0 20rpx 0 40rpx;
background: #f5f5f5;
color: #333;
border-radius: 40rpx;
}
.iconfont {
position: absolute;
top: 50%;
right: 10rpx;
transform: translateY(-50%);
font-size: $font-size-toolbar;
z-index: 10;
color: #89899a;
width: 80rpx;
text-align: center;
}
}
// 切换列表图标
.iconfont {
font-size: 36rpx;
margin: 0 0rpx 0 20rpx;
}
}
.cart-box {
height: 100rpx;
width: 100%;
background: #fff;
border-top: 1px solid #f5f5f5;
box-sizing: border-box;
padding: 0 30rpx;
display: flex;
align-items: center;
justify-content: space-between;
.left-wrap {
display: flex;
align-items: center;
}
.cart-icon {
width: 70rpx;
height: 70rpx;
position: relative;
.iconfont {
color: var(--btn-text-color);
width: inherit;
height: inherit;
background-color: $base-color;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.num {
position: absolute;
top: 0;
right: 0;
transform: translate(60%, 0);
display: inline-block;
box-sizing: border-box;
color: #fff;
line-height: 1.2;
text-align: center;
font-size: 24rpx;
padding: 0 6rpx;
min-width: 30rpx;
border-radius: 16rpx;
background-color: var(--price-color);
border: 2rpx solid #fff;
}
}
.price {
margin-left: 30rpx;
.title {
color: #333;
}
.money {
font-weight: bold;
color: var(--price-color);
}
.unit {
font-weight: bold;
color: var(--price-color);
}
}
.settlement-btn {
margin: 0 0 0 20rpx;
width: 200rpx;
font-weight: bold;
border-radius: 50rpx;
height: 70rpx;
line-height: 70rpx;
}
}
.cart-point {
width: 26rpx;
height: 26rpx;
position: fixed;
z-index: 1000;
background: #f00;
border-radius: 50%;
transition: all 0.05s;
}
.category-empty {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
image {
width: 380rpx;
}
.tips {
font-size: 26rpx;
font-weight: 500;
color: #999;
margin-top: 50rpx;
}
}
.end-tips {
text-align: center;
color: #999;
font-size: 24rpx;
padding: 20rpx 0;
opacity: 0;
}
// 全屏骨架占位 — 不画 navbar/TabBar (由外层 category.vue 渲染), 避免错位
// 兼容鸿蒙元服务 (JSVM 对 gap 支持不一): 用 margin 兜底替代 gap
.category-page-skeleton {
flex: 1;
height: 0; // 与 .content-box 一致, 让 flex 子项撑开父容器剩余高度
display: flex;
background-color: #f4f6fa;
.skeleton-content {
flex: 1;
display: flex;
min-height: 0;
.skeleton-tree {
width: 170rpx;
background-color: #f5f5f5;
padding: 20rpx 0;
box-sizing: border-box;
display: flex;
flex-direction: column;
// #ifndef MP-HARMONY
gap: 18rpx;
// #endif
flex-shrink: 0;
.skeleton-tree-item {
height: 40rpx;
// #ifndef MP-HARMONY
margin: 0 28rpx;
// #endif
// #ifdef MP-HARMONY
margin: 9rpx 28rpx; // 18rpx 间距 上下各 9rpx, 替代 gap:18rpx
// #endif
border-radius: 12rpx;
background: linear-gradient(90deg, #ececec 25%, #f5f5f5 37%, #ececec 63%);
background-size: 400% 100%;
animation: category-skeleton-shimmer 1.4s ease infinite;
}
}
.skeleton-grid {
flex: 1;
padding: 24rpx;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
// #ifndef MP-HARMONY
gap: 16rpx;
// #endif
background-color: #f4f6fa;
min-width: 0;
.skeleton-card {
width: calc((100% - 32rpx) / 3);
height: 220rpx;
// #ifdef MP-HARMONY
margin: 8rpx;
// #endif
border-radius: 16rpx;
background: linear-gradient(90deg, #ececec 25%, #f5f5f5 37%, #ececec 63%);
background-size: 400% 100%;
animation: category-skeleton-shimmer 1.4s ease infinite;
}
}
}
}
@keyframes category-skeleton-shimmer {
0% {
background-position: 100% 50%;
}
100% {
background-position: 0 50%;
}
}
// 风格四
.category-template-4 {
// 模板4 顶部搜索框(图片样式)
.search-box-template-4 {
padding: 20rpx 30rpx 16rpx;
background: #fff;
.search-content {
height: 64rpx;
background-color: #f5f5f5;
border-radius: 40rpx;
input {
height: 64rpx;
line-height: 64rpx;
padding: 0 30rpx 0 30rpx;
background: #f5f5f5;
border-radius: 40rpx;
font-size: 26rpx;
color: #333;
}
.iconfont {
font-size: 32rpx;
color: #89899a;
width: 70rpx;
text-align: center;
right: 10rpx;
}
}
}
// 模板4 底部购物车栏(图片样式)
.cart-box-template-4 {
height: 110rpx;
width: 100%;
flex-shrink: 0;
background: #fff;
border-top: 1rpx solid #f0f0f0;
position: relative;
z-index: 10;
box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.04);
.left-wrap {
display: flex;
align-items: center;
}
.cart-icon {
width: 90rpx;
height: 90rpx;
position: relative;
margin-top: -10rpx;
.iconfont {
color: var(--btn-text-color, #fff);
width: 80rpx;
height: 80rpx;
background-color: $base-color;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 40rpx;
box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.35);
}
.num {
position: absolute;
top: 0;
right: 0;
transform: translate(20%, 0);
display: inline-block;
box-sizing: border-box;
color: #fff;
line-height: 1.2;
text-align: center;
font-size: 24rpx;
padding: 0 8rpx;
min-width: 32rpx;
border-radius: 18rpx;
background-color: var(--price-color, #FF6B35);
border: 2rpx solid #fff;
}
}
.price {
margin-left: 16rpx;
display: flex;
align-items: baseline;
line-height: 1;
.title {
color: #333;
font-size: 28rpx;
}
.unit,
.money {
color: var(--price-color, #FF6B35);
font-weight: bold;
}
.unit {
font-size: 24rpx;
}
.money {
font-size: 36rpx;
}
}
.right-wrap {
display: flex;
align-items: center;
}
.settlement-btn {
margin: 0;
width: 220rpx;
font-weight: bold;
border-radius: 50rpx;
height: 80rpx;
line-height: 80rpx;
font-size: 30rpx;
background: $base-color;
color: var(--btn-text-color, #fff);
&::after {
border: none;
}
}
}
.search-box {
.search-content input {
background-color: #f1f1f1;
}
}
::v-deep .template-four {
position: relative;
z-index: 1;
.template-four-wrap {
position: relative;
z-index: 1;
padding-left: 20rpx;
padding-right: 80rpx;
padding-bottom: 10rpx;
display: flex;
height: 172rpx;
align-items: baseline;
box-sizing: border-box;
box-shadow: 0 4rpx 4rpx rgba(123, 123, 123, 0.1);
}
.template-four-popup {
display: flex;
flex-direction: column;
overflow: hidden;
.title {
line-height: 1;
margin-bottom: 20rpx;
font-weight: bold;
}
.template-four-scroll {
display: flex;
flex-wrap: wrap;
align-items: baseline;
align-content: baseline;
padding: 20rpx;
white-space: nowrap;
height: 380rpx;
box-sizing: border-box;
.uni-scroll-view-content {
flex-wrap: wrap;
align-items: baseline;
align-content: baseline;
}
.item {
display: flex;
flex-direction: column;
align-items: center;
padding: 4rpx 0;
color: #666;
margin-right: 16rpx;
border-radius: 40rpx;
margin-bottom: 10rpx;
width: calc((100% - 64rpx) / 5);
&:nth-child(5n + 5) {
margin-right: 0;
}
.image-warp {
margin-bottom: 6rpx;
padding: 4rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 42rpx;
border: 4rpx solid transparent;
}
image {
width: 84rpx;
height: 84rpx;
border-radius: 32rpx;
}
.text {
padding: 2rpx 16rpx;
border-radius: 40rpx;
font-size: $font-size-tag;
}
&.selected {
.text {
background-color: $base-color;
color: var(--btn-text-color);
}
}
}
}
.pack-up {
font-size: $font-size-tag;
color: #888888;
height: 74rpx;
display: flex;
align-items: center;
justify-content: center;
border-top: 2rpx solid #f2f2f2;
.iconfont {
font-size: 40rpx;
margin-left: -4rpx;
}
}
}
.category-item-all {
position: absolute;
bottom: 0;
z-index: 1;
right: 0;
top: 0;
width: 72rpx;
line-height: 1;
background-color: #fff;
.category-item-all-wrap {
position: absolute;
bottom: 0;
right: 0;
top: 0;
left: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 2;
}
.text {
writing-mode: tb-rl;
margin-bottom: 6rpx;
letter-spacing: 4rpx;
font-size: $font-size-tag;
font-weight: bold;
}
.img {
width: 20rpx;
height: 20rpx;
}
&::after {
content: '';
// box-shadow: -4rpx 10rpx 20rpx rgba(0, 0, 0, 0.1);
position: absolute;
left: 0;
width: 10rpx;
// height: 100rpx;
top: 20%;
bottom: 20%;
// transform: translateY(-50%);
// background-color: #F0F5FF;
}
}
.uni-scroll-view-content {
display: flex;
}
.category-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-width: 130rpx;
flex-shrink: 0;
margin-right: 20rpx;
padding: 4rpx 0;
&:last-of-type {
margin-right: 0;
}
.image-warp {
margin-bottom: 6rpx;
padding: 4rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 42rpx;
border: 4rpx solid transparent;
}
image {
width: 84rpx;
height: 84rpx;
border-radius: 32rpx;
}
.text {
font-size: $font-size-tag;
}
}
.select {
.text {
padding: 8rpx 16rpx;
border-radius: 40rpx;
color: #fff;
font-size: $font-size-tag;
line-height: 1;
}
}
}
// 风格2 复用模板4 的底部购物车栏样式
.category-template-2 {
// 模板4 底部购物车栏(图片样式)
.cart-box-template-4 {
height: 110rpx;
width: 100%;
flex-shrink: 0;
background: #fff;
border-top: 1rpx solid #f0f0f0;
position: relative;
z-index: 10;
box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.04);
.left-wrap {
display: flex;
align-items: center;
}
.cart-icon {
width: 90rpx;
height: 90rpx;
position: relative;
margin-top: -10rpx;
.iconfont {
color: var(--btn-text-color, #fff);
width: 80rpx;
height: 80rpx;
background-color: $base-color;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 40rpx;
box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.35);
}
.num {
position: absolute;
top: 0;
right: 0;
transform: translate(20%, 0);
display: inline-block;
box-sizing: border-box;
color: #fff;
line-height: 1.2;
text-align: center;
font-size: 24rpx;
padding: 0 8rpx;
min-width: 32rpx;
border-radius: 18rpx;
background-color: var(--price-color, #FF6B35);
border: 2rpx solid #fff;
}
}
.price {
margin-left: 16rpx;
display: flex;
align-items: baseline;
line-height: 1;
.title {
color: #333;
font-size: 28rpx;
}
.unit,
.money {
color: var(--price-color, #FF6B35);
font-weight: bold;
}
.unit {
font-size: 24rpx;
}
.money {
font-size: 36rpx;
}
}
.right-wrap {
display: flex;
align-items: center;
}
.settlement-btn {
margin: 0;
width: 220rpx;
font-weight: bold;
border-radius: 50rpx;
height: 80rpx;
line-height: 80rpx;
font-size: 30rpx;
background: $base-color;
color: var(--btn-text-color, #fff);
&::after {
border: none;
}
}
}
}
.content-wrap .categoty-goods-wrap .goods-list {
margin-top: 30rpx;
}
.tree-wrap .category-item.select::before {
border-top-right-radius: 8rpx;
border-bottom-right-radius: 8rpx;
}
}
// ===================== 模板1 仿京东/淘宝商品分类样式 =====================
.category-template-1 {
.search-box {
padding: 20rpx 24rpx;
background: #fff;
.search-content {
background-color: #f5f5f5;
border-radius: 40rpx;
input {
background: #f5f5f5;
border-radius: 40rpx;
padding: 0 30rpx;
font-size: 26rpx;
color: #333;
}
.iconfont {
right: 16rpx;
color: #89899a;
font-size: 32rpx;
}
}
}
.content-box {
// 模板1 + 三级分类: 标题行 (离型纸 ----) 所在整片右半边需要去掉白色,
// 把 .content-box 也改为透明, 让右侧不再被强制白; 再让 .right-flex-wrap 显式
// 染 #f5f5f5, 与左侧 tree-wrap 默认色一致, 标题行只显示文字+短横线.
background: transparent;
.tree-wrap {
width: 170rpx;
background-color: #fff;
}
.right-flex-wrap {
background: #f5f5f5;
.content-wrap {
background: transparent;
}
}
}
// 穿透到子组件 diy-category-item: 调整 banner 横向内边距 + 分类网格容器为透明
::v-deep .category-adv {
padding: 0 20rpx;
margin-top: 0;
}
::v-deep .category-list {
margin: 0 20rpx;
border-radius: 0;
background: transparent;
padding: 0;
}
// 穿透到子组件 diy-category-item: 模板1标题行定高, 与左侧第一个一级分类等高对齐
::v-deep .category-title-with-lines {
height: 100rpx;
box-sizing: border-box;
padding: 30rpx 0 20rpx;
}
// 模板1: 左侧第一个一级分类定高, 与右侧标题行(如"离型纸 ----")垂直对齐
.tree-wrap .category-item.first-category-item {
height: 100rpx;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
white-space: nowrap;
overflow: hidden;
view {
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
// 穿透修复: 图片不需要灰色背景框,与目标图保持一致
::v-deep .img-box {
background: transparent !important;
border-radius: 0 !important;
width: 70% !important;
padding-bottom: 70% !important;
margin-bottom: 16rpx !important;
}
::v-deep .img-box image {
width: 100% !important;
height: 100% !important;
left: 0 !important;
top: 0 !important;
transform: none !important;
}
// 左侧分类栏样式
.tree-wrap .category-item {
padding: 28rpx 16rpx;
background-color: var(--diy-category-tree-normal-bg-color, #f5f5f5);
border-radius: 0;
view {
color: var(--diy-category-tree-normal-text-color, #222222);
font-size: 26rpx;
line-height: 1.3;
}
&.border-top,
&.border-bottom {
border-radius: 0;
}
&.select {
background: var(--diy-category-tree-select-bg-color, #fff);
view {
color: var(--diy-category-tree-select-text-color, #333);
font-weight: bold;
font-size: 28rpx;
}
&::before {
content: ' ';
width: 6rpx;
height: 32rpx;
background: var(--base-color, #FF6B35);
display: block;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
border-radius: 0 6rpx 6rpx 0;
}
}
}
}
// ===================== 模板5 商品分类样式(新增: 基于模板1双栏布局) =====================
.category-template-5 {
.search-box {
padding: 20rpx 24rpx;
background: #fff;
.search-content {
background-color: #f5f5f5;
border-radius: 40rpx;
input {
background: #f5f5f5;
border-radius: 40rpx;
padding: 0 30rpx;
font-size: 26rpx;
color: #333;
}
.iconfont {
right: 16rpx;
color: #89899a;
font-size: 32rpx;
}
}
}
.content-box {
// 模板5 + 三级分类: 标题行所在整片右半边去掉白色, 与模板1一致
background: transparent;
.tree-wrap {
width: 170rpx;
background-color: #fff;
}
.right-flex-wrap {
background: #f5f5f5;
.content-wrap {
background: transparent;
}
// 模板5 纵向平铺: 每个一级分类独占一行(与模板99"全部分类"结构一致)
.child-category {
width: 100%;
box-sizing: border-box;
}
}
}
// 穿透到子组件 diy-category-item: 调整 banner 横向内边距 + 分类网格容器为透明
::v-deep .category-adv {
padding: 0 20rpx;
margin-top: 0;
}
// 模板5: .category-list 直接沿用子组件的 legacy 白底卡片样式(不再覆盖为透明)
// 穿透到子组件 diy-category-item: 模板5标题行定高, 与左侧第一个一级分类等高对齐
::v-deep .category-title-with-lines {
height: 100rpx;
box-sizing: border-box;
padding: 30rpx 0 20rpx;
}
// 模板5: 左侧第一个一级分类定高, 与右侧标题行垂直对齐
.tree-wrap .category-item.first-category-item {
height: 100rpx;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
white-space: nowrap;
overflow: hidden;
view {
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
// 模板5: 分类网格的 .img-box / .name 直接沿用子组件 legacy 尺寸(80% + 名称),
// 不再做 70%/透明 覆盖, 以与 Branch_5cfee417 的商品分类保持一致
// 左侧分类栏样式
.tree-wrap .category-item {
padding: 28rpx 16rpx;
background-color: var(--diy-category-tree-normal-bg-color, #f5f5f5);
border-radius: 0;
view {
color: var(--diy-category-tree-normal-text-color, #222222);
font-size: 26rpx;
line-height: 1.3;
}
&.border-top,
&.border-bottom {
border-radius: 0;
}
&.select {
background: var(--diy-category-tree-select-bg-color, #fff);
view {
color: var(--diy-category-tree-select-text-color, #333);
font-weight: bold;
font-size: 28rpx;
}
&::before {
content: ' ';
width: 6rpx;
height: 32rpx;
background: var(--base-color, #FF6B35);
display: block;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
border-radius: 0 6rpx 6rpx 0;
}
}
}
}
// ===================== 模板99 商品分类样式 =====================
.category-template-99 {
.search-box {
padding: 20rpx 24rpx;
background: #fff;
border-bottom: 1rpx solid #f0f0f0;
.search-content {
height: 64rpx;
background-color: #f5f5f5;
border-radius: 40rpx;
input {
height: 64rpx;
line-height: 64rpx;
padding: 0 80rpx 0 30rpx;
background: #f5f5f5;
border-radius: 40rpx;
font-size: 26rpx;
color: #333;
}
.iconfont {
font-size: 32rpx;
color: #89899a;
width: 70rpx;
text-align: center;
}
}
> .iconfont {
font-size: 40rpx;
color: #666;
margin-left: 20rpx;
}
}
.content-box {
.tree-wrap {
width: 200rpx;
background-color: #f5f5f5;
}
.right-flex-wrap {
flex: 1;
width: 0;
background: #f5f5f5;
}
}
.tree-wrap .category-item {
padding: 30rpx 16rpx;
background-color: var(--diy-category-tree-normal-bg-color, #f5f5f5);
white-space: normal;
word-break: break-all;
view {
color: var(--diy-category-tree-normal-text-color, #222222);
font-size: 26rpx;
line-height: 1.5;
}
&.select {
background: var(--diy-category-tree-select-bg-color, #fff);
border-bottom: 1rpx solid #f0f0f0;
view {
color: var(--diy-category-tree-select-text-color, #333);
font-weight: bold;
font-size: 28rpx;
}
&::before {
content: ' ';
width: 6rpx;
height: 36rpx;
background: #FF4646;
display: block;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
border-radius: 0 6rpx 6rpx 0;
}
}
}
// 模板99: 第一个分类项固定高度,与右侧二级分类按钮区域对齐
.tree-wrap .category-item.first-category-item {
height: 100rpx;
display: flex;
align-items: center;
white-space: nowrap;
overflow: hidden;
view {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.content-wrap {
background: #f5f5f5;
padding: 0;
// #ifdef H5
::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
background: transparent;
}
// #endif
}
.child-category {
width: 100%;
box-sizing: border-box;
}
}
</style>