945 lines
24 KiB
Vue
Raw Normal View History

2025-05-08 09:16:37 +08:00
<template>
<div class="list_box" v-show="loading" :style="{'padding': statusBarHeight + 'px 0 0'}">
<view class="list_one flex">
<view class="item flex" v-for="i in 4" :key="i">
<view class="circle box"></view>
<view class="text box"></view>
</view>
</view>
<view class="list_two">
<div class="list_left">
<view class="box_left box"></view>
<view class="box_left box"></view>
<view class="box_left box"></view>
<view class="box_left box"></view>
<view class="box_left box"></view>
<view class="box_left box"></view>
<view class="box_left box"></view>
</div>
<div class="list_right">
<view class="item flex1" v-for="i in 8" :key="i">
<view class="img box"></view>
<view class="cont">
<view class="tit box"></view>
<view class="desc box"></view>
<view class="desc box"></view>
</view>
</view>
</div>
</view>
</div>
<view>
<view class="whole" v-show="!loading">
<view id="content">
<view class="searchBox flex1" :style="{'padding': (statusBarHeight + 10) + 'px 24rpx 20rpx'}">
<view class="box" @click="toSearch">
<up-icon name="search" size="18" color="#656565" />
<text>&nbsp;请输入搜索关键词</text>
</view>
</view>
<view class="oneBox">
<scroll-view class="topBox" scroll-x :scroll-left="scrollLeft" :scroll-with-animation="true">
<view v-for="(item, index) in topList" :key="item.id" :class="['item', index == topIndex ? 'active' : '']" @click="handleTop(item, index)">
<image class="img" mode="aspectFill" :src="item.pic_url"></image>
<text>{{item.name}}</text>
</view>
</scroll-view>
<view class="switch flex" @click="showDown = true">
<view></view>
<view style="margin-bottom: 6rpx;"></view>
<up-icon name="list-dot" color="#333" size="16" />
<image class="bg" src="https://ct-upimg.yx090.com/ju8hn6/shop/image/2024/10/08/MMmwsyxuwnCaV7BpQCZ1fGUDtwXTSpdT6riqUX1u.png" mode="widthFix"></image>
</view>
</view>
<view style="height: 20rpx;"></view>
</view>
<view class="classBox" :style="{height: 'calc(100% - ' + topHeight + 'px)'}">
<view class="leftBox">
<view class="item"
v-for="(item, index) in leftList"
:key="item.id"
:class="leftIndex === index ? 'active' : ''"
@click="handleLeft(item.id, index)">{{item.name}}</view>
</view>
<view class="rightBox">
<view class="sort_box">
<view class="sort_item" v-for="(item, index) in sortList" :key="index" @click="chooseSort(index)">
<text :class="sortIndex === index ? 'choose' : ''">{{item}}</text>
<view class="icon" v-if="index === 2">
<up-icon name="arrow-up" size="10" :color="priceType && sortIndex === 2 ? Color : ''" />
<up-icon name="arrow-down" size="10" :color="!priceType && sortIndex === 2 ? Color : ''" />
</view>
</view>
</view>
<view class="scrollBox">
<swiper :vertical="true" :circular="false" class="swiper" @transition="transition" @touchend="touchend" @animationfinish="animationfinish">
<swiper-item class="swiperItem">
<scroll-view :scroll-y="true" class="scrollView" :scroll-with-animation="false" @scrolltolower="scorllBottom" :scroll-top="topNum"
:lower-threshold="0" :enable-passive="true"
:bounces="true" :enhanced="true" :scroll-anchoring="true" :clip="false" type="list"
@dragstart="onDragStart" @dragging="onDragging" @dragend="onDragEnd">
<view class="upbox flex" v-if="downPulling && leftIndex != 0">
<up-icon name="arrow-up" size="14" color="#999" />&nbsp;下滑查看上一分类
</view>
<view class="item" v-for="(item, index) in groupList" :key="item.id" @click.stop="toGoods(item.id)">
<view class="faceImg">
<image :src="item.face_img + '?x-oss-process=image/format,webp'" :webp="true" mode="aspectFill" class="image"></image>
<view class="out" v-if="item.sold_status === 2">
<img src="https://ct-upimg.yx090.com/ju8hn6/shop/image/2024/01/16/Cr6cRRhhqYNhScigxIyumyV9hXXMI3vKvsE1jgt0.png" class="out_img" />
</view>
<view class="out" v-else-if="item.sold_status === 0">
<img src="https://ct-upimg.yx090.com/ju8hn6/shop/image/2024/08/21/QBhUheeYWSJ3OGM27fkAufJAlFSA8GBhWjpY5oNy.png" class="out_img" />
</view>
<view class="out" v-else-if="item.total_stock === 0">
<img src="https://ct-upimg.yx090.com/ju8hn6/shop/image/2024/08/21/pMTv6QiZZEpSqkJmk7hMcbEzIuNzMyp7YVBbe42H.png" class="out_img" />
</view>
</view>
<view class="info">
<view class="box">
<view class="name">{{item.title}}</view>
<view class="desc">{{item.characteristic}}</view>
<view v-if="item.actives && item.actives.length">
<view v-for="it in item.actives" class="move_img">{{it.name}}</view>
</view>
</view>
<view class="price flex1">
<view>
<text v-if="parseFloat(item.min_price) == parseFloat(item.max_price)">{{item.min_price}}</text>
<text v-else>{{item.min_price * 1}}~{{item.max_price}}</text>
</view>
<view v-if="item.group_goods.length === 1">
<view v-if="item.group_goods[0].specs_type == 0 && item.group_goods[0].goods.limit_type == 0"
@click.stop="openSku(item, item.group_goods[0])" class="cart flex">
<up-icon name="shopping-cart" color="#fff" size="20" />
</view>
<view v-else-if="item.group_goods[0].specs_type == 0 && item.group_goods[0].goods.limit_type != 0" class="cart flex">
<up-icon name="shopping-cart" color="#fff" size="20" />
</view>
<view v-else-if="item.group_goods[0].specs_type == 1" @click.stop="openSku(item, item.group_goods[0])" class="cart flex">
<up-icon name="shopping-cart" color="#fff" size="20" />
</view>
<view v-else class="cart flex">
<up-icon name="shopping-cart" color="#fff" size="20" />
</view>
</view>
<view class="cart flex" v-else>
<up-icon name="shopping-cart" color="#fff" size="20" />
</view>
</view>
</view>
</view>
<view class="bottom" v-if="groupList.length && page >= lastPage && !loading1 && leftIndex == leftList.length - 1">
没有更多了
</view>
<view class="pull" v-if="leftIndex < leftList.length - 1 && page == lastPage">
<template v-if="upPulling"><up-icon name="arrow-down" size="14" color="#999" />&nbsp;释放查看下一分类</template>
<template v-else><up-icon name="arrow-up" size="14" color="#999" />&nbsp;上拉查看下一分类</template>
</view>
<!-- <view class="pull" v-if="leftIndex < leftList.length - 1 && page == lastPage" :class="isDrag ? 'pm' : ''">
<template v-if="isDrag"><van-icon name="arrow-down" size="14" color="#999" />&nbsp;释放查看下一分类</template>
<template v-else><van-icon name="arrow-up" size="14" color="#999" />&nbsp;上拉查看下一分类</template>
</view> -->
<view v-if="!groupList.length && !loading1">
<up-empty mode="list" icon="https://ct-upimg.yx090.com/g.ii090/images/sprite/empty/list.png" text="暂无数据"></up-empty>
</view>
</scroll-view>
</swiper-item>
</swiper>
</view>
</view>
</view>
<up-popup :show="showDown" mode="top" :round="5" :close-on-click-overlay="true" @close="showDown = false" :z-index="98" :duration="300">
<view class="itemBox" :style="{'padding': barHeight + 'px 0 0'}">
<view class="box">
<view style="display: flex;flex-wrap: wrap;">
<view v-for="(item, index) in topList" :key="item.id" :class="['item', index == topIndex ? 'active' : '']" @click="handleTop(item, index)">
<image class="img" mode="aspectFill" :src="item.pic_url"></image>
<text>{{item.name}}</text>
</view>
</view>
</view>
<view class="up flex" @click="showDown = false">点击收起&nbsp;<up-icon name="arrow-up" size="14" color="#999" /></view>
</view>
</up-popup>
</view>
<choose-sku
:show-sku="showSku"
:show1="true"
:sku-info="skuInfo"
:skus_1="skus_1"
@close="showSku = false"
isTabbar
:groupId="groupId"
:remind-stock="remind_stock"
:sold-status="orign_status"
:fenxiang="true"
:can-buy="can_buy"/>
<tabbar :chooseIndex="1" :num="cartNum" :msgNum="msgNum" />
</view>
</template>
<script>
import { ref, reactive, toRefs } from 'vue'
import { get, post } from '@/api/request.js'
import { goodsItem, showToast, whetherLogin } from '@/components/common.js'
import tabbar from '@/components/tabbar/index.vue'
import { Style } from '@/utils/list.js'
import chooseSku from '@/components/sku/ChooseSku.vue'
export default {
components: {
tabbar, chooseSku
},
setup() {
const data = reactive({
statusBarHeight: 24,
topNum: 0,
topList: [],
leftIndex: 0,
intoView: '',
loading: true,
Color: '',
priceColor: '',
msgNum: Number(uni.getStorageSync('msgNum')) || 0,
cartNum: Number(uni.getStorageSync('cartNum')) || 0,
orign_status: 0,
topIndex: 0,
scrollLeft: 0,
leftList: [],
page: 1,
sortList: ['综合', '销量', '价格', '上新'],
sortIndex: 0,
priceType: false,
groupList: [],
showDown: false,
sort: {
sales: '',
price: '',
score: 2,
new: ''
},
lastPage: 0,
loading1: false,
topHeight: 161,
barHeight: 66,
groupId: 0,
pullNum: 0,
can_buy: true,
remind_stock: false,
isDrag: false,
startTop: 0,
hasDrag: false,
upPulling: false,
downPulling: false,
showPull: false,
orignId: 0
})
const getClassifyList = async() => {
await get('/api/app/group').then((res) => {
data.topList = res.data
data.leftList = res.data && res.data[0] && res.data[0].children || []
data.page = 1
if(data.topIndex) {
data.leftList = res.data && res.data[data.topIndex] && res.data[data.topIndex].children || []
} else if(data.orignId) {
for (var index = 0; index < res.data.length; index++) {
let itm = res.data[index]
if(itm.id == data.orignId) {
data.topIndex = index
break
}
if(itm.children) {
for (var i = 0; i < itm.children.length; i++) {
if(itm.children[i].id == data.orignId) {
data.topIndex = index
data.leftIndex = i
break
}
}
}
}
data.leftList = res.data && res.data[data.topIndex] && res.data[data.topIndex].children || []
}
getGroupList(0, 'init')
})
}
const handleLeft = (id, index) => {
if(data.leftIndex !== index) {
data.leftIndex = index
data.page = 1
getGroupList(0)
}
}
function getGroupList(val = 0) {
data.upPulling = false
data.downPulling = false
data.showPull = false
if(!data.leftList.length) {
data.groupList = []
return
}
data.loading1 = true
uni.showLoading({
title: '加载中...',
mask: true
})
let classId = data.leftList[data.leftIndex].id
let params = {
page: data.page,
shop_group_id: classId,
...data.sort
}
get('/api/app/search', params).then((res) => {
if(val == 0) {
data.topNum = data.topNum == 0 ? 0.1 : 0
}
data.groupList = val == 0 ? res.data : data.groupList.concat(res.data)
data.lastPage = res.meta.last_page
data.upPulling = false
data.downPulling = false
uni.hideLoading()
setTimeout(() => {
data.loading1 = false
}, 200)
}).catch(() => {
setTimeout(() => {
data.loading1 = false
}, 200)
})
}
function getCartNum() {
get('/api/v1/carts/num').then((res) => {
data.cartNum = Number(res.data.num)
})
}
function toSearch() {
uni.navigateTo({
url: '/pages/search/index'
})
}
function handleTop(item, index) {
if(data.topIndex !== index) {
if(index <= 2) {
data.scrollLeft = 0
} else {
data.scrollLeft = (index - 2) * 82
}
data.topIndex = index
data.leftIndex = 0
data.leftList = data.topList[index] && data.topList[index].children || []
data.page = 1
getGroupList(0)
}
data.showDown = false
}
function scorllBottom() {
if(data.loading1) {
return
}
if (data.page < data.lastPage) {
data.page++
getGroupList(1)
} else if(data.page == data.lastPage && data.leftIndex < data.leftList.length - 1) {
setTimeout(() => {
data.showPull = true
}, 500)
}
}
function chooseSort(index) {
data.sortIndex = index
if (index === 2) {
data.priceType = !data.priceType
data.sort.price = data.priceType ? 1 : 2
data.sort.new = ''
} else {
data.sort.price = ''
}
data.sort.sales = index === 1 ? 2 : ''
data.sort.score = index === 0 ? 2 : ''
data.sort.new = index === 3 ? 2 : ''
data.page = 1
getGroupList(0)
}
function getTopHeight() {
setTimeout(() => {
uni.createSelectorQuery().select('.searchBox').boundingClientRect((res) => {
data.barHeight = res ? res.height : 66
}).exec()
uni.createSelectorQuery().select('#content').boundingClientRect((res) => {
data.topHeight = res ? res.height : 161
}).exec()
}, 500)
}
const { toGoods, showSku, getGoodsSku, skuInfo, skus_1 } = goodsItem()
function openSku(item, row) {
data.orign_status = item.sold_status
if(item.sold_status == 1 && item.total_stock <= 0) {
data.orign_status = 2
}
data.groupId = item.id
get(`/api/app/goods/group/detail/${item.id}`).then((res) => {
data.remind_stock = res.data.is_subscribe_remind_stock
data.can_buy = !res.data.is_new_purchase || (res.data.is_new_purchase && res.user.is_purchase === 0)
getGoodsSku(row)
})
}
function transition(e){
if(data.loading1) {
data.upPulling = false
data.downPulling = false
return
}
if(e.detail.dy < 0) { // 下滑
data.upPulling = false
data.downPulling = true
} else if(e.detail.dy > 0) { // 上滑
data.downPulling = false
data.upPulling = true
}
}
function animationfinish(){
data.upPulling = false
data.downPulling = false
}
function touchend(e){
if(data.loading1) {
return
}
// console.log('e', e.currentTarget.offsetTop)
uni.createSelectorQuery().select('.swiperItem').boundingClientRect((res) => {
// console.log('res', res.top)
if(Math.round(res.top) > e.currentTarget.offsetTop && data.leftIndex != 0) { // 下滑查看上一分类
data.loading1 = true
data.leftIndex -= 1
data.page = 1
getGroupList(0)
} else if(Math.round(res.top) < e.currentTarget.offsetTop && data.leftIndex != data.leftList.length - 1) { // 上滑拉查看下一分类
data.loading1 = true
data.leftIndex += 1
data.page = 1
getGroupList(0)
}
}).exec()
}
function onDragStart(e){
console.log('onDragStart', e.detail.scrollTop)
if(data.page == data.lastPage && data.leftIndex < data.leftList.length - 1 && data.showPull) {
data.startTop = e.detail.scrollTop
}
}
function onDragging(){
if(data.page == data.lastPage && data.leftIndex < data.leftList.length - 1 && data.showPull) {
data.isDrag = true
}
}
function onDragEnd(e){
console.log('onDragEnd', e.detail.scrollTop)
data.isDrag = false
if(e.detail.scrollTop == data.startTop && data.showPull) {
data.leftIndex += 1
data.page = 1
getGroupList(0)
}
}
return {
whetherLogin,
toGoods,
showSku,
getGoodsSku,
skuInfo,
skus_1,
...toRefs(data),
getClassifyList,
getGroupList,
getCartNum,
handleLeft,
handleTop,
toSearch,
scorllBottom,
chooseSort,
getTopHeight,
openSku,
transition,
animationfinish,
touchend,
onDragStart,
onDragging,
onDragEnd
}
},
async onLoad(options) {
this.orignId = (options.classId || 0) * 1
uni.getSystemInfo({
success: (res) => {
this.statusBarHeight = res.statusBarHeight || 24
}
})
await this.getClassifyList()
this.Color = uni.getStorageSync('theme_color')
this.priceColor = Style[uni.getStorageSync('theme_index') * 1].priceColor
this.loading = false
this.getTopHeight()
if(whetherLogin()) {
this.getCartNum()
}
}
}
</script>
<style lang="scss" scoped>
.flex{
display: flex;
align-items: center;
justify-content: center;
}
.flex1{
display: flex;
align-items: center;
justify-content: space-between;
}
.searchBox{
font-size: 28rpx;
color: #C7C7C7;
position: relative;
z-index: 99;
background-color: #fff;
height: 64rpx;
.box{
display: flex;
align-items: center;
padding: 0 20rpx;
box-sizing: border-box;
background-color: #F7F8FA;
border-radius: 60rpx;
height: 60rpx;
width: 100%;
}
}
.whole{
background: #fff;
height: calc(100vh - 55px);
.oneBox{
height: 170rpx;
display: flex;
align-items: center;
.topBox{
width: calc(100% - 80rpx);
display: flex;
align-items: center;
white-space: nowrap;
box-sizing: border-box;
position: relative;
z-index: 2;
.item{
text-align: center;
display: inline-block;
color: #000;
font-size: 30rpx;
padding: 0 24rpx;
.img{
width: 110rpx;
height: 110rpx;
border-radius: 50%;
border: 4rpx solid transparent;
margin-bottom: 10rpx;
display: block;
box-sizing: border-box;
}
text{
display: inline-block;
padding: 10rpx;
line-height: 1;
font-size: 24rpx;
color: #333;
border-radius: 6rpx;
}
&.active{
.img{
border: 4rpx solid v-bind('Color');
}
text{
color: #fff;
background-color: v-bind('Color');
}
}
}
}
.switch{
width: 80rpx;
height: 100%;
font-size: 28rpx;
color: #333;
font-weight: 600;
flex-direction: column;
line-height: 1.3;
position: relative;
z-index: 1;
.bg{
position: absolute !important;
top: 0;
width: 80rpx;
left: -80rpx;
}
}
}
.classBox{
width: 100%;
overflow: hidden;
display: flex;
box-sizing: border-box;
background: #F9F9F9;
border-top: 1px solid #f6f6f6;
.leftBox{
height: 100%;
overflow: auto;
width: 180rpx;
.item{
position: relative;
font-size: 28rpx;
color: #666;
display: flex;
align-items: center;
box-sizing: border-box;
min-height: 90rpx;
padding: 20rpx 0 20rpx 30rpx;
&.active{
color: #333;
font-weight: 600;
background: #fff;
&:before{
content: '';
position: absolute;
left: 0;
height: 50rpx;
top: 20rpx;
background: v-bind('Color');
width: 6rpx;
}
}
}
}
.rightBox{
width: calc(100% - 180rpx);
background: #fff;
height: 100%;
.sort_box {
display: flex;
background-color: #fff;
height: 70rpx;
align-items: center;
justify-content: flex-end;
box-sizing: border-box;
width: 100%;
.sort_item {
display: flex;
align-items: center;
justify-content: center;
margin: 0 24rpx;
color: #888;
font-size: 24rpx;
.icon {
display: flex;
flex-direction: column;
margin-left: 10rpx;
.iconfont {
font-size: 24rpx;
line-height: 16rpx;
}
}
}
.choose {
color: v-bind('Color');
font-weight: bold;
}
}
.scrollBox{
height: calc(100% - 70rpx);
swiper{
height: 100%;
width: 100%;
}
.swiperItem{
height: 100%;
box-sizing: border-box;
overflow: auto;
}
}
.scrollView{
height: 100%;
overflow: auto;
position: relative;
background-color: #fff;
.upbox{
height: 40px;
transition: all 0.3s;
font-size: 24rpx;
color: #999;
}
.item{
display: flex;
background-color: #fff;
border-radius: 10rpx;
padding: 20rpx;
box-sizing: border-box;
.faceImg{
position: relative;
margin-right: 20rpx;
.image {
width: 180rpx;
height: 180rpx;
border-radius: 10rpx;
}
.out{
position: absolute;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
top: 0;
left: 0;
.out_img{
width: 100rpx;
height: 100rpx;
}
}
}
.info{
flex: 1;
border-bottom: 1px solid #F6F6F6;
min-height: 180rpx;
padding-bottom: 24rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
.name {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
white-space: break-spaces;
font-size: 28rpx;
line-height: 40rpx;
}
.desc{
color: #BF9E6B;
margin-bottom: 10rpx;
font-size: 24rpx;
}
.price{
font-size: 32rpx;
color: v-bind('priceColor');
.cart{
width: 50rpx;
height: 50rpx;
border-radius: 50%;
background: v-bind('Color');
}
}
}
}
.bottom{
text-align: center;
font-size: 24rpx;
color: #999;
padding: 20rpx 0 60rpx;
}
.pull{
font-size: 24rpx;
text-align: center;
padding: 30rpx 0 100rpx;
box-sizing: border-box;
height: 160rpx;
color: #999;
transition: all 0.3s;
&.pm{
padding: 10rpx 0 100rpx;
}
}
}
}
}
}
.itemBox{
.box{
height: 65vh;
overflow: auto;
.item{
text-align: center;
width: 20%;
margin-bottom: 24rpx;
.img{
width: 100rpx;
height: 100rpx;
border-radius: 50%;
border: 4rpx solid transparent;
margin: 0 auto 10rpx;
display: block;
box-sizing: border-box;
}
text{
display: inline-block;
padding: 10rpx;
line-height: 1;
font-size: 24rpx;
color: #333;
border-radius: 6rpx;
}
&.active{
.img{
border: 4rpx solid v-bind('Color');
}
text{
color: #fff;
background-color: v-bind('Color');
}
}
}
}
.up{
height: 90rpx;
border-top: 1px solid #eee;
color: #989898;
font-size: 26rpx;
}
}
.move_img {
display: inline-block;
font-size: 22rpx;
color: #fff;
margin: 3px 10px 3px 0;
background-image: url('@/static/image/move_img.png');
background-repeat: no-repeat;
padding: 0px 3px 0px 12px;
height: 40rpx;
background-size: 100% 100%;
line-height: 40rpx;
}
.list_box{
background: #fff;
height: 100vh;
width: 100%;
overflow: hidden;
padding-top: 70px;
box-sizing: border-box;
.list_one{
padding-top: 10px;
margin-bottom: 40rpx;
.item{
width: 25%;
padding: 0 20rpx;
box-sizing: border-box;
flex-direction: column;
.circle{
width: 110rpx;
height: 110rpx;
margin-bottom: 20rpx;
border-radius: 50%;
}
.text{
width: 100%;
height: 40rpx;
}
}
}
.list_two{
.list_left {
width: 180rpx;
height: 100%;
float: left;
.box_left {
height: 70rpx;
margin-bottom: 15rpx;
}
}
.list_right{
width: calc(100% - 220rpx);
margin-left: 20rpx;
height: 100%;
float: left;
.item{
margin-bottom: 30rpx;
.img{
height: 150rpx;
width: 150rpx;
float: left;
margin-bottom: 20rpx;
border-radius: 6rpx;
}
.cont{
width: calc(100% - 180rpx);
.tit{
width: 100%;
height: 40rpx;
margin-bottom: 20rpx;
}
.desc{
width: 60%;
height: 36rpx;
margin-bottom: 20rpx;
}
}
}
}
}
.box {
background: #e1e1e1;
animation: fade 2s infinite;
}
}
@keyframes fade {
from {
opacity: 1;
}
50% {
opacity: 0.5;
}
to {
opacity: 1;
}
}
</style>