爱简收旧衣按件回收前端代码仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

717 lines
20 KiB

<template>
<view class="pickup-container" :style="{paddingTop: navBarHeightRpx + 'rpx'}">
<!-- 顶部导航栏 -->
<view class="nav-bar" :style="{height: (statusBarHeight + 88) + 'rpx', paddingTop: statusBarHeight + 'px'}">
<view class="back" @tap="goBack">
<uni-icons type="left" size="20"></uni-icons>
</view>
<text class="title">免费上门取件预约</text>
</view>
<!-- 内容区域 -->
<view class="content">
<!-- 回收流程卡片 -->
<view class="card process-card">
<view class="card-title process-title">回收流程</view>
<view class="process-steps">
<view
class="process-step-card"
v-for="(step, i) in steps"
:key="i"
>
<image :src="step.icon" class="step-icon" mode="aspectFit" />
<view v-if="i === 0" class="step-bottom-bar">
<view class="step-num-bar">
<text class="text-main">{{ step.num }}{{ step.text }}</text>
</view>
</view>
<view v-else class="step-label-gray">
<text class="text-gray">{{ step.num }}{{ step.text }}</text>
</view>
</view>
</view>
<view class="divider"></view>
<!-- 取件信息 -->
<view class="pickup-info">
<view class="info-item" @tap="selectAddress">
<text class="label">取件地址</text>
<view class="value">
<text class="text" :class="{placeholder: !displayAddress}">{{ displayAddress || '请选择' }}</text>
<text class="arrow">></text>
</view>
</view>
<view class="info-item" @tap="openTimePicker">
<text class="label">上门时间</text>
<view class="value">
<text class="text" :class="{placeholder: !selectedTime}">{{ selectedTime || '请选择' }}</text>
<text class="arrow">></text>
</view>
</view>
</view>
</view>
<!-- 订单详情卡片 -->
<view class="card order-card">
<view class="card-title process-title">订单详情</view>
<view class="order-items">
<view class="order-item" v-for="(item, index) in showAllItems ? selectedItems : selectedItems.slice(0, 3)" :key="index">
<image :src="item.icon" mode="aspectFit"></image>
<view class="item-info">
<view class="name">{{ item.name }}</view>
<view class="desc">{{ item.desc }}</view>
<view class="price-row">
<text class="price">{{ item.unitPrice }}/</text>
<text class="count">x{{ item.quantity }}</text>
<text class="amount">{{ item.unitPrice * item.quantity }}</text>
</view>
</view>
</view>
</view>
<view v-if="selectedItems.length > 3" class="expand-btn" @tap="toggleExpandOrder">
<text>{{ showAllItems ? '收起' : `展开(共${selectedItems.length}件)` }}</text>
<text class="arrow">{{ showAllItems ? '▲' : '▼' }}</text>
</view>
</view>
</view>
<!-- 订单说明 -->
<view class="order-desc">
<view>1. 当前回收快递免费上门,由于快递成本较高,为避免不必要的成本及资源二次浪费不属于回收品类或不符合回收标准的物品请勿寄出。</view>
<view>2. 已通过的回收物品将正常结算。不符合回收要求的物品可选择安排取回,逾期未联系将默认捐赠,无法再次取回。</view>
<view>3. 若用户寄出大量不可回收的物品,平台有权限制下次回收权限,或取消下次包邮服务。</view>
<view>4. 对于合格率高的回收订单,平台将根据实际情况,给予额外回收奖励。</view>
</view>
<!-- 底部提交栏 -->
<view class="agreement-bar">
<view class="checkbox" :class="{active: agreed}" @tap="toggleAgreement">
<text v-if="agreed">✓</text>
</view>
<text>我已阅读并同意</text>
<text class="link" @tap="showServiceAgreement">《回收服务协议》</text>
<text>和</text>
<text class="link" @tap="showPrivacyPolicy">《隐私政策》</text>
</view>
<view class="bottom-bar">
<view class="summary">
<text>已选 {{ totalCount }} 件 预估回收可得</text>
<text class="amount">¥{{ totalPriceRange }}</text>
</view>
<button class="main-btn" @tap="submitOrder">预约上门取件</button>
</view>
<!-- 时间选择弹窗 -->
<view class="time-picker" v-if="showTimePicker">
<view class="mask" @tap="closeTimePicker"></view>
<view class="picker-content">
<view class="picker-header">
<text class="reset" @tap="resetPicker">重置</text>
<text class="title">预约上门时间</text>
</view>
<view class="picker-section">
<view class="section-title">选择日期</view>
<view class="date-btns">
<view
v-for="(tab, index) in dateTabs"
:key="index"
:class="['date-btn', {active: currentDateTab === index}]"
@tap="selectDateTab(index)"
>
{{ tab }}
</view>
</view>
</view>
<view class="picker-section">
<view class="section-title">选择时间</view>
<view class="time-btns">
<view
v-for="(slot, idx) in timeSlots"
:key="idx"
:class="['time-btn', {active: selectedTimeSlot === idx}]"
@tap="selectTimeSlot(idx)"
>
{{ slot }}
</view>
</view>
</view>
<view class="confirm-btn" @tap="confirmTime">确认</view>
</view>
</view>
</view>
</template>
<script>
import pullRefreshMixin from '@/pages/mixins/pullRefreshMixin.js'
export default {
mixins: [pullRefreshMixin],
data() {
return {
statusBarHeight: 0,
navBarHeight: 0, // px
navBarHeightRpx: 0, // rpx
fromRecycle: false,
address: '',
selectedAddress: null,
selectedTime: '',
agreed: false,
selectedItems: [
{
name: '羽绒服',
icon: '/static/home/羽绒服.png',
desc: '允许脏破烂,160码以上',
unitPrice: 8,
quantity: 8
},
{
name: '品牌服饰',
icon: '/static/home/品牌服饰.png',
desc: '允许脏破烂,160码以上',
unitPrice: 8,
quantity: 1
},{
name: '羽绒服',
icon: '/static/home/羽绒服.png',
desc: '允许脏破烂,160码以上',
unitPrice: 8,
quantity: 8
},
{
name: '品牌服饰',
icon: '/static/home/品牌服饰.png',
desc: '允许脏破烂,160码以上',
unitPrice: 8,
quantity: 1
},{
name: '羽绒服',
icon: '/static/home/羽绒服.png',
desc: '允许脏破烂,160码以上',
unitPrice: 8,
quantity: 8
},
{
name: '品牌服饰',
icon: '/static/home/品牌服饰.png',
desc: '允许脏破烂,160码以上',
unitPrice: 8,
quantity: 1
}
],
showTimePicker: false,
currentDateTab: 0,
dateTabs: ['今天 04-20', '明天 04-21', '后天 04-22', '周一 04-23', '周二 04-24', '周三 04-25'],
timeSlots: ['11:00~13:00', '13:00~15:00', '15:00~17:00'],
selectedTimeSlot: 0,
steps: [
{ icon: '/static/home/① 在线预约.png', num: '①', text: '在线预约' },
{ icon: '/static/home/② 快递上门.png', num: '②', text: '快递上门' },
{ icon: '/static/home/③ 透明质检.png', num: '③', text: '透明质检' },
{ icon: '/static/home/④ 现金打款.png', num: '④', text: '现金打款' }
],
showAllItems: false
}
},
onShow() {
// 页面显示时触发,包括从地址选择页面返回时
console.log('当前选中的地址:', this.selectedAddress)
if (this.selectedAddress) {
// 确保地址信息被正确更新
this.address = this.selectedAddress.address
// 强制更新视图
this.$forceUpdate()
}
},
onLoad(options) {
// 判断是否从回收页面跳转而来
this.fromRecycle = options.fromRecycle === 'true'
// 如果是从回收页面跳转来的,解析传递的衣物信息
if (this.fromRecycle && options.items) {
try {
this.selectedItems = JSON.parse(decodeURIComponent(options.items))
} catch (e) {
console.error('解析衣物信息失败:', e)
}
}
// 监听地址选择事件
uni.$on('addressSelected', (address) => {
console.log('接收到选中的地址:', address)
this.selectedAddress = address
this.address = address.address
this.$forceUpdate()
})
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight
let navBarHeight = 44
try {
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
navBarHeight = menuButtonInfo.bottom + menuButtonInfo.top - sysInfo.statusBarHeight
} catch (e) {}
this.navBarHeight = navBarHeight
this.navBarHeightRpx = Math.round(navBarHeight * 750 / sysInfo.windowWidth)
},
onUnload() {
// 页面卸载时移除事件监听
uni.$off('addressSelected')
},
computed: {
totalCount() {
return this.selectedItems.reduce((sum, item) => sum + item.quantity, 0)
},
totalPriceRange() {
if (this.selectedItems.length === 0) return '0-0'
const total = this.selectedItems.reduce((sum, item) => sum + (item.unitPrice * item.quantity), 0)
return `${(total * 0.92).toFixed(1)}~${(total * 1.1).toFixed(1)}`
},
canSubmit() {
return this.agreed && this.selectedItems.length > 0 && this.selectedTime && this.displayAddress
},
displayAddress() {
console.log('displayAddress computed:', this.selectedAddress)
if (this.selectedAddress) {
return this.selectedAddress.address
}
return ''
}
},
methods: {
async onRefresh() {
// 模拟刷新数据
await new Promise(resolve => setTimeout(resolve, 1000))
this.stopPullRefresh()
},
goBack() {
uni.navigateBack()
},
showMoreMenu() {
uni.showModal({ title: '更多', content: '这里可以放更多操作' })
},
showScan() {
uni.showModal({ title: '扫码', content: '这里可以实现扫码功能' })
},
selectAddress() {
uni.navigateTo({ url: '/pages/subcomponent/select?mode=select' })
},
openTimePicker() {
this.showTimePicker = true
},
closeTimePicker() {
this.showTimePicker = false
},
selectDateTab(index) {
this.currentDateTab = index
},
selectTimeSlot(index) {
this.selectedTimeSlot = index
},
confirmTime() {
this.selectedTime = `${this.dateTabs[this.currentDateTab]} ${this.timeSlots[this.selectedTimeSlot]}`
this.closeTimePicker()
},
resetPicker() {
this.currentDateTab = 0
this.selectedTimeSlot = 0
},
toggleAgreement() {
this.agreed = !this.agreed
},
showServiceAgreement() {
uni.showModal({ title: '回收服务协议', content: '这里展示回收服务协议内容' })
},
showPrivacyPolicy() {
uni.showModal({ title: '隐私政策', content: '这里展示隐私政策内容' })
},
submitOrder() {
if (!this.agreed) {
uni.showToast({ title: '请先同意服务协议', icon: 'none' })
return
}
if (!this.displayAddress || this.displayAddress === '请选择取件地址') {
uni.showToast({ title: '请选择取件地址', icon: 'none' })
return
}
if (!this.selectedTime) {
uni.showToast({ title: '请选择上门时间', icon: 'none' })
return
}
if (this.selectedItems.length === 0) {
uni.showToast({ title: '请选择回收物品', icon: 'none' })
return
}
// 校验通过,提交
uni.showLoading({ title: '提交中...' })
setTimeout(() => {
uni.hideLoading()
uni.showToast({ title: '预约成功', icon: 'success' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
}, 1000)
},
toggleExpandOrder() {
this.showAllItems = !this.showAllItems
}
}
}
</script>
<style lang="scss" scoped>
.pickup-container {
min-height: 100vh;
background: #f8f8f8;
}
.nav-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
display: flex;
align-items: center;
background: #fff;
padding: 0 30rpx;
.back {
padding: 20rpx;
margin-left: -20rpx;
}
.title {
flex: 1;
text-align: center;
font-size: 34rpx;
font-weight: 500;
color: #222;
}
.right-btns {
display: flex;
gap: 30rpx;
.more, .scan {
font-size: 40rpx;
color: #333;
}
}
}
.content {
padding: 20rpx;
}
.card {
background: linear-gradient(to bottom,#fff3db 0%,#fffefb 40%);
border-radius: 20rpx;
margin-bottom: 20rpx;
overflow: hidden;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.03);
}
.process-card {
background: #fff;
border-radius: 24rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 149, 0, 0.08);
padding: 0 0 20rpx 0;
}
.process-steps {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 0 30rpx 30rpx;
.process-step-card {
background: #FFF8ED;
border-radius: 24rpx;
min-width: 140rpx;
min-height: 180rpx;
display: flex;
flex-direction: column;
align-items: center;
margin-right: 24rpx;
position: relative;
.step-icon {
width: 64rpx;
height: 64rpx;
margin: 24rpx 0 18rpx 0;
}
.step-bottom-bar {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 56rpx;
background: #FFB74D;
border-radius: 0 0 24rpx 24rpx;
display: flex;
align-items: center;
justify-content: center;
.step-num-bar {
display: flex;
flex-direction: row;
align-items: center;
margin-top: 8rpx;
.num-main {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
background: #fff;
color: #FFB74D;
font-size: 22rpx;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
margin-right: 10rpx;
}
.text-main {
color: #fff;
font-size: 26rpx;
font-weight: 500;
}
}
}
.step-label-gray {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 56rpx;
background: #FFF8ED;
border-radius: 0 0 24rpx 24rpx;
display: flex;
align-items: center;
justify-content: center;
.num-gray {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
background: #eee;
color: #bbb;
font-size: 22rpx;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
margin-right: 10rpx;
}
.text-gray {
color: #bbb;
font-size: 26rpx;
font-weight: 500;
}
}
}
.process-step-card:last-child {
margin-right: 0;
}
}
.divider {
height: 1rpx;
background: rgba(0, 0, 0, 0.05);
margin: 0 30rpx;
}
.pickup-info {
padding: 0 30rpx;
.info-item {
padding: 30rpx 0;
border-bottom: 1rpx solid rgba(0, 0, 0, 0.05);
&:last-child { border-bottom: none; }
.label {
font-size: 28rpx;
color: #333;
margin-bottom: 16rpx;
display: block;
}
.value {
display: flex;
justify-content: space-between;
align-items: center;
.text {
flex: 1;
font-size: 28rpx;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.text.placeholder { color: #ccc; }
.arrow {
color: #999;
font-size: 28rpx;
margin-left: 10rpx;
}
}
}
}
.order-card {
background: #fff;
border-radius: 24rpx;
box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.04);
margin-bottom: 20rpx;
.order-items {
padding: 0 30rpx;
.order-item {
display: flex;
align-items: center;
padding: 30rpx 0;
border-bottom: 1rpx solid #f5f5f5;
&:last-child { border-bottom: none; }
image { width: 80rpx; height: 80rpx; margin-right: 20rpx; }
.item-info {
flex: 1;
.name { font-size: 30rpx; color: #333; font-weight: 500; }
.desc { font-size: 24rpx; color: #999; margin: 4rpx 0 8rpx 0; }
.price-row {
display: flex;
align-items: center;
.price { color: #FF9500; font-size: 26rpx; margin-right: 10rpx; }
.count { color: #999; font-size: 24rpx; margin-right: 10rpx; }
.amount { color: #333; font-size: 28rpx; margin-left: auto; }
}
}
}
}
.expand-btn {
text-align: center;
color: #999;
font-size: 24rpx;
padding: 10rpx 0;
.arrow { font-size: 20rpx; }
}
}
.agreement-bar {
display: flex;
align-items: center;
background: #fffbe6;
padding: 20rpx 30rpx;
font-size: 24rpx;
.checkbox {
width: 32rpx; height: 32rpx; border-radius: 50%; border: 2rpx solid #FFB74D;
margin-right: 10rpx; display: flex; align-items: center; justify-content: center;
background: #fff;
&.active { background: #FFB74D; color: #fff; }
}
.link { color: #FFB74D; }
}
.bottom-bar {
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
padding: 20rpx 30rpx calc(40rpx + env(safe-area-inset-bottom));
.summary { color: #666; font-size: 26rpx; }
.amount { color: #FF9500; font-size: 32rpx; font-weight: bold; margin-left: 10rpx; }
.main-btn {
background: #FFB74D;
color: #fff;
font-size: 28rpx;
border-radius: 40rpx;
padding: 0 40rpx;
width: 60%;
height: 80rpx;
display: flex;
justify-content: center;
&[disabled] { opacity: 0.5; }
}
}
.order-desc {
color: #999;
font-size: 22rpx;
padding: 0 30rpx 20rpx 30rpx;
line-height: 1.7;
}
.time-picker {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 1000;
.mask {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
}
.picker-content {
position: absolute;
left: 0;
right: 0;
bottom: 0;
background: #fff;
border-radius: 20rpx 20rpx 0 0;
padding-bottom: env(safe-area-inset-bottom);
.picker-header {
padding: 30rpx 0 0 0;
display: flex;
align-items: center;
border-bottom: 1rpx solid #eee;
.reset {
color: #bbb;
font-size: 28rpx;
margin-left: 30rpx;
}
.title {
flex: 1;
text-align: center;
font-size: 32rpx;
font-weight: 500;
color: #222;
margin-right: 60rpx;
}
}
.picker-section {
padding: 30rpx 30rpx 0 30rpx;
.section-title {
font-size: 28rpx;
color: #222;
margin-bottom: 20rpx;
}
.date-btns, .time-btns {
display: flex;
flex-wrap: wrap;
gap: 20rpx 20rpx;
}
.date-btn, .time-btn {
width: 200rpx;
height: 70rpx;
background: #f5f5f5;
color: #999;
border-radius: 18rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
border: 2rpx solid transparent;
margin-bottom: 10rpx;
}
.date-btn.active, .time-btn.active {
background: #fff;
color: #FFB74D;
border: 2rpx solid #FFB74D;
font-weight: 500;
}
}
.confirm-btn {
margin: 40rpx 30rpx 30rpx 30rpx;
height: 90rpx;
background: linear-gradient(90deg, #FFB74D 0%, #FF9500 100%);
color: #fff;
font-size: 32rpx;
border-radius: 45rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 16rpx rgba(255, 149, 0, 0.08);
}
}
}
.process-title {
font-size: 32rpx;
font-weight: bold;
background: linear-gradient(to bottom,#fff3db 0%,#fffefb 40%);
color: #222;
text-align: left;
padding: 36rpx 0 24rpx 30rpx;
letter-spacing: 1rpx;
}
</style>