Browse Source

‘优化质检流程’

master
Lj 3 weeks ago
parent
commit
d6a731f604
9 changed files with 1325 additions and 367 deletions
  1. +38
    -13
      pages/component/home.vue
  2. +225
    -38
      pages/component/recycle.vue
  3. +788
    -278
      pages/manager/inspect.vue
  4. +63
    -7
      pages/manager/order-detail.vue
  5. +39
    -8
      pages/subcomponent/detail.vue
  6. +11
    -4
      pages/subcomponent/pickup.vue
  7. +1
    -0
      pages/subcomponent/promotion.vue
  8. +2
    -2
      pages/subcomponent/select.vue
  9. +158
    -17
      pages/subcomponent/wallet.vue

+ 38
- 13
pages/component/home.vue View File

@ -38,12 +38,12 @@
@pause="onVideoPause(index)"
@ended="onVideoEnded(index)"
></video>
<!-- 播放按钮覆盖层 -->
<view v-if="!videoPlayingStates[index]" class="video-overlay" @click="playVideoFullscreen(item, index)">
<!-- <view class="play-button-large">
<uni-icons type="play-filled" size="50" color="#fff"></uni-icons>
</view> -->
</view>
<!-- 播放按钮覆盖层 -->
<view v-if="!videoPlayingStates[index]" class="video-overlay" @click="playVideoFullscreen(item, index)">
<view class="play-button-large">
<view class="play-triangle"></view>
</view>
</view>
</view>
<image v-else :src="item.image" mode="aspectFill" style="width: 100%; height: 100%;" @click="showServiceQrcode" />
</swiper-item>
@ -125,7 +125,10 @@
<image v-if="item.icon" :src="item.icon" mode="aspectFit" class="item-icon"></image>
<view v-else class="item-icon placeholder"></view>
<text class="item-name">{{item.name}}</text>
<text class="item-price">¥ {{item.price}}
<text class="item-price" v-if="!item.maxPrice || item.maxPrice == item.price">¥ {{item.price}}
<text class="item-price-right">/{{item.unit}}</text>
</text>
<text class="item-price" v-else>¥ {{item.price}}-{{item.maxPrice}}
<text class="item-price-right">/{{item.unit}}</text>
</text>
</view>
@ -383,6 +386,7 @@
icon: item.icon ? item.icon : '',
name: item.title,
price: item.priceNo ? item.priceNo : '',
maxPrice: item.maxPriceNo ? item.maxPriceNo : '',
unit: item.unit ? item.unit : ''
}
})
@ -688,7 +692,7 @@
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
background: rgba(0, 0, 0, 0.4);
z-index: 10;
cursor: pointer;
display: flex;
@ -696,20 +700,41 @@
align-items: center;
.play-button-large {
width: 100rpx;
height: 100rpx;
background: rgba(0, 0, 0, 0.7);
width: 120rpx;
height: 120rpx;
background: transparent;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
backdrop-filter: blur(10rpx);
transition: all 0.3s ease;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.3);
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.3);
// border: 3rpx solid rgba(255, 255, 255, 0.9);
&:active {
transform: scale(0.9);
background: rgba(0, 0, 0, 0.8);
background: rgba(255, 255, 255, 0.1);
}
.play-triangle {
width: 0;
height: 0;
border-left: 24rpx solid #fff;
border-top: 18rpx solid transparent;
border-bottom: 18rpx solid transparent;
margin-left: 8rpx;
transition: all 0.3s ease;
filter: drop-shadow(0 2rpx 4rpx rgba(0, 0, 0, 0.3));
}
}
&:hover .play-button-large {
transform: scale(1.1);
box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.4);
.play-triangle {
border-left-color: #ff8917;
}
}
}


+ 225
- 38
pages/component/recycle.vue View File

@ -2,32 +2,61 @@
<view class="container">
<!-- 顶部banner -->
<view class="banner">
<!-- <swiper
<swiper
:indicator-dots="false"
:autoplay="true"
:interval="3000"
:duration="500"
circular
style="width: 100%; height: 320rpx;"
> -->
<!-- <swiper-item v-for="(item, index) in bannerList" :key="item.id || index">
<video
v-if="item.type == 1"
:src="item.voUrl"
autoplay
muted
loop
:controls="false"
:show-play-btn="false"
:show-center-play-btn="false"
object-fit="cover"
style="width: 100%; height: 100%;"
></video>
>
<swiper-item v-for="(item, index) in bannerList" :key="item.id || index">
<view v-if="item.type == 1" class="video-container">
<!-- 预览状态显示封面图 -->
<image
v-if="!videoPlayingStates[index]"
:src="item.image || ''"
mode="aspectFill"
style="width: 100%; height: 100%;"
class="video-poster"
@click="playVideoFullscreen(item, index)"
/>
<!-- 播放状态显示视频 -->
<video
v-else
:id="`recycle-video-${index}`"
:src="item.voUrl"
:autoplay="true"
:muted="false"
:loop="false"
:controls="true"
:show-play-btn="true"
:show-center-play-btn="false"
:show-fullscreen-btn="true"
:show-progress="true"
:show-mute-btn="true"
:enable-progress-gesture="true"
:enable-play-gesture="true"
object-fit="cover"
style="width: 100%; height: 100%;"
@fullscreenchange="onFullscreenChange"
@play="onVideoPlay(index)"
@pause="onVideoPause(index)"
@ended="onVideoEnded(index)"
></video>
<!-- 播放按钮覆盖层 -->
<view v-if="!videoPlayingStates[index]" class="video-overlay" @click="playVideoFullscreen(item, index)">
<view class="play-button-large">
<view class="play-triangle"></view>
</view>
</view>
</view>
<image v-else :src="item.image" mode="aspectFill" style="width: 100%; height: 100%;" />
</swiper-item>
</swiper> -->
</swiper>
<image v-if="recycle_banner" :src="recycle_banner" mode="aspectFill" style="width: 100%; height: 100%;" />
<!-- 备用静态图片当没有轮播图数据时显示 -->
<image v-if="!bannerList || bannerList.length === 0" :src="recycle_banner" mode="aspectFill" style="width: 100%; height: 100%;" />
</view>
@ -70,11 +99,12 @@
</view>
</view>
<view class="goods-info">
<view class="price-info">
<text class="price-symbol">¥</text>
<text class="price-value">{{item.price}}</text>
<text class="price-unit">/{{ item.unit || '件' }}</text>
</view>
<view class="price-info">
<text class="price-symbol">¥</text>
<text class="price-value" v-if="!item.maxPrice || item.maxPrice == item.price">{{item.price}}</text>
<text class="price-value" v-else>{{item.price}}-{{item.maxPrice}}</text>
<text class="price-unit">/{{ item.unit || '件' }}</text>
</view>
<view class="quantity-control">
<button class="btn-minus" @click="updateQuantity(index, -1)">-</button>
<text class="quantity">{{getItemTotalQuantity(item)}}</text>
@ -116,7 +146,8 @@
</view>
<view class="amount-row">
<uni-icons :type="showDetailPanel ? 'up' : 'down'" size="18" color="#5e5e5e" style="margin-right: 8rpx;vertical-align: middle;" @click="toggleDetailPanel" />
<text class="amount">¥{{priceRange.min}}-{{priceRange.max}}</text>
<text class="amount" v-if="priceRange.min === priceRange.max">¥{{priceRange.min}}</text>
<text class="amount" v-else>¥{{priceRange.min}}-{{priceRange.max}}</text>
</view>
</view>
<button class="submit-btn" @click="submitOrder">预约上门取件</button>
@ -142,7 +173,8 @@
<text class="panel-item-name">{{item.name}}</text>
<text class="panel-item-desc" v-if="item.brandName">品牌{{item.brandName}}</text>
<text class="panel-item-desc" v-else>{{item.service}}</text>
<text class="panel-item-price">¥{{item.price}}/{{ item.unit || '件' }}</text>
<text class="panel-item-price" v-if="!item.maxPrice || item.maxPrice == item.price">¥{{item.price}}/{{ item.unit || '' }}</text>
<text class="panel-item-price" v-else>¥{{item.price}}-{{item.maxPrice}}/{{ item.unit || '' }}</text>
</view>
<view class="panel-quantity-control">
<button class="btn-minus" @click="updateQuantityByProduct(item, -1)">-</button>
@ -159,7 +191,8 @@
</view>
<view class="amount-row">
<uni-icons :type="showDetailPanel ? 'up' : 'down'" size="18" color="#5e5e5e" style="margin-right: 8rpx;vertical-align: middle;" @click="toggleDetailPanel" />
<text class="amount">¥{{priceRange.min}}-{{priceRange.max}}</text>
<text class="amount" v-if="priceRange.min === priceRange.max">¥{{priceRange.min}}</text>
<text class="amount" v-else>¥{{priceRange.min}}-{{priceRange.max}}</text>
</view>
</view>
<button class="submit-btn" @click="submitOrder">预约上门取件</button>
@ -330,6 +363,7 @@ export default {
pageSize: 10,
currentCategory: 0,
tabbarHeight: 0,
videoPlayingStates: {}, //
showDetailPanel: false,
showBrandPopup: false,
showRulePopup: false,
@ -341,6 +375,7 @@ export default {
name: ''
},
brandList: [],
brandCache: {}, // { productId: [brandList] }
brandIndexList: ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'],
currentLetter: 'A',
scrollToView: '',
@ -677,6 +712,14 @@ export default {
},
//
getBrandInfo(brandId) {
//
for (const productId in this.brandCache) {
const brandInfo = this.brandCache[productId].find(brand => brand.id === brandId)
if (brandInfo) {
return brandInfo
}
}
// brandList
return this.brandList.find(brand => brand.id === brandId)
},
//
@ -763,7 +806,7 @@ export default {
icon: item.image,
quantity: item.quantity,
unitPrice: item.price,
maxPrice : item.maxPrice,
maxPrice: item.maxPrice,
desc: item.brandName ? `品牌:${item.brandName}` : '允许脏破烂,160码以上'
}
@ -824,6 +867,9 @@ export default {
//
this.viewedRuleItems.clear()
//
this.brandCache = {}
//
await new Promise(resolve => setTimeout(resolve, 1000))
@ -1017,7 +1063,7 @@ export default {
this.$api('getGoodsBrandList', params, res => {
// console.log(res,'res')
if (res && res.success && res.result && res.result.records) {
this.brandList = res.result.records.map(item => {
const brandList = res.result.records.map(item => {
//
const firstChar = this.getPinyinFirstLetter(item.name)
return {
@ -1027,6 +1073,13 @@ export default {
letter: firstChar
}
})
//
this.$set(this.brandCache, productId, brandList)
// brandList
this.brandList = brandList
// console.log(this.brandList,'this.brandList')
}
})
@ -1068,6 +1121,54 @@ export default {
}, 500)
},
//
playVideoFullscreen(item, index) {
if (!this.videoPlayingStates[index]) {
//
this.$set(this.videoPlayingStates, index, true);
//
this.$nextTick(() => {
setTimeout(() => {
const videoContext = uni.createVideoContext(`recycle-video-${index}`, this);
if (videoContext) {
//
videoContext.requestFullScreen({
direction: -1 //
});
}
}, 200);
});
}
},
onVideoPlay(index) {
this.$set(this.videoPlayingStates, index, true);
},
onVideoPause(index) {
this.$set(this.videoPlayingStates, index, false);
},
onVideoEnded(index) {
//
this.$set(this.videoPlayingStates, index, false);
},
onFullscreenChange(e) {
console.log('全屏状态改变:', e.detail);
const videoIndex = e.target.id.replace('recycle-video-', '');
if (e.detail.fullScreen) {
//
console.log('进入全屏模式,方向:', e.detail.direction);
} else {
// 退
console.log('退出全屏模式,回到预览状态');
this.$set(this.videoPlayingStates, videoIndex, false);
}
},
//
initializePageData() {
const options = this.loadOptions || {}
@ -1102,6 +1203,7 @@ export default {
//
this.viewedRuleItems.clear()
this.showDetailPanel = false
this.brandCache = {} //
this.$forceUpdate()
getApp().globalData.shouldClearRecycle = false
}
@ -1197,6 +1299,7 @@ export default {
//
this.viewedRuleItems.clear()
this.showDetailPanel = false
this.brandCache = {} //
this.$forceUpdate()
getApp().globalData.shouldClearRecycle = false
}
@ -1213,6 +1316,8 @@ export default {
})
//
this.viewedRuleItems.clear()
//
this.brandCache = {}
//
this.showDetailPanel = false
this.$forceUpdate()
@ -1256,13 +1361,80 @@ export default {
width: 100%;
height: 90%;
}
.video-container {
position: relative;
width: 100%;
height: 100%;
.video-poster {
cursor: pointer;
transition: transform 0.2s ease;
&:active {
transform: scale(0.98);
}
}
.video-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 10;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
.play-button-large {
width: 120rpx;
height: 120rpx;
background: transparent;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
backdrop-filter: blur(10rpx);
transition: all 0.3s ease;
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.3);
&:active {
transform: scale(0.9);
background: rgba(255, 255, 255, 0.1);
}
.play-triangle {
width: 0;
height: 0;
border-left: 24rpx solid #fff;
border-top: 18rpx solid transparent;
border-bottom: 18rpx solid transparent;
margin-left: 8rpx;
transition: all 0.3s ease;
filter: drop-shadow(0 2rpx 4rpx rgba(0, 0, 0, 0.3));
}
}
&:hover .play-button-large {
transform: scale(1.1);
box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.4);
.play-triangle {
border-left-color: #ff8917;
}
}
}
}
}
.goods-list {
// flex: 1;
display: flex;
position: relative;
height: calc(100vh - 320rpx - 120rpx - env(safe-area-inset-bottom)); /* 减去banner和底部栏的高度 */
height: calc(110vh - 320rpx - 160rpx - env(safe-area-inset-bottom)); /* 减去banner、底部栏和绿色提示条的高度 */
margin-top: -10rpx;
z-index: 2;
border-radius: 20rpx 20rpx 0 0;
@ -1336,13 +1508,14 @@ export default {
.goods-content {
flex: 1;
height: 100%;
padding: 0 30rpx;
padding: 0 30rpx 180rpx 30rpx; /* 添加底部padding,为固定底部栏预留空间 */
background: #ffffff;
width: 70%;
margin: 1rpx;
margin-left: 0;
border-radius: 0 20rpx 0 0;
overflow-y: auto;
box-sizing: border-box;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
&::-webkit-scrollbar {
@ -1432,11 +1605,15 @@ export default {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: nowrap;
gap: 10rpx;
}
.price-info {
display: flex;
align-items: baseline;
white-space: nowrap;
flex-shrink: 0;
.price-symbol {
font-size: 24rpx;
@ -1448,17 +1625,21 @@ export default {
color: #ff7a0e;
font-weight: bold;
margin: 0 4rpx;
white-space: nowrap;
}
.price-unit {
font-size: 24rpx;
color: #999;
white-space: nowrap;
}
}
.quantity-control {
display: flex;
align-items: center;
flex-shrink: 0;
white-space: nowrap;
button {
width: 60rpx;
@ -1484,7 +1665,7 @@ export default {
}
.quantity {
width: 80rpx;
width: 50rpx;
text-align: center;
font-size: 32rpx;
color: #333;
@ -1531,15 +1712,16 @@ export default {
flex-direction: column;
justify-content: center;
min-width: 0;
overflow: hidden;
}
.other-unrecycle-title {
font-size: 30rpx;
color: #222;
font-weight: bold;
margin-bottom: 8rpx;
word-break: break-all;
white-space: normal;
overflow: visible;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.other-unrecycle-desc {
font-size: 24rpx;
@ -1552,11 +1734,13 @@ export default {
.other-unrecycle-price-row {
display: flex;
align-items: center;
white-space: nowrap;
}
.other-unrecycle-price {
font-size: 28rpx;
color: #ff9c00;
font-weight: bold;
white-space: nowrap;
}
.other-unrecycle-btn {
width: 60rpx;
@ -1578,7 +1762,7 @@ export default {
right: 0;
bottom: calc(v-bind('tabbarHeight + "px"') + env(safe-area-inset-bottom));
width: 100vw;
z-index: 1001;
z-index: 100;
background: transparent;
box-sizing: border-box;
pointer-events: auto;
@ -1597,13 +1781,14 @@ export default {
background: #eaffea;
color: #13ac47;
font-size: 20rpx;
// padding: 16rpx 30rpx 0 30rpx;
padding: 8rpx 30rpx;
box-sizing: border-box;
text-align: left;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
// flex-direction: r;
line-height: 1.4;
min-height: 40rpx;
.tip-highlight {
color: #ff9c00;
font-weight: bold;
@ -1796,6 +1981,8 @@ export default {
display: flex;
align-items: center;
margin-left: 20rpx;
margin-right: 30rpx;
flex-shrink: 0;
}
.panel-quantity-control button {
width: 48rpx;
@ -1818,7 +2005,7 @@ export default {
}
}
.panel-quantity-control .quantity {
width: 40rpx;
width: 30rpx;
text-align: center;
font-size: 28rpx;
color: #333;


+ 788
- 278
pages/manager/inspect.vue
File diff suppressed because it is too large
View File


+ 63
- 7
pages/manager/order-detail.vue View File

@ -83,11 +83,13 @@
<text class="custom-goods-desc">{{ item.pinName }}</text>
<text class="custom-goods-desc">{{ item.details }}</text>
<view class="custom-goods-meta">
<text class="custom-goods-price">¥{{ item.onePrice }}<text class="custom-goods-unit"> /</text></text>
<text class="custom-goods-price" v-if="shouldShowSinglePrice(item.estimatedPrice)">¥{{ item.onePrice }}<text class="custom-goods-unit"> /</text></text>
<text class="custom-goods-price" v-else>¥{{ getSinglePriceRange(item.estimatedPrice, item.num) }}<text class="custom-goods-unit"> /</text></text>
<text class="custom-goods-count">x{{ item.num }}</text>
</view>
</view>
<text class="custom-goods-total">¥{{ item.estimatedPrice }}</text>
<text class="custom-goods-total" v-if="shouldShowSinglePrice(item.estimatedPrice)">¥{{ parseFloat(item.estimatedPrice.split('-')[0]).toFixed(2) }}</text>
<text class="custom-goods-total" v-else>¥{{ item.estimatedPrice.replace('-', '~') }}</text>
</view>
</view>
</view>
@ -123,11 +125,13 @@
<text class="custom-goods-desc">{{ item.pinName }}</text>
<text class="custom-goods-desc">{{ item.details }}</text>
<view class="custom-goods-meta">
<text class="custom-goods-price">¥{{ item.onePrice }}<text class="custom-goods-unit"> /</text></text>
<text class="custom-goods-price" v-if="shouldShowSinglePrice(item.estimatedPrice)">¥{{ item.onePrice }}<text class="custom-goods-unit"> /</text></text>
<text class="custom-goods-price" v-else>¥{{ getSinglePriceRange(item.estimatedPrice, item.num) }}<text class="custom-goods-unit"> /</text></text>
<text class="custom-goods-count">x{{ item.num }}</text>
</view>
</view>
<text class="custom-goods-total">¥{{ item.estimatedPrice }}</text>
<text class="custom-goods-total" v-if="shouldShowSinglePrice(item.estimatedPrice)">¥{{ parseFloat(item.estimatedPrice.split('-')[0]).toFixed(2) }}</text>
<text class="custom-goods-total" v-else>¥{{ item.estimatedPrice.replace('-', '~') }}</text>
</view>
</view>
</view>
@ -166,11 +170,13 @@
<text class="custom-goods-desc">{{ item.pinName }}</text>
<text class="custom-goods-desc">{{ item.details }}</text>
<view class="custom-goods-meta">
<text class="custom-goods-price">¥{{ item.onePrice }}<text class="custom-goods-unit"> /</text></text>
<text class="custom-goods-price" v-if="shouldShowSinglePrice(item.estimatedPrice)">¥{{ item.onePrice }}<text class="custom-goods-unit"> /</text></text>
<text class="custom-goods-price" v-else>¥{{ getSinglePriceRange(item.estimatedPrice, item.num) }}<text class="custom-goods-unit"> /</text></text>
<text class="custom-goods-count">x{{ item.num }}</text>
</view>
</view>
<text class="custom-goods-total">¥{{ item.estimatedPrice }}</text>
<text class="custom-goods-total" v-if="shouldShowSinglePrice(item.estimatedPrice)">¥{{ parseFloat(item.estimatedPrice.split('-')[0]).toFixed(2) }}</text>
<text class="custom-goods-total" v-else>¥{{ item.estimatedPrice.replace('-', '~') }}</text>
</view>
</view>
</view>
@ -326,6 +332,34 @@ export default {
url: `/pages/subcomponent/inspection-report?orderId=${this.order.orderId}`
})
},
// estimatedPrice
shouldShowSinglePrice(estimatedPrice) {
if (!estimatedPrice || typeof estimatedPrice !== 'string') return true;
// estimatedPrice"25-0""25-50"
const parts = estimatedPrice.split('-');
if (parts.length !== 2) return true;
const maxPrice = parseFloat(parts[1]);
return maxPrice === 0;
},
//
getSinglePriceRange(estimatedPrice, quantity) {
if (!estimatedPrice || typeof estimatedPrice !== 'string' || !quantity) return '';
// estimatedPrice"25-50"
const parts = estimatedPrice.split('-');
if (parts.length !== 2) return estimatedPrice;
const minTotal = parseFloat(parts[0]);
const maxTotal = parseFloat(parts[1]);
//
const minPrice = (minTotal / quantity).toFixed(2);
const maxPrice = (maxTotal / quantity).toFixed(2);
return `${minPrice}~${maxPrice}`;
},
}
}
</script>
@ -793,6 +827,8 @@ $order-card-padding: 40px 28px;
box-shadow: 0 2px 8px rgba(255, 156, 0, 0.04);
position: relative;
margin-bottom: 18px;
min-width: 0;
flex-wrap: nowrap;
}
.custom-goods-img {
width: 48px;
@ -806,27 +842,42 @@ $order-card-padding: 40px 28px;
display: flex;
flex-direction: column;
justify-content: center;
min-width: 0;
margin-right: 10px;
}
.custom-goods-name {
font-size: 15px;
color: #222;
font-weight: bold;
margin-bottom: 2px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.custom-goods-desc {
font-size: 13px;
color: #bcbcbc;
margin-bottom: 10px;
margin-bottom: 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:last-child {
margin-bottom: 10px;
}
}
.custom-goods-meta {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: nowrap;
white-space: nowrap;
}
.custom-goods-price {
color: #ffb400;
font-size: 15px;
font-weight: bold;
white-space: nowrap;
flex-shrink: 0;
}
.custom-goods-unit {
color: #bcbcbc;
@ -837,6 +888,8 @@ $order-card-padding: 40px 28px;
color: #bcbcbc;
font-size: 13px;
margin-left: 8px;
white-space: nowrap;
flex-shrink: 0;
}
.custom-goods-total {
color: #222;
@ -844,6 +897,9 @@ $order-card-padding: 40px 28px;
font-weight: bold;
margin-left: 18px;
flex-shrink: 0;
white-space: nowrap;
min-width: 80px;
text-align: right;
}
.user-stat-modal-mask {
position: fixed;


+ 39
- 8
pages/subcomponent/detail.vue View File

@ -115,11 +115,13 @@
<text class="desc">{{ item.pinName }}</text>
<text class="goods-desc">{{ item.details }}</text>
<view class="goods-meta">
<text class="goods-price">¥ {{ item.onePrice }}<text class="goods-unit"> /</text></text>
<text class="goods-price" v-if="shouldShowSinglePrice(item.estimatedPrice)">¥ {{ item.onePrice }}<text class="goods-unit"> /</text></text>
<text class="goods-price" v-else>¥ {{ getSinglePriceRange(item.estimatedPrice, item.num) }}<text class="goods-unit"> /</text></text>
<text class="goods-count">x{{ item.num }}</text>
</view>
</view>
<text class="goods-total">¥{{ item.estimatedPrice }}</text>
<text class="goods-total" v-if="shouldShowSinglePrice(item.estimatedPrice)">¥{{ parseFloat(item.estimatedPrice.split('-')[0]).toFixed(2) }}</text>
<text class="goods-total" v-else>¥{{ item.estimatedPrice.replace('-', '~') }}</text>
</view>
</view>
</view>
@ -347,6 +349,34 @@ export default {
const min = now.getMinutes().toString().padStart(2, '0');
return `${y}-${m}-${d} ${h}:${min}`;
},
// estimatedPrice
shouldShowSinglePrice(estimatedPrice) {
if (!estimatedPrice || typeof estimatedPrice !== 'string') return true;
// estimatedPrice"5-0""5-10"
const parts = estimatedPrice.split('-');
if (parts.length !== 2) return true;
const maxPrice = parseFloat(parts[1]);
return maxPrice === 0;
},
//
getSinglePriceRange(estimatedPrice, quantity) {
if (!estimatedPrice || typeof estimatedPrice !== 'string' || !quantity) return '';
// estimatedPrice"25-50"
const parts = estimatedPrice.split('-');
if (parts.length !== 2) return estimatedPrice;
const minTotal = parseFloat(parts[0]);
const maxTotal = parseFloat(parts[1]);
//
const minPrice = (minTotal / quantity).toFixed(2);
const maxPrice = (maxTotal / quantity).toFixed(2);
return `${parseFloat(minPrice)}~${parseFloat(maxPrice)}`;
},
contactCourier() {
//
if (this.phone) {
@ -396,7 +426,7 @@ export default {
this.currentStatus = {
text: '【在线预约】',
time: data.goTime || '',
icon: '/static/home/① 在线预约.png'
icon: '/static/my/【待取件】快递员正在赶来.png'
}
} else if (status == 1 && state == 0) {
this.currentStep = 2
@ -802,15 +832,15 @@ letter-spacing: 0%;
position: fixed;
left: 0;
right: 0;
bottom: 30rpx;
z-index: 10;
bottom: 0;
z-index: 999;
background: #fff;
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 24rpx calc(env(safe-area-inset-bottom) + 12rpx) 24rpx;
box-shadow: 0 -2rpx 8rpx rgba(255, 156, 0, 0.04);
border-radius: 24rpx 24rpx 0 0;
padding: 24rpx 24rpx calc(env(safe-area-inset-bottom) + 24rpx) 24rpx;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.1);
border-top: 1rpx solid #f0f0f0;
gap: 24rpx;
}
.btn {
@ -954,5 +984,6 @@ letter-spacing: 0%;
}
.content {
margin-top: calc(150rpx + var(--status-bar-height));
padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
}
</style>

+ 11
- 4
pages/subcomponent/pickup.vue View File

@ -66,9 +66,11 @@
</view>
<!-- <view class="desc">{{ item.desc }}</view> -->
<view class="price-row">
<text class="price">{{ item.price || item.unitPrice }}~{{ item.maxPrice || item.unitPrice }}/</text>
<text class="price" v-if="!item.maxPrice || item.maxPrice == (item.price || item.unitPrice)">{{ item.price || item.unitPrice }}/</text>
<text class="price" v-else>{{ item.price || item.unitPrice }}~{{ item.maxPrice }}/</text>
<text class="count">x{{ item.quantity }}</text>
<text class="amount">{{ ((item.price || item.unitPrice) * item.quantity).toFixed(2) }}~{{ ((item.maxPrice || item.unitPrice) * item.quantity).toFixed(2) }}</text>
<text class="amount" v-if="!item.maxPrice || item.maxPrice == (item.price || item.unitPrice)">{{ ((item.price || item.unitPrice) * item.quantity).toFixed(2) }}</text>
<text class="amount" v-else>{{ ((item.price || item.unitPrice) * item.quantity).toFixed(2) }}~{{ ((item.maxPrice) * item.quantity).toFixed(2) }}</text>
</view>
</view>
</view>
@ -239,18 +241,23 @@ export default {
return this.selectedItems.reduce((sum, item) => sum + item.quantity, 0)
},
totalPriceRange() {
if (this.selectedItems.length === 0) return '0-0'
if (this.selectedItems.length === 0) return '0.0'
let minTotal = 0
let maxTotal = 0
this.selectedItems.forEach(item => {
const minPrice = item.price || item.unitPrice || 0
const maxPrice = item.maxPrice || item.unitPrice || 0
const maxPrice = item.maxPrice || minPrice || 0
minTotal += minPrice * item.quantity
maxTotal += maxPrice * item.quantity
})
//
if (minTotal === maxTotal) {
return minTotal.toFixed(2)
}
return `${minTotal.toFixed(2)}~${maxTotal.toFixed(2)}`
},
canSubmit() {


+ 1
- 0
pages/subcomponent/promotion.vue View File

@ -88,6 +88,7 @@
<view class="qrcode-section">
<view class="qrcode-title">联系客服</view>
<image class="qrcode-img" :src="qrcodeUrl" mode="aspectFit" @click="openQrcodeModal" />
<text class="qrcode-modal-benefit">添加客服可以获取更多推广福利</text>
</view>
</view>
<!-- 底部按钮区 -->


+ 2
- 2
pages/subcomponent/select.vue View File

@ -64,7 +64,7 @@
<text class="label">手机号</text>
<input class="input" v-model="form.phone" placeholder="请输入" />
</view>
<view class="form-item" @tap="selectAddress">
<view class="form-item" @tap="selectRegion">
<text class="label">所在地区</text>
<view class="input region-input">
<text :class="{placeholder: !form.address}">
@ -302,7 +302,7 @@ export default {
this.regionIndex = [pIdx, cIdx, dIdx]
},
//
selectAddress() {
selectRegion() {
//
uni.getSetting({
success: (res) => {


+ 158
- 17
pages/subcomponent/wallet.vue View File

@ -16,9 +16,46 @@
<swiper :indicator-dots="false" :autoplay="true" :interval="3000" :duration="500" circular
style="width: 100%; height: 100%;">
<swiper-item v-for="(item, index) in bannerList" :key="item.id || index">
<video v-if="item.type == 1" :src="item.voUrl" autoplay muted loop :controls="false"
:show-play-btn="false" :show-center-play-btn="false" object-fit="cover"
style="width: 100%; height: 100%;"></video>
<view v-if="item.type == 1" class="video-container">
<!-- 预览状态显示封面图 -->
<image
v-if="!videoPlayingStates[index]"
:src="item.image || ''"
mode="aspectFill"
style="width: 100%; height: 100%;"
class="video-poster"
@click="playVideoFullscreen(item, index)"
/>
<!-- 播放状态显示视频 -->
<video
v-else
:id="`wallet-video-${index}`"
:src="item.voUrl"
:autoplay="true"
:muted="false"
:loop="false"
:controls="true"
:show-play-btn="true"
:show-center-play-btn="false"
:show-fullscreen-btn="true"
:show-progress="true"
:show-mute-btn="true"
:enable-progress-gesture="true"
:enable-play-gesture="true"
object-fit="cover"
style="width: 100%; height: 100%;"
@fullscreenchange="onFullscreenChange"
@play="onVideoPlay(index)"
@pause="onVideoPause(index)"
@ended="onVideoEnded(index)"
></video>
<!-- 播放按钮覆盖层 -->
<view v-if="!videoPlayingStates[index]" class="video-overlay" @click="playVideoFullscreen(item, index)">
<view class="play-button-large">
<view class="play-triangle"></view>
</view>
</view>
</view>
<image v-else :src="item.image" mode="aspectFill" style="width: 100%; height: 100%;" />
</swiper-item>
</swiper>
@ -127,7 +164,8 @@ export default {
noMore: false,
refreshing: false,
isInitialized: false,
bannerList: []
bannerList: [],
videoPlayingStates: {} //
}
},
computed: {
@ -357,6 +395,54 @@ export default {
} finally {
this.loadingMore = false
}
},
//
playVideoFullscreen(item, index) {
if (!this.videoPlayingStates[index]) {
//
this.$set(this.videoPlayingStates, index, true);
//
this.$nextTick(() => {
setTimeout(() => {
const videoContext = uni.createVideoContext(`wallet-video-${index}`, this);
if (videoContext) {
//
videoContext.requestFullScreen({
direction: -1 //
});
}
}, 200);
});
}
},
onVideoPlay(index) {
this.$set(this.videoPlayingStates, index, true);
},
onVideoPause(index) {
this.$set(this.videoPlayingStates, index, false);
},
onVideoEnded(index) {
//
this.$set(this.videoPlayingStates, index, false);
},
onFullscreenChange(e) {
console.log('全屏状态改变:', e.detail);
const videoIndex = e.target.id.replace('wallet-video-', '');
if (e.detail.fullScreen) {
//
console.log('进入全屏模式,方向:', e.detail.direction);
} else {
// 退
console.log('退出全屏模式,回到预览状态');
this.$set(this.videoPlayingStates, videoIndex, false);
}
}
}
}
@ -408,23 +494,78 @@ export default {
overflow: hidden;
border-radius: 0 0 30rpx 30rpx;
margin-top: 0;
}
.video-container {
position: relative;
width: 100%;
height: 100%;
.video-poster {
cursor: pointer;
transition: transform 0.2s ease;
&:active {
transform: scale(0.98);
}
}
.video-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 10;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
.play-button-large {
width: 120rpx;
height: 120rpx;
background: transparent;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
backdrop-filter: blur(10rpx);
transition: all 0.3s ease;
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.3);
&:active {
transform: scale(0.9);
background: rgba(255, 255, 255, 0.1);
}
.play-triangle {
width: 0;
height: 0;
border-left: 24rpx solid #fff;
border-top: 18rpx solid transparent;
border-bottom: 18rpx solid transparent;
margin-left: 8rpx;
transition: all 0.3s ease;
filter: drop-shadow(0 2rpx 4rpx rgba(0, 0, 0, 0.3));
}
}
&:hover .play-button-large {
transform: scale(1.1);
box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.4);
.play-triangle {
border-left-color: #ff8917;
}
}
}
}
}
.banner-content {
position: relative;
z-index: 1;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.wallet-icon {
width: 240rpx;
height: 240rpx;
}
}
/* .banner-content 样式已移除,现在使用轮播图 */
.balance-card {
margin: -60rpx 30rpx 0;


Loading…
Cancel
Save