feat(inde): 首页改版
This commit is contained in:
parent
5c52892f34
commit
2f1a31d349
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 })
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue