feat(inde): 首页改版

This commit is contained in:
woody 2025-06-03 16:40:49 +08:00
parent 5c52892f34
commit 2f1a31d349
5 changed files with 801 additions and 250 deletions

View File

@ -1,5 +1,5 @@
<template> <template>
<view class="simple-vertical-swiper" :style="{ height: height + 'px' }"> <view class="simple-vertical-swiper" :style="{ height: height + 'rpx' }">
<view <view
class="swiper-wrapper" class="swiper-wrapper"
@touchstart.stop="onTouchStart" @touchstart.stop="onTouchStart"
@ -7,10 +7,10 @@
@touchend.stop="onTouchEnd" @touchend.stop="onTouchEnd"
> >
<view <view
v-for="(item, index) in items" v-for="(item, index) in displayItems"
:key="index" :key="index"
class="swiper-slide" class="swiper-slide"
:class="{ 'swiper-slide-active': index === currentIndex }" :class="{ 'swiper-slide-active': index === virtualCurrentIndex }"
:style="getSlideStyle(index)" :style="getSlideStyle(index)"
> >
<!-- 公告类型 --> <!-- 公告类型 -->
@ -44,7 +44,7 @@
</view> </view>
<!-- 自定义插槽 --> <!-- 自定义插槽 -->
<slot v-else :item="item" :index="index"> <slot v-else :item="item" :index="getOriginalIndex(index)">
<view class="default-item"> <view class="default-item">
<text>{{ item.text || item.content || item }}</text> <text>{{ item.text || item.content || item }}</text>
</view> </view>
@ -55,10 +55,10 @@
<!-- 指示器 --> <!-- 指示器 -->
<view v-if="showIndicators && items.length > 1" class="indicators"> <view v-if="showIndicators && items.length > 1" class="indicators">
<view <view
v-for="(item, index) in items" v-for="(_item, originalItemIndex) in items"
:key="index" :key="originalItemIndex"
class="indicator" class="indicator"
:class="{ active: index === currentIndex }" :class="{ active: originalItemIndex === currentIndex }"
></view> ></view>
</view> </view>
</view> </view>
@ -111,100 +111,145 @@ export default {
}, },
data() { data() {
return { return {
currentIndex: 0, currentIndex: 0, // Index in original items array
virtualCurrentIndex: 0, // Index in displayItems array
timer: null, timer: null,
isTransitioning: false, isTransitioning: false,
// isJumping: false, // Flag for instant jumps, to disable CSS transition
touchStartY: 0, touchStartY: 0,
touchStartTime: 0, touchStartTime: 0,
isTouching: false, isTouching: false,
} }
}, },
computed: { computed: {
itemsLength() { originalItemsLength() {
return this.items.length return this.items.length
}, },
displayItems() {
if (!this.items || this.originalItemsLength === 0) {
return []
}
if (!this.circular) {
return this.items
}
if (this.originalItemsLength === 1) {
return [this.items[0], this.items[0], this.items[0]]
}
const lastItem = this.items[this.originalItemsLength - 1]
const firstItem = this.items[0]
return [lastItem, ...this.items, firstItem]
},
}, },
watch: { watch: {
items: { items: {
handler() { handler() {
this.initSwiper() this.initSwiperState()
}, },
immediate: true, immediate: true,
}, },
autoplay(newVal) { autoplay(newVal) {
if (this.originalItemsLength === 0) return
if (newVal) { if (newVal) {
this.startAutoplay() this.startAutoplay()
} else { } else {
this.clearTimer() this.clearTimer()
} }
}, },
circular() {
this.initSwiperState()
},
virtualCurrentIndex() {
// This watcher can be used for debugging or complex state reactions if needed
},
}, },
mounted() { mounted() {
this.initSwiper() // Initialization is handled by immediate watch on items
}, },
beforeDestroy() { beforeDestroy() {
this.clearTimer() this.clearTimer()
}, },
// uniapp // uniapp
onReady() { onReady() {
this.initSwiper() this.initSwiperState()
}, },
onUnload() { onUnload() {
this.clearTimer() this.clearTimer()
}, },
methods: { methods: {
// - getOriginalIndex(displayIndex) {
getSlideStyle(index) { if (this.originalItemsLength === 0) return 0
if (this.itemsLength <= 1) { if (!this.circular) {
return { return displayIndex
transform: 'translateY(0px)', }
opacity: 1, if (this.originalItemsLength === 1) {
zIndex: 1, return 0
} }
if (displayIndex === 0) return this.originalItemsLength - 1
if (displayIndex === this.displayItems.length - 1) return 0
if (displayIndex > 0 && displayIndex <= this.originalItemsLength) {
return displayIndex - 1
}
return 0 // Fallback, should ideally not be reached with correct logic
},
initSwiperState() {
this.clearTimer()
if (this.originalItemsLength === 0) {
this.currentIndex = 0
this.virtualCurrentIndex = 0
if (this.autoplay) this.startAutoplay() // Try to start autoplay even if no items initially
return
} }
let translateY = 0 if (this.circular) {
if (this.originalItemsLength === 1) {
this.virtualCurrentIndex = 1
this.currentIndex = 0
} else {
this.virtualCurrentIndex = 1
this.currentIndex = 0
}
} else {
this.virtualCurrentIndex = 0
this.currentIndex = 0
}
// Ensure DOM is updated if items change, then start autoplay
this.$nextTick(() => {
if (this.autoplay) {
this.startAutoplay()
}
})
},
getSlideStyle(indexInDisplayItems) {
const offset = indexInDisplayItems - this.virtualCurrentIndex
const translateY = offset * this.height
let opacity = 0.6 let opacity = 0.6
let zIndex = 1 let zIndex = 1
if (index === this.currentIndex) { if (offset === 0) {
// // Active slide
translateY = 0
opacity = 1 opacity = 1
zIndex = 10 zIndex = 10
} else if (Math.abs(offset) === 1) {
// Adjacent slides
opacity = 0.3
zIndex = 5
} else { } else {
// // Far away slides
let offset = index - this.currentIndex opacity = 0
zIndex = 0
//
if (this.circular && this.itemsLength > 2) {
const halfLength = this.itemsLength / 2
//
if (offset > halfLength) {
offset = offset - this.itemsLength //
} else if (offset < -halfLength) {
offset = offset + this.itemsLength //
}
}
translateY = offset * this.height
//
const absOffset = Math.abs(offset)
if (absOffset <= 1) {
opacity = absOffset === 1 ? 0.3 : 0.6
zIndex = absOffset === 1 ? 5 : 1
} else {
opacity = 0
zIndex = 0
}
} }
const transition = const transition =
this.isTransitioning && !this.isTouching (this.isTransitioning || this.isJumping) &&
? `all ${this.duration}ms cubic-bezier(0.25, 0.46, 0.45, 0.94)` !this.isTouching &&
!(this.isJumping && !this.isTransitioning) // Allow transition if only isTransitioning is true
? this.isJumping
? 'none'
: `all ${this.duration}ms cubic-bezier(0.25, 0.46, 0.45, 0.94)`
: 'none' : 'none'
return { return {
@ -214,168 +259,196 @@ export default {
transition: transition, transition: transition,
} }
}, },
//
initSwiper() {
console.log(
'初始化轮播, items数量:',
this.itemsLength,
'autoplay:',
this.autoplay
)
if (this.itemsLength === 0) return
this.currentIndex = 0
if (this.autoplay && this.itemsLength > 1) {
this.startAutoplay()
}
},
//
startAutoplay() { startAutoplay() {
this.clearTimer() this.clearTimer()
if (this.autoplay && this.itemsLength > 1) { // Condition for autoplay: must have items, and if not circular, must have more than 1 item.
console.log('开始自动轮播,间隔:', this.interval) const canAutoplay =
this.originalItemsLength > 0 &&
(this.circular || this.originalItemsLength > 1)
if (this.autoplay && canAutoplay) {
this.timer = setInterval(() => { this.timer = setInterval(() => {
this.next() this.next()
}, this.interval) }, this.interval)
} }
}, },
//
clearTimer() { clearTimer() {
if (this.timer) { if (this.timer) {
clearInterval(this.timer) clearInterval(this.timer)
this.timer = null this.timer = null
} }
}, },
updateCurrentIndex(currentVirtualIdx, isMidJump = false) {
const oldOriginalIndex = this.currentIndex
let newOriginalIndex = 0
// if (this.originalItemsLength === 0) {
this.currentIndex = 0
return
}
if (!this.circular) {
newOriginalIndex = currentVirtualIdx
} else if (this.originalItemsLength === 1) {
newOriginalIndex = 0
} else {
if (currentVirtualIdx === 0) {
newOriginalIndex = this.originalItemsLength - 1
} else if (currentVirtualIdx === this.displayItems.length - 1) {
newOriginalIndex = 0
} else {
newOriginalIndex = currentVirtualIdx - 1
}
}
this.currentIndex = newOriginalIndex
// Emit change only if it's not a mid-jump correction that results in the same original index visually
// Or if the original index actually changed.
if (oldOriginalIndex !== newOriginalIndex || !isMidJump) {
this.$emit('change', {
current: this.currentIndex,
currentItem: this.items[this.currentIndex],
})
}
},
moveTo(targetVirtualIndex, direction) {
// direction can be 'next' or 'prev', used to emit correct original index during transition
if (this.isTransitioning && !this.isJumping) return
if (this.originalItemsLength === 0) return
this.isTransitioning = true
this.virtualCurrentIndex = targetVirtualIndex
// Emit change based on where we are going (even if it's a clone)
this.updateCurrentIndex(targetVirtualIndex)
setTimeout(() => {
this.isTransitioning = false
if (this.circular) {
this.checkAndCorrectLoopBoundary()
}
}, this.duration)
},
checkAndCorrectLoopBoundary() {
if (this.originalItemsLength <= 1 && this.circular) {
// Special handling for 1 item circular
// displayItems = [item, item, item], virtualCurrentIndex can be 0, 1, 2
if (this.virtualCurrentIndex !== 1) {
this.isJumping = true
this.virtualCurrentIndex = 1 // Jump to the middle "real" one
this.updateCurrentIndex(1, true)
this.$nextTick(() => {
this.isJumping = false
})
}
return
}
if (this.originalItemsLength < 2 || !this.circular) return // Only for 2+ items circular
const firstRealItemVirtualIdx = 1
const lastRealItemVirtualIdx = this.originalItemsLength
let jumpToVirtualIndex = -1
if (this.virtualCurrentIndex === 0) {
jumpToVirtualIndex = lastRealItemVirtualIdx
} else if (this.virtualCurrentIndex === this.displayItems.length - 1) {
jumpToVirtualIndex = firstRealItemVirtualIdx
}
if (jumpToVirtualIndex !== -1) {
this.isJumping = true
this.virtualCurrentIndex = jumpToVirtualIndex
this.updateCurrentIndex(jumpToVirtualIndex, true) // Update original index reflecting the jump destination
this.$nextTick(() => {
this.isJumping = false
})
}
},
next() { next() {
if (this.isTransitioning || this.itemsLength <= 1) return if (this.originalItemsLength === 0) return
if (
!this.circular &&
this.virtualCurrentIndex >= this.originalItemsLength - 1
)
return
console.log('切换到下一页,当前:', this.currentIndex) let targetVirtualIndex = this.virtualCurrentIndex + 1
this.moveTo(targetVirtualIndex, 'next')
this.isTransitioning = true
const nextIndex = this.circular
? (this.currentIndex + 1) % this.itemsLength
: Math.min(this.currentIndex + 1, this.itemsLength - 1)
this.currentIndex = nextIndex
setTimeout(() => {
this.isTransitioning = false
this.$emit('change', {
current: this.currentIndex,
currentItem: this.items[this.currentIndex],
})
}, this.duration)
}, },
//
prev() { prev() {
if (this.isTransitioning || this.itemsLength <= 1) return if (this.originalItemsLength === 0) return
if (!this.circular && this.virtualCurrentIndex <= 0) return
this.isTransitioning = true let targetVirtualIndex = this.virtualCurrentIndex - 1
this.moveTo(targetVirtualIndex, 'prev')
const prevIndex = this.circular
? (this.currentIndex - 1 + this.itemsLength) % this.itemsLength
: Math.max(this.currentIndex - 1, 0)
this.currentIndex = prevIndex
setTimeout(() => {
this.isTransitioning = false
this.$emit('change', {
current: this.currentIndex,
currentItem: this.items[this.currentIndex],
})
}, this.duration)
}, },
goToSlide(originalIndex) {
if (originalIndex < 0 || originalIndex >= this.originalItemsLength) return
// if (originalIndex === this.currentIndex && !this.isTransitioning && !this.isJumping) return;
// let targetVirtualIndex
goToSlide(index) { if (!this.circular) {
if (index === this.currentIndex || this.isTransitioning) return targetVirtualIndex = originalIndex
if (index < 0 || index >= this.itemsLength) return } else {
if (this.originalItemsLength === 1) targetVirtualIndex = 1
this.isTransitioning = true else targetVirtualIndex = originalIndex + 1
this.currentIndex = index }
this.clearTimer()
setTimeout(() => { this.moveTo(targetVirtualIndex)
this.isTransitioning = false if (this.autoplay) {
this.$emit('change', { setTimeout(() => this.startAutoplay(), this.duration + 100)
current: this.currentIndex, }
currentItem: this.items[this.currentIndex],
})
}, this.duration)
}, },
//
onTouchStart(e) { onTouchStart(e) {
if (this.itemsLength <= 1) return if (this.originalItemsLength === 0) return
if (!this.circular && this.originalItemsLength <= 1) return // No swipe for single non-circular item
console.log('触摸开始') this.clearTimer()
this.clearTimer() //
this.touchStartY = e.touches[0].clientY this.touchStartY = e.touches[0].clientY
this.touchStartTime = Date.now() this.touchStartTime = Date.now()
this.isTouching = true this.isTouching = true
this.isJumping = false
}, },
//
onTouchMove(e) { onTouchMove(e) {
if (this.isTransitioning || !this.isTouching || this.itemsLength <= 1) if (!this.isTouching) return
return
const deltaY = e.touches[0].clientY - this.touchStartY
console.log('触摸移动, deltaY:', deltaY)
//
//
}, },
//
onTouchEnd(e) { onTouchEnd(e) {
if (!this.isTouching || this.itemsLength <= 1) return if (!this.isTouching || this.originalItemsLength === 0) {
this.isTouching = false // Ensure isTouching is reset
return
}
this.isTouching = false this.isTouching = false
const currentY = e.changedTouches[0].clientY const currentY = e.changedTouches[0].clientY
const deltaY = currentY - this.touchStartY const deltaY = currentY - this.touchStartY
const deltaTime = Date.now() - this.touchStartTime const deltaTime = Date.now() - this.touchStartTime
const velocity = Math.abs(deltaY) / deltaTime
console.log('触摸结束, deltaY:', deltaY, 'velocity:', velocity) const threshold = this.height * 0.2
//
const threshold = this.height * 0.25 //
const velocityThreshold = 0.3 const velocityThreshold = 0.3
const shouldSwitch = const velocity = deltaTime > 0 ? Math.abs(deltaY) / deltaTime : 0
Math.abs(deltaY) > threshold || velocity > velocityThreshold
if (shouldSwitch) { if (Math.abs(deltaY) > threshold || velocity > velocityThreshold) {
if (deltaY > 0) { if (deltaY < 0) {
//
console.log('手势触发:上一页')
this.prev()
} else {
//
console.log('手势触发:下一页')
this.next() this.next()
} else if (deltaY > 0) {
this.prev()
} }
} else { } else {
console.log('手势未达到切换阈值,不切换') // No swipe, or too small. Potentially restart autoplay if it was interrupted.
}
//
setTimeout(() => {
if (this.autoplay) { if (this.autoplay) {
this.startAutoplay() this.startAutoplay()
} }
}, 1000) }
// Autoplay restart is handled within next/prev through moveTo or if no swipe occurs
// If a swipe occurs, next/prev call moveTo, which stops autoplay via clearTimer. Then this onTouchEnd may restart.
// If no swipe, this onTouchEnd directly restarts.
// Consider a more unified autoplay restart logic after touch interaction concludes.
// For now, let's consolidate: if autoplay was on, restart it after a delay.
if (this.autoplay) {
setTimeout(() => {
if (!this.timer) this.startAutoplay() // Restart only if not already restarted by a quick succession
}, this.duration + 200) // Delay after potential transition
}
}, },
}, },
} }
@ -396,7 +469,7 @@ export default {
width: 100%; width: 100%;
height: 100%; height: 100%;
// uniapp App // uniapp App
touch-action: none; touch-action: pan-y; /* More specific touch action */
// //
-webkit-touch-callout: none; -webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;

View File

@ -20,3 +20,7 @@ export const pickLogList = params =>
export const getBanners = params => export const getBanners = params =>
http.get('/system/api/banner/list', { params }) http.get('/system/api/banner/list', { params })
// 首页商品列表
export const getIndexGoodsList = params =>
http.get('/sale/api/order/index-wares-list', { params })

View File

@ -30,7 +30,7 @@
<simple-vertical-swiper <simple-vertical-swiper
:items="noticeList" :items="noticeList"
type="custom" type="custom"
:height="40" :height="80"
:autoplay="true" :autoplay="true"
:interval="3000" :interval="3000"
:show-indicators="false" :show-indicators="false"
@ -46,80 +46,104 @@
</template> </template>
</simple-vertical-swiper> </simple-vertical-swiper>
</view> </view>
<view class="goods-sort"> <view class="recommend-goods-list">
<view class="goods-flexs"> <view
<view v-for="(item, index) in recommendSpecialAreaList" :key="index"> v-if="isEmpty(recommendGoodsList) == false"
<view class="recommend-section"
class="goods-view" >
@click="navTap(item)" <view class="recommend-header">
v-if="item.waresList && (index < 8 || moreFlag == true)" <view class="recommend-title">
> <!-- <text class="title-icon"></text> -->
<area-product-list <text class="title-text">精选推荐</text>
:list="item.waresList"
:title="item.specialAreaName"
size="small"
/>
</view> </view>
</view> </view>
</view> <view class="recommend-swiper-container">
<view v-if="isEmpty(goodsList.recommendSpecialAreaList) == false"> <simple-vertical-swiper
<view :items="recommendGoodsList"
class="more" type="custom"
@click="more" :height="450"
v-if="!moreFlag && goodsList.recommendSpecialAreaList.length > 6" autoplay
> :interval="4000"
{{ '查看更多' }} :show-indicators="false"
>
<template #default="{ item }">
<view @click="goDetails(item)" class="recommend-item">
<view class="recommend-item-image">
<image :src="item.cover" mode="aspectFill"></image>
<view class="recommend-badge">推荐</view>
</view>
<view class="recommend-item-content">
<view class="recommend-item-name">{{
item.waresName
}}</view>
<view class="recommend-item-price">
<text class="price-symbol" v-if="priceSymbolVisible">{{
priceSymbol
}}</text>
<text class="price-value">{{
formatCurrency(item.waresPrice)
}}</text>
</view>
</view>
<view class="recommend-arrow"></view>
</view>
</template>
</simple-vertical-swiper>
</view> </view>
</view> </view>
<view class="goods_content"> </view>
<view <!-- New Product List Section Wrapper -->
class="goods-center-lists" <view class="hot-sellers-card-wrapper">
v-for="item in goodsList.waresVoList" <view
:key="item.waresId" class="main-product-list-section"
@click="goDetails(item)" v-if="displayedGoodsList.length > 0"
> >
<!-- <view class="section-title">热销甄选</view> -->
<view class="product-grid">
<view <view
class="fly" class="product-card"
v-show="item.preSaleStatus == 3 || item.isSale == 1" v-for="item in displayedGoodsList"
></view> :key="item.waresId"
<view class="goods-flex-s"> @click="goDetails(item)"
<view class="goods-img"> >
<image :src="item.cover1"></image> <view
</view> class="fly"
<view class="padding_s goods-info"> v-show="item.preSaleStatus == 3 || item.isSale == 1"
<view class="goods-name">{{ item.waresName }}</view> ></view>
<view class="goods-sales-wrapper"> <image
<!-- <view class="goods-sales">累计销量{{ formatSales(item.sales) }}</view> --> class="product-image"
<view :src="item.cover"
class="goods-price" mode="aspectFill"
v-if="item.specialArea == 31 && userInfo.isMakerSpace == 1" ></image>
> <view class="product-info">
<text class="product-name">{{ item.waresName }}</text>
<text class="product-tagline"></text>
<view class="product-price-row">
<view class="product-price">
<span v-if="priceSymbolVisible" class="price-symbol"> <span v-if="priceSymbolVisible" class="price-symbol">
{{ priceSymbol }} {{ priceSymbol }}
</span> </span>
<span>{{ formatCurrency(item.vipPrice) }}</span> <span
</view> v-if="
<view class="goods-price" v-if="item.specialArea != 31"> item.specialArea == 31 && userInfo.isMakerSpace == 1
<span v-if="priceSymbolVisible" class="price-symbol"> "
{{ priceSymbol }} >{{ formatCurrency(item.vipPrice) }}</span
</span> >
<span>{{ formatCurrency(item.waresPrice) }}</span> <span v-else>{{ formatCurrency(item.waresPrice) }}</span>
</view>
<view
class="goods-price"
v-if="item.specialArea == 31 && userInfo.isMakerSpace == 0"
>
<span v-if="priceSymbolVisible" class="price-symbol">
{{ priceSymbol }}
</span>
<span>{{ formatCurrency(item.waresPrice) }}</span>
</view> </view>
<view class="buy-now-button">立即购买</view>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
<view class="load-more-container" v-if="hasMoreGoods">
<button @click="loadMoreGoods" class="load-more-button">
加载更多
</button>
</view>
</view> </view>
</view> </view>
<!-- End of New Product List Section Wrapper -->
<cl-tabbar :current="0"></cl-tabbar> <cl-tabbar :current="0"></cl-tabbar>
<div> <div>
<!-- 公告弹窗 --> <!-- 公告弹窗 -->
@ -204,10 +228,15 @@ export default {
interval: 5000, interval: 5000,
autoplay: true, autoplay: true,
duration: 500, duration: 500,
goodsList: [],
recommendSpecialAreaList: [], recommendSpecialAreaList: [],
banners: [], banners: [],
noticeList: [], noticeList: [],
recommendGoodsList: [],
goodsList: [],
displayedGoodsList: [],
goodsListPage: 1,
goodsListPageSize: 4,
hasMoreGoods: true,
zoneList: [ zoneList: [
{ {
label: '注册专区', label: '注册专区',
@ -403,10 +432,11 @@ export default {
uni.getStorageSync('showInfo') == 0 uni.getStorageSync('showInfo') == 0
) { ) {
} }
this.getGoodsInfo() // this.getAreaGoods()
this.getAreaGoods()
this.getBanners() this.getBanners()
this.getNoticeList() this.getNoticeList()
this.getRecommendGoodsList()
this.getGoodsList()
// this.getLanguage(); // this.getLanguage();
this.getService() this.getService()
}, },
@ -416,7 +446,6 @@ export default {
onPullDownRefresh() { onPullDownRefresh() {
let that = this let that = this
setTimeout(() => { setTimeout(() => {
that.getGoodsInfo()
uni.stopPullDownRefresh() // uni.stopPullDownRefresh() //
}, 1000) }, 1000)
}, },
@ -440,10 +469,8 @@ export default {
}) })
}, },
goNotice(item) { goNotice(item) {
console.log(item, '....item')
return
uni.navigateTo({ uni.navigateTo({
url: '/pages/notice/index?id=' + item.id, url: '/pages/email/noticeDetail?index=1&pkId=' + item.pkId,
}) })
}, },
goAreaUrl() { goAreaUrl() {
@ -470,11 +497,44 @@ export default {
} }
}) })
}, },
getAreaGoods() { getRecommendGoodsList() {
getAreaGoods().then(res => { indexApi.getIndexGoodsList({ isRecommend: 0 }).then(res => {
this.recommendSpecialAreaList = res.data?.recommendSpecialAreaList || [] if (res.code == 200) {
this.recommendGoodsList = res.rows || []
}
}) })
}, },
getGoodsList() {
indexApi.getIndexGoodsList().then(res => {
if (res.code == 200) {
this.goodsList = res.rows || []
this.goodsListPage = 1
this.displayedGoodsList = []
this.loadMoreGoods()
}
})
},
loadMoreGoods() {
if (!this.goodsList || this.goodsList.length === 0) {
this.hasMoreGoods = false
return
}
const start = (this.goodsListPage - 1) * this.goodsListPageSize
const end = start + this.goodsListPageSize
const newGoods = this.goodsList.slice(start, end)
if (newGoods.length > 0) {
this.displayedGoodsList = [...this.displayedGoodsList, ...newGoods]
this.goodsListPage++
}
if (this.displayedGoodsList.length >= this.goodsList.length) {
this.hasMoreGoods = false
} else {
this.hasMoreGoods = true
}
},
toDel() { toDel() {
this.promptFlag = false this.promptFlag = false
if (this.jumpPage == 1) { if (this.jumpPage == 1) {
@ -555,11 +615,6 @@ export default {
window.location.href = urls window.location.href = urls
} }
}, },
getGoodsInfo() {
indexApi.userIndex().then(res => {
this.goodsList = res.data
})
},
goUrl(item) { goUrl(item) {
ban.agreementName().then(res => { ban.agreementName().then(res => {
@ -671,6 +726,8 @@ export default {
.content_a { .content_a {
overflow: auto; overflow: auto;
height: 100%; height: 100%;
padding-bottom: 120rpx;
box-sizing: border-box;
} }
} }
.content1 { .content1 {
@ -680,6 +737,8 @@ export default {
.content_a { .content_a {
overflow: auto; overflow: auto;
height: 100%; height: 100%;
padding-bottom: 120rpx;
box-sizing: border-box;
} }
} }
.more { .more {
@ -1204,4 +1263,367 @@ page {
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
.recommend-header {
padding: 0rpx;
.recommend-title {
display: flex;
align-items: center;
margin-bottom: 8rpx;
.title-icon {
font-size: 28rpx;
// margin-right: 12rpx;
color: #ff6b6b;
}
.title-text {
color: #333;
font-size: 28rpx;
font-weight: bold;
}
}
.recommend-subtitle {
color: #999;
font-size: 24rpx;
margin-left: 40rpx;
}
}
.recommend-swiper-container {
background: #fff;
}
.recommend-item {
display: block;
padding: 8px 0;
background-color: #fff;
border-bottom: 1px solid #f0f0f0;
transition: background-color 0.2s ease;
width: 100%;
}
.recommend-item:last-child {
border-bottom: none;
}
.recommend-item:active {
background-color: #f8f9fa;
}
.recommend-item-image {
width: 100%;
height: 160px;
object-fit: cover;
border-radius: 6px;
overflow: hidden;
margin-right: 0;
margin-bottom: 10px;
position: relative;
background-color: #f0f0f0;
}
.recommend-item-image image {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
.recommend-badge {
position: absolute;
top: 0px;
right: 0px;
background-color: #de3932;
color: #ffffff;
font-size: 10px;
padding: 2px 6px;
border-radius: 6px;
z-index: 1;
}
.recommend-item-content {
padding: 0 4px;
}
.recommend-item-name {
color: #333333;
font-size: 14px;
font-weight: 500;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.recommend-item-price {
display: flex;
align-items: baseline;
}
.recommend-item-price .price-symbol {
color: #de3932;
font-size: 12px;
margin-right: 2px;
}
.recommend-item-price .price-value {
color: #de3932;
font-size: 16px;
font-weight: bold;
}
.recommend-arrow {
display: none;
}
// Styles for New Product List
.main-product-list-section {
margin: 20rpx;
padding-bottom: 20rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
padding: 18rpx 24rpx;
text-align: left;
// background: #f8f9fa;
border-bottom: 1rpx solid #e9ecef;
}
.product-grid {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.product-card {
background: #fff;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
display: flex;
flex-direction: column;
position: relative;
.product-image {
display: block;
width: 100%;
height: 200px;
object-fit: cover;
background-color: #f0f0f0;
}
.product-info {
padding: 12px;
display: flex;
flex-direction: column;
flex-grow: 1;
background-color: #f7f8fa;
}
.product-name {
font-size: 15px;
color: #2d3748;
font-weight: 600;
line-height: 1.4;
margin-bottom: 6px;
min-height: calc(15px * 1.4 * 2);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.product-tagline {
font-size: 22rpx;
color: #999;
margin-bottom: 8rpx;
line-height: 1.3;
}
.product-price-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: auto;
}
.product-price {
color: #de3932;
font-size: 32rpx;
font-weight: bold;
.price-symbol {
font-size: 22rpx;
margin-right: 2rpx;
}
}
.buy-now-button {
background-color: transparent;
color: #005bac;
border: 1px solid #005bac;
padding: 7px 12px;
border-radius: 20px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
text-align: center;
transition:
background-color 0.2s,
color 0.2s;
}
.buy-now-button:active {
background-color: #e6f0fa;
color: #005bac;
}
}
.load-more-container {
margin-top: 30rpx;
text-align: center;
.load-more-button {
background-color: #fff;
color: #333;
border: 1px solid #eee;
padding: 16rpx 40rpx;
font-size: 28rpx;
border-radius: 40rpx;
display: inline-block;
transition: background-color 0.2s;
&:active {
background-color: #f0f0f0;
}
}
}
.hot-sellers-card-wrapper {
margin-top: 20rpx;
background: #fff;
// border-radius: 16rpx;
overflow: hidden;
// box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
}
/* --- 精选推荐模块样式 --- */
/* 模块容器 */
.recommend-section {
background-color: #ffffff;
padding: 20rpx;
}
/* 模块标题 */
.recommend-title {
display: flex;
align-items: center;
margin-bottom: 0;
}
.recommend-title .title-icon {
font-size: 16px;
color: #005bac;
margin-right: 6px;
}
.recommend-title .title-text {
color: #222222;
font-size: 18px;
font-weight: 600;
}
/* 副标题 (如果存在且需要调整) */
.recommend-subtitle {
color: #777777;
font-size: 13px;
margin-left: 0;
padding-left: 22px;
}
/* 单个推荐商品项 */
.recommend-item {
display: block;
padding: 8px 0;
background-color: #fff;
border-bottom: 1px solid #f0f0f0;
transition: background-color 0.2s ease;
}
.recommend-item:last-child {
border-bottom: none;
}
.recommend-item:active {
background-color: #f8f9fa;
}
.recommend-item-image {
width: 100%;
height: 160px;
object-fit: cover;
border-radius: 6px;
overflow: hidden;
margin-right: 0;
margin-bottom: 10px;
position: relative;
background-color: #f0f0f0;
}
.recommend-item-image image {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
.recommend-badge {
position: absolute;
top: 0px;
right: 0px;
background-color: #de3932;
color: #ffffff;
font-size: 10px;
padding: 2px 6px;
border-radius: 6px;
z-index: 1;
}
.recommend-item-content {
padding: 0 4px;
}
.recommend-item-name {
color: #333333;
font-size: 14px;
font-weight: 500;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.recommend-item-price {
display: flex;
align-items: baseline;
}
.recommend-item-price .price-symbol {
color: #de3932;
font-size: 12px;
margin-right: 2px;
}
.recommend-item-price .price-value {
color: #de3932;
font-size: 16px;
font-weight: bold;
}
.recommend-arrow {
display: none;
}
/* 如果 simple-vertical-swiper 的高度需要适配内容 */
.recommend-swiper-container {
/* simple-vertical-swiper :height="140" (rpx/px?)
那么内部 recommend-item 的数量和padding需要仔细计算以避免显示问题
这里假设 simple-vertical-swiper 内部可以正确处理其子项的布局
如果swiper高度导致内容显示不全可能需要调整swiper的height参数
或者调整recommend-item的padding/margin
*/
}
</style> </style>

View File

@ -1,18 +1,56 @@
<template> <template>
<view> <view>
<view> 123321 </view> <AreaWrapper v-if="!loading" :areaSpecialAreaList="areaSpecialAreaList" />
<cl-tabbar :current="1"></cl-tabbar> <cl-tabbar :current="1"></cl-tabbar>
</view> </view>
</template> </template>
<script> <script>
import clTabbar from '@/components/cl-tabbar.vue' import clTabbar from '@/components/cl-tabbar.vue'
import AreaWrapper from '@/components/area-wrapper/index.vue'
import { REPEAT_PURCHASE, RESCISSION } from '@/util/specialAreaMap'
import { mapActions } from 'vuex'
export default { export default {
name: 'Mall',
components: { components: {
'cl-tabbar': clTabbar, 'cl-tabbar': clTabbar,
AreaWrapper,
},
computed: {},
data() {
return {
areaSpecialAreaList: [],
visible: false,
loading: true,
}
},
onLoad(options) {
this.init()
},
onShow() {},
onHide() {},
beforeDestroy() {},
methods: {
...mapActions(['getSpecialAreaAuth']),
init() {
uni.showLoading({
title: '加载中...',
})
this.getSpecialAreaAuth()
.then(specialAreaList => {
if (specialAreaList.includes(REPEAT_PURCHASE.menuKey)) {
this.areaSpecialAreaList.push(REPEAT_PURCHASE)
}
if (specialAreaList.includes(RESCISSION.menuKey)) {
this.areaSpecialAreaList.push(RESCISSION)
}
})
.finally(() => {
this.loading = false
this.$nextTick(() => {
uni.hideLoading()
})
})
},
}, },
} }
</script> </script>
<style></style>

View File

@ -11,6 +11,20 @@ export const UPGRADE = {
specialArea: 2, specialArea: 2,
menuKey: 'upgrade', menuKey: 'upgrade',
} }
// 复购专区
export const REPEAT_PURCHASE = {
label: '复购专区',
specialArea: 3,
menuKey: 'repurchase',
}
// 重消专区
export const RESCISSION = {
label: '重消专区',
specialArea: 10,
menuKey: 'rescission',
}
export const specialAreaMap = { export const specialAreaMap = {
regiest: REGIEST, regiest: REGIEST,
upgrade: UPGRADE, upgrade: UPGRADE,