4 changed files with 1406 additions and 3 deletions
@ -0,0 +1,900 @@ |
|||
<template> |
|||
<view class="confirm-container"> |
|||
<scroll-view class="content-scroll" scroll-y> |
|||
<!-- 预约日期 --> |
|||
<view class="date-section" v-if="needReservation"> |
|||
<view class="section-title"> |
|||
<text>预约日期</text> |
|||
<text class="required">*</text> |
|||
</view> |
|||
<view class="reservation-desc"> |
|||
<text class="desc-text" |
|||
>此商品需要提前{{ goodsInfo.reservationDays }}天预约</text |
|||
> |
|||
</view> |
|||
<view class="date-picker-container"> |
|||
<uni-datetime-picker |
|||
v-model="selectedDate" |
|||
type="date" |
|||
:clear-icon="false" |
|||
:start="minDate" |
|||
placeholder="请选择预约日期" |
|||
@change="onDateChange" |
|||
> |
|||
<view class="date-input"> |
|||
<text class="date-text" :class="{ placeholder: !selectedDate }"> |
|||
{{ selectedDate || "请选择预约日期" }} |
|||
</text> |
|||
<uni-icons type="calendar" size="18" color="#c0c4cc" /> |
|||
</view> |
|||
</uni-datetime-picker> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 商品信息 --> |
|||
<view class="goods-section"> |
|||
<!-- <view class="section-title"> |
|||
<text>{{ goodsInfo.title || "-" }}</text> |
|||
</view> --> |
|||
<view class="goods-card"> |
|||
<image class="goods-image" :src="goodsInfo.image" mode="aspectFill" /> |
|||
<view class="goods-info"> |
|||
<text class="goods-name">{{ goodsInfo.goodsName || "--" }}</text> |
|||
|
|||
<!-- 规格选择按钮 --> |
|||
<view class="specifications-list"> |
|||
<view |
|||
class="spec-item" |
|||
v-for="(spec, specIndex) in specifications" |
|||
:key="specIndex" |
|||
@click="selectSpecification(specIndex)" |
|||
> |
|||
<view class="spec-content"> |
|||
<text class="spec-label">第{{ specIndex + 1 }}份规格</text> |
|||
<text |
|||
class="spec-text" |
|||
:class="{ placeholder: !spec.selectedSpec }" |
|||
> |
|||
{{ spec.selectedSpec || "请选择规格" }} |
|||
</text> |
|||
</view> |
|||
<uni-icons type="right" size="14" color="#c0c4cc" /> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 备注 --> |
|||
<view class="note-section"> |
|||
<view class="section-row"> |
|||
<text class="section-label">备注</text> |
|||
<view class="note-input"> |
|||
<input |
|||
v-model="note" |
|||
placeholder="选填" |
|||
placeholder-class="placeholder" |
|||
maxlength="200" |
|||
/> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="submit-section-content" style="height: 230rpx"> </view> |
|||
</scroll-view> |
|||
|
|||
<!-- SKU选择弹窗 --> |
|||
<uni-popup ref="specificationPopup" type="bottom"> |
|||
<view class="specification-popup"> |
|||
<view class="popup-header"> |
|||
<text class="popup-title">选择规格</text> |
|||
<text class="popup-close" @click="closeSpecificationPopup">×</text> |
|||
</view> |
|||
|
|||
<view class="popup-content"> |
|||
<!-- 商品信息 --> |
|||
<view class="goods-preview"> |
|||
<image |
|||
class="preview-image" |
|||
:src="currentGoods.image || '/static/image/goods-default.jpg'" |
|||
mode="aspectFill" |
|||
/> |
|||
<view class="preview-info"> |
|||
<text class="preview-name">{{ currentGoods.goodsName }}</text> |
|||
</view> |
|||
</view> |
|||
<!-- SKU选择 --> |
|||
<view class="sku-section"> |
|||
<text class="sku-title">选择规格</text> |
|||
<view class="sku-options"> |
|||
<view |
|||
v-for="(sku, index) in goodsInfo.specCombinations" |
|||
:key="sku.specCombinationId" |
|||
:class="['sku-option', { active: selectedSkuIndex === index }]" |
|||
@click="selectSku(index, sku)" |
|||
> |
|||
<view class="sku-option-content"> |
|||
<view class="sku-option-info"> |
|||
<text class="sku-option-name">{{ |
|||
getSkuDisplayName(sku) |
|||
}}</text> |
|||
<!-- <text class="sku-option-price">¥{{ sku.salePrice }}</text> --> |
|||
</view> |
|||
<view class="sku-option-details"> |
|||
<text class="sku-option-quantity" |
|||
>库存: {{ sku.quantity }}</text |
|||
> |
|||
<!-- <text class="sku-option-code">编码: {{ sku.skuCode }}</text> --> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 确认按钮 --> |
|||
<view class="popup-actions"> |
|||
<button |
|||
class="confirm-spec-btn" |
|||
@click="confirmSpecification" |
|||
:disabled="selectedSkuIndex === -1" |
|||
> |
|||
确认规格 |
|||
</button> |
|||
</view> |
|||
</view> |
|||
</uni-popup> |
|||
|
|||
<!-- 底部提交区域 --> |
|||
<view |
|||
class=" " |
|||
style="position: fixed; width: 100%; bottom: 0; z-index: 98" |
|||
> |
|||
<view class="submit-section"> |
|||
<button class="submit-btn" @click="submitOrder">确认下单</button> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
// 选中的日期 |
|||
selectedDate: "2025-01-03", // 假设今天是2024-12-27,7天后是2025-01-03 |
|||
// 商品信息 |
|||
goodsInfo: { |
|||
id: "goods001", |
|||
name: "精品咖啡体验券", |
|||
goodsName: "精品咖啡体验券", |
|||
desc: "享受一杯精心调制的手冲咖啡,感受咖啡豆的独特香气", |
|||
price: "58.00", |
|||
image: "https://via.placeholder.com/300x200/4A90E2/FFFFFF?text=Coffee", |
|||
specCombinations: [ |
|||
{ |
|||
specCombinationId: "spec001", |
|||
specValueOne: "大杯", |
|||
specValueTwo: "美式咖啡", |
|||
salePrice: "58.00", |
|||
quantity: 10, |
|||
skuCode: "COFFEE-L-AM", |
|||
}, |
|||
{ |
|||
specCombinationId: "spec002", |
|||
specValueOne: "中杯", |
|||
specValueTwo: "拿铁咖啡", |
|||
salePrice: "48.00", |
|||
quantity: 5, |
|||
skuCode: "COFFEE-M-LT", |
|||
}, |
|||
{ |
|||
specCombinationId: "spec003", |
|||
specValueOne: "大杯", |
|||
specValueTwo: "卡布奇诺", |
|||
salePrice: "55.00", |
|||
quantity: 8, |
|||
skuCode: "COFFEE-L-CP", |
|||
}, |
|||
], |
|||
reservationDays: 7, // 预约天数,0表示不需要预约,7表示需要提前7天预约 |
|||
orderChildNum: 2, |
|||
}, |
|||
// 备注 |
|||
note: "", |
|||
// 是否需要预约 |
|||
needReservation: true, |
|||
// 最小可选日期 |
|||
minDate: "", |
|||
// SKU选择相关 |
|||
currentSpecIndex: -1, |
|||
currentGoods: {}, |
|||
selectedSkuIndex: -1, |
|||
specifications: [ |
|||
{ |
|||
selectedSpec: "大杯 美式咖啡", |
|||
selectedSkuIndex: 0, |
|||
selectedSku: { |
|||
specCombinationId: "spec001", |
|||
specValueOne: "大杯", |
|||
specValueTwo: "美式咖啡", |
|||
salePrice: "58.00", |
|||
quantity: 10, |
|||
skuCode: "COFFEE-L-AM", |
|||
}, |
|||
}, |
|||
{ |
|||
selectedSpec: "中杯 拿铁咖啡", |
|||
selectedSkuIndex: 1, |
|||
selectedSku: { |
|||
specCombinationId: "spec002", |
|||
specValueOne: "中杯", |
|||
specValueTwo: "拿铁咖啡", |
|||
salePrice: "48.00", |
|||
quantity: 5, |
|||
skuCode: "COFFEE-M-LT", |
|||
}, |
|||
}, |
|||
], |
|||
orderChildId: "order001", |
|||
}; |
|||
}, |
|||
|
|||
onLoad(options) { |
|||
// 接收页面参数 |
|||
if (options.goodsId) { |
|||
this.orderChildId = options.orderChildId; |
|||
this.loadGoodsInfo(options.goodsId, options.orderChildId); |
|||
} else { |
|||
// 使用假数据时,设置预约日期逻辑 |
|||
this.initReservationLogic(); |
|||
} |
|||
if (options.goodsName) { |
|||
this.goodsInfo.name = decodeURIComponent(options.goodsName); |
|||
} |
|||
}, |
|||
|
|||
methods: { |
|||
// 日期变化回调 |
|||
onDateChange(date) { |
|||
this.selectedDate = date; |
|||
console.log("选择的日期:", date); |
|||
}, |
|||
|
|||
// 初始化预约逻辑(用于假数据) |
|||
initReservationLogic() { |
|||
// 判断是否需要预约 |
|||
const reservationDays = this.goodsInfo.reservationDays || 0; |
|||
this.needReservation = reservationDays > 0; |
|||
|
|||
// 设置最小可选日期 |
|||
if (this.needReservation) { |
|||
const today = new Date(); |
|||
today.setDate(today.getDate() + reservationDays); |
|||
this.minDate = today.toISOString().split("T")[0]; |
|||
|
|||
// 更新默认选中日期为最小可选日期 |
|||
this.selectedDate = this.minDate; |
|||
} |
|||
}, |
|||
|
|||
// 加载商品信息 |
|||
async loadGoodsInfo(goodsId, orderChildId) { |
|||
try { |
|||
// 这里调用实际的API接口获取商品详情 |
|||
this.Post( |
|||
{ |
|||
orderChildId: orderChildId, |
|||
}, |
|||
`/framework/goods/queryByGoodsId/${goodsId}`, |
|||
"DES" |
|||
).then((res) => { |
|||
if (res.code == 200) { |
|||
this.goodsInfo = res.data; |
|||
this.goodsInfo.image = res.data.mainUrl.split(",")[0]; |
|||
|
|||
// 判断是否需要预约 |
|||
const reservationDays = res.data.reservationDays || 0; |
|||
this.needReservation = reservationDays > 0; |
|||
|
|||
// 设置最小可选日期 |
|||
if (this.needReservation) { |
|||
const today = new Date(); |
|||
today.setDate(today.getDate() + reservationDays); |
|||
this.minDate = today.toISOString().split("T")[0]; |
|||
} |
|||
|
|||
// 初始化规格选择数组 |
|||
for (var i = 0; i < res.data.orderChildNum; i++) { |
|||
this.specifications.push({ |
|||
selectedSpec: "", |
|||
selectedSkuIndex: -1, |
|||
selectedSku: null, |
|||
}); |
|||
} |
|||
} else { |
|||
uni.showToast({ |
|||
title: res.msg, |
|||
icon: "none", |
|||
}); |
|||
} |
|||
}); |
|||
} catch (error) { |
|||
console.error("加载商品信息失败:", error); |
|||
} |
|||
}, |
|||
|
|||
// 选择规格 |
|||
selectSpecification(specIndex) { |
|||
this.currentSpecIndex = specIndex; |
|||
this.currentGoods = this.goodsInfo; |
|||
|
|||
// 回显之前的选择 |
|||
const currentSpec = this.specifications[specIndex]; |
|||
this.selectedSkuIndex = currentSpec.selectedSkuIndex || -1; |
|||
|
|||
this.$refs.specificationPopup.open(); |
|||
}, |
|||
|
|||
// 关闭规格选择弹窗 |
|||
closeSpecificationPopup() { |
|||
this.$refs.specificationPopup.close(); |
|||
this.resetSpecificationData(); |
|||
}, |
|||
|
|||
// 重置规格选择数据 |
|||
resetSpecificationData() { |
|||
this.selectedSkuIndex = -1; |
|||
}, |
|||
|
|||
// 选择SKU |
|||
selectSku(index, sku) { |
|||
if (sku.quantity > 0) { |
|||
this.selectedSkuIndex = index; |
|||
} else { |
|||
uni.showToast({ |
|||
title: "当前规格暂无库存", |
|||
icon: "none", |
|||
}); |
|||
} |
|||
}, |
|||
|
|||
// 获取SKU显示名称 |
|||
getSkuDisplayName(sku) { |
|||
let displayName = ""; |
|||
if (sku.specValueOne) { |
|||
displayName += sku.specValueOne; |
|||
} |
|||
if (sku.specValueTwo) { |
|||
displayName += ` / ${sku.specValueTwo}`; |
|||
} |
|||
return displayName || "默认规格"; |
|||
}, |
|||
|
|||
// 确认规格选择 |
|||
confirmSpecification() { |
|||
if (this.selectedSkuIndex === -1) { |
|||
uni.showToast({ |
|||
title: "请选择规格", |
|||
icon: "none", |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
const selectedSku = |
|||
this.goodsInfo.specCombinations[this.selectedSkuIndex]; |
|||
|
|||
// 更新商品规格信息 |
|||
this.specifications[this.currentSpecIndex].selectedSpec = |
|||
this.getSkuDisplayName(selectedSku); |
|||
this.specifications[this.currentSpecIndex].selectedSkuIndex = |
|||
this.selectedSkuIndex; |
|||
this.specifications[this.currentSpecIndex].selectedSku = selectedSku; |
|||
|
|||
this.closeSpecificationPopup(); |
|||
|
|||
uni.showToast({ |
|||
title: "规格选择成功", |
|||
icon: "success", |
|||
}); |
|||
}, |
|||
|
|||
// 提交订单 |
|||
async submitOrder() { |
|||
// 验证必填项 |
|||
if (this.needReservation && !this.selectedDate) { |
|||
uni.showToast({ |
|||
title: "请选择预约日期", |
|||
icon: "none", |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
// 验证是否已选择所有规格 |
|||
const unselectedSpecs = this.specifications.filter( |
|||
(spec) => !spec.selectedSpec |
|||
); |
|||
if (unselectedSpecs.length > 0) { |
|||
uni.showToast({ |
|||
title: "请选择所有规格", |
|||
icon: "none", |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
// 构建订单数据 |
|||
const orderData = { |
|||
goodsId: this.goodsInfo.goodsId, |
|||
bookDeliveryTime: this.selectedDate, |
|||
remark: this.note, |
|||
childId: this.orderChildId, |
|||
orderExchangeDetailBoList: this.specifications |
|||
.filter((spec) => spec.selectedSpec) |
|||
.map((item) => { |
|||
return { |
|||
specCombinationId: item.selectedSku.specCombinationId, |
|||
specValueOne: item.selectedSku.specValueOne, |
|||
specValueTwo: item.selectedSku.specValueTwo, |
|||
orderChildId: this.orderChildId, |
|||
quantity: 1, |
|||
}; |
|||
}), |
|||
}; |
|||
|
|||
console.log("订单数据:", orderData); |
|||
|
|||
// 直接跳转到核销码展示页面,传递订单信息 |
|||
uni.redirectTo({ |
|||
url: `/subPackages/orderQy/writeOffCode?orderId=123456&goodsInfo=${encodeURIComponent( |
|||
JSON.stringify(this.goodsInfo) |
|||
)}&specifications=${encodeURIComponent( |
|||
JSON.stringify( |
|||
this.specifications.filter((spec) => spec.selectedSpec) |
|||
) |
|||
)}&selectedDate=${this.selectedDate}`, |
|||
}); |
|||
}, |
|||
}, |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
// 主题色彩变量 |
|||
$primary-color: #667eea; |
|||
$secondary-color: #f8f9fa; |
|||
$text-primary: #2d3748; |
|||
$text-secondary: #718096; |
|||
$text-muted: #a0aec0; |
|||
$border-color: #e2e8f0; |
|||
$success-color: #48bb78; |
|||
$warning-color: #ed8936; |
|||
$danger-color: #f56565; |
|||
$bg-light: #f7fafc; |
|||
|
|||
.confirm-container { |
|||
height: 100vh; |
|||
background-color: $bg-light; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.content-scroll { |
|||
flex: 1; |
|||
padding: 20rpx; |
|||
width: 710rpx; |
|||
} |
|||
|
|||
// 通用section样式 |
|||
.section-header, |
|||
.section-row { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 24rpx 30rpx; |
|||
background-color: #ffffff; |
|||
border-radius: 16rpx; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.section-title { |
|||
font-size: 28rpx; |
|||
color: $text-primary; |
|||
font-weight: 600; |
|||
margin-bottom: 16rpx; |
|||
|
|||
.required { |
|||
color: $danger-color; |
|||
margin-left: 4rpx; |
|||
} |
|||
} |
|||
|
|||
// 预约日期 |
|||
.date-section { |
|||
margin-bottom: 24rpx; |
|||
} |
|||
|
|||
.reservation-desc { |
|||
margin-bottom: 16rpx; |
|||
padding: 0 30rpx; |
|||
} |
|||
|
|||
.desc-text { |
|||
font-size: 24rpx; |
|||
color: $text-secondary; |
|||
line-height: 1.5; |
|||
} |
|||
|
|||
.date-picker-container { |
|||
background-color: #ffffff; |
|||
border-radius: 16rpx; |
|||
padding: 24rpx 30rpx; |
|||
} |
|||
|
|||
.date-input { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
} |
|||
|
|||
.date-text { |
|||
font-size: 28rpx; |
|||
color: $text-primary; |
|||
|
|||
&.placeholder { |
|||
color: $text-muted; |
|||
} |
|||
} |
|||
|
|||
// 商品信息 |
|||
.goods-section { |
|||
margin-bottom: 24rpx; |
|||
} |
|||
|
|||
.goods-card { |
|||
display: flex; |
|||
background-color: #ffffff; |
|||
border-radius: 16rpx; |
|||
padding: 24rpx; |
|||
gap: 20rpx; |
|||
} |
|||
|
|||
.goods-image { |
|||
width: 120rpx; |
|||
height: 120rpx; |
|||
border-radius: 12rpx; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
.goods-info { |
|||
flex: 1; |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 12rpx; |
|||
} |
|||
|
|||
.goods-name { |
|||
font-size: 26rpx; |
|||
color: $text-primary; |
|||
font-weight: 600; |
|||
line-height: 1.4; |
|||
} |
|||
|
|||
// 备注 |
|||
.note-section { |
|||
margin-bottom: 24rpx; |
|||
|
|||
.section-row { |
|||
align-items: flex-start; |
|||
} |
|||
} |
|||
|
|||
.section-label { |
|||
font-size: 28rpx; |
|||
color: $text-primary; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.note-input { |
|||
flex: 1; |
|||
margin-left: 40rpx; |
|||
|
|||
input { |
|||
font-size: 26rpx; |
|||
color: $text-primary; |
|||
text-align: right; |
|||
width: 100%; |
|||
} |
|||
|
|||
.placeholder { |
|||
color: $text-muted; |
|||
} |
|||
} |
|||
|
|||
// 费用明细 |
|||
.cost-section { |
|||
background-color: #ffffff; |
|||
border-radius: 16rpx; |
|||
padding: 24rpx 30rpx; |
|||
margin-bottom: 24rpx; |
|||
} |
|||
|
|||
.cost-item { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 16rpx 0; |
|||
|
|||
&:not(:last-child) { |
|||
border-bottom: 1px solid $border-color; |
|||
} |
|||
} |
|||
|
|||
.cost-label { |
|||
font-size: 26rpx; |
|||
color: $text-primary; |
|||
} |
|||
|
|||
.cost-value { |
|||
font-size: 26rpx; |
|||
color: $text-primary; |
|||
|
|||
&.coupon { |
|||
color: $text-muted; |
|||
} |
|||
} |
|||
|
|||
// 底部提交区域 |
|||
.submit-section { |
|||
flex-shrink: 0; // 防止被压缩 |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 24rpx 30rpx; |
|||
background-color: #ffffff; |
|||
border-top: 1px solid $border-color; |
|||
box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.04); |
|||
|
|||
// 添加安全区域适配 |
|||
padding-bottom: calc(24rpx + env(safe-area-inset-bottom)); |
|||
} |
|||
|
|||
.submit-section-content { |
|||
padding-bottom: calc(24rpx + env(safe-area-inset-bottom)); |
|||
} |
|||
|
|||
.submit-btn { |
|||
width: 100%; |
|||
background-color: $text-primary; |
|||
color: #ffffff; |
|||
border: none; |
|||
border-radius: 24rpx; |
|||
padding: 16rpx; |
|||
font-size: 28rpx; |
|||
font-weight: 600; |
|||
transition: all 0.3s ease; |
|||
|
|||
&:active { |
|||
transform: scale(0.95); |
|||
background-color: rgba(45, 55, 72, 0.8); |
|||
} |
|||
} |
|||
|
|||
// SKU选择弹窗样式 |
|||
.specification-popup { |
|||
background: white; |
|||
border-radius: 24rpx 24rpx 0 0; |
|||
max-height: 80vh; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.popup-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 32rpx 30rpx 24rpx; |
|||
border-bottom: 1rpx solid $border-color; |
|||
|
|||
.popup-title { |
|||
font-size: 32rpx; |
|||
font-weight: 600; |
|||
color: $text-primary; |
|||
} |
|||
|
|||
.popup-close { |
|||
font-size: 40rpx; |
|||
color: $text-muted; |
|||
line-height: 1; |
|||
padding: 8rpx; |
|||
cursor: pointer; |
|||
transition: color 0.3s; |
|||
|
|||
&:active { |
|||
color: $text-secondary; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.popup-content { |
|||
padding: 30rpx; |
|||
max-height: 60vh; |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
// 商品预览 |
|||
.goods-preview { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 20rpx; |
|||
padding: 24rpx; |
|||
background: $bg-light; |
|||
border-radius: 16rpx; |
|||
margin-bottom: 30rpx; |
|||
|
|||
.preview-image { |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
border-radius: 12rpx; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
.preview-info { |
|||
flex: 1; |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 8rpx; |
|||
} |
|||
|
|||
.preview-name { |
|||
font-size: 26rpx; |
|||
color: $text-primary; |
|||
font-weight: 500; |
|||
line-height: 1.3; |
|||
} |
|||
} |
|||
|
|||
// SKU选择区域 |
|||
.sku-section { |
|||
margin-bottom: 30rpx; |
|||
|
|||
.sku-title { |
|||
font-size: 28rpx; |
|||
color: $text-primary; |
|||
font-weight: 600; |
|||
margin-bottom: 20rpx; |
|||
display: block; |
|||
} |
|||
|
|||
.sku-options { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 16rpx; |
|||
} |
|||
|
|||
.sku-option { |
|||
padding: 20rpx; |
|||
border: 2rpx solid $border-color; |
|||
border-radius: 12rpx; |
|||
background: white; |
|||
transition: all 0.3s ease; |
|||
cursor: pointer; |
|||
|
|||
&:active { |
|||
transform: scale(0.98); |
|||
} |
|||
|
|||
&.active { |
|||
border-color: $primary-color; |
|||
background: rgba(102, 126, 234, 0.05); |
|||
} |
|||
|
|||
.sku-option-content { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 12rpx; |
|||
} |
|||
|
|||
.sku-option-info { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
} |
|||
|
|||
.sku-option-name { |
|||
font-size: 28rpx; |
|||
color: $text-primary; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
.sku-option-price { |
|||
font-size: 28rpx; |
|||
color: $danger-color; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
.sku-option-details { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
font-size: 24rpx; |
|||
color: $text-secondary; |
|||
} |
|||
|
|||
.sku-option-quantity { |
|||
color: $success-color; |
|||
} |
|||
|
|||
.sku-option-code { |
|||
color: $text-muted; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 确认按钮 |
|||
.popup-actions { |
|||
padding: 24rpx 30rpx 40rpx; |
|||
border-top: 1rpx solid $border-color; |
|||
background: white; |
|||
|
|||
.confirm-spec-btn { |
|||
width: 100%; |
|||
height: 88rpx; |
|||
background: $primary-color; |
|||
color: white; |
|||
border: none; |
|||
border-radius: 44rpx; |
|||
font-size: 32rpx; |
|||
font-weight: 600; |
|||
transition: all 0.3s ease; |
|||
|
|||
&:active { |
|||
transform: scale(0.98); |
|||
background: rgba(102, 126, 234, 0.8); |
|||
} |
|||
|
|||
&:disabled { |
|||
background: $text-muted; |
|||
cursor: not-allowed; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 商品卡片中的规格信息样式 |
|||
.specifications-list { |
|||
margin: 12rpx 0; |
|||
} |
|||
|
|||
.spec-item { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
padding: 16rpx 0; |
|||
border-bottom: 1rpx solid #f0f0f0; |
|||
|
|||
&:last-child { |
|||
border-bottom: none; |
|||
} |
|||
} |
|||
|
|||
.spec-content { |
|||
flex: 1; |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 8rpx; |
|||
} |
|||
|
|||
.spec-label { |
|||
font-size: 22rpx; |
|||
color: $text-secondary; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.spec-text { |
|||
font-size: 24rpx; |
|||
color: $text-primary; |
|||
font-weight: 500; |
|||
|
|||
&.placeholder { |
|||
color: $text-muted; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,487 @@ |
|||
<template> |
|||
<view class="write-off-container"> |
|||
<scroll-view class="content-scroll" scroll-y> |
|||
<!-- 核销码展示区域 --> |
|||
<view class="code-section"> |
|||
<view class="section-title"> |
|||
<text>核销码</text> |
|||
</view> |
|||
<view class="code-card"> |
|||
<!-- 二维码 --> |
|||
<view class="qr-code-container"> |
|||
<canvas canvas-id="qrcode" class="qr-code"></canvas> |
|||
</view> |
|||
<!-- 兑换码 --> |
|||
<view class="exchange-code"> |
|||
<text class="code-label">兑换码</text> |
|||
<text class="code-value">{{ exchangeCode }}</text> |
|||
<view class="copy-btn" @click="copyCode"> |
|||
<uni-icons type="copy" size="16" color="#667eea" /> |
|||
<text>复制</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 商品信息 --> |
|||
<view class="goods-section"> |
|||
<view class="section-title"> |
|||
<text>商品信息</text> |
|||
</view> |
|||
<view class="goods-card"> |
|||
<image class="goods-image" :src="goodsInfo.image" mode="aspectFill" /> |
|||
<view class="goods-info"> |
|||
<text class="goods-name">{{ goodsInfo.goodsName || "--" }}</text> |
|||
<view class="specs-info"> |
|||
<view |
|||
class="spec-item" |
|||
v-for="(spec, index) in selectedSpecs" |
|||
:key="index" |
|||
> |
|||
<text class="spec-label">第{{ index + 1 }}份规格:</text> |
|||
<text class="spec-value">{{ spec.selectedSpec }}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 预约信息 --> |
|||
<view class="reservation-section" v-if="selectedDate"> |
|||
<view class="section-title"> |
|||
<text>预约信息</text> |
|||
</view> |
|||
<view class="reservation-card"> |
|||
<view class="reservation-item"> |
|||
<text class="reservation-label">预约日期</text> |
|||
<text class="reservation-value">{{ selectedDate }}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 使用说明 --> |
|||
<view class="instruction-section"> |
|||
<view class="section-title"> |
|||
<text>使用说明</text> |
|||
</view> |
|||
<view class="instruction-card"> |
|||
<view class="instruction-item"> |
|||
<text class="instruction-text">1. 请在预约时间内到店核销</text> |
|||
</view> |
|||
<view class="instruction-item"> |
|||
<text class="instruction-text" |
|||
>2. 出示此页面或提供兑换码给工作人员</text |
|||
> |
|||
</view> |
|||
<view class="instruction-item"> |
|||
<text class="instruction-text">3. 核销后商品不可退换</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="bottom-space"></view> |
|||
</scroll-view> |
|||
|
|||
<!-- 底部操作区域 --> |
|||
<view class="bottom-actions"> |
|||
<button class="cancel-btn" @click="cancelReservation">取消预约</button> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import QRCode from "@/static/js/weapp-qrcode.js"; |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
orderId: "WO202412250001", |
|||
exchangeCode: "EX8A9B2C3D", |
|||
goodsInfo: { |
|||
name: "精品咖啡体验券", |
|||
image: "https://via.placeholder.com/300x200/4A90E2/FFFFFF?text=Coffee", |
|||
description: "享受一杯精心调制的手冲咖啡,感受咖啡豆的独特香气", |
|||
}, |
|||
selectedSpecs: { |
|||
size: "大杯", |
|||
type: "美式咖啡", |
|||
temperature: "热饮", |
|||
}, |
|||
reservationDate: "2024-12-28 14:30", |
|||
}; |
|||
}, |
|||
|
|||
onLoad(options) { |
|||
// 接收页面参数 |
|||
if (options.orderId) { |
|||
this.orderId = options.orderId; |
|||
this.generateExchangeCode(); |
|||
} |
|||
if (options.goodsInfo) { |
|||
this.goodsInfo = JSON.parse(decodeURIComponent(options.goodsInfo)); |
|||
} |
|||
if (options.specifications) { |
|||
this.selectedSpecs = JSON.parse( |
|||
decodeURIComponent(options.specifications) |
|||
); |
|||
} |
|||
if (options.selectedDate) { |
|||
this.selectedDate = options.selectedDate; |
|||
} |
|||
}, |
|||
|
|||
onReady() { |
|||
// 生成二维码 |
|||
this.generateQRCode(); |
|||
}, |
|||
|
|||
methods: { |
|||
// 生成兑换码 |
|||
generateExchangeCode() { |
|||
// 生成8位随机兑换码 |
|||
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; |
|||
let code = ""; |
|||
for (let i = 0; i < 8; i++) { |
|||
code += chars.charAt(Math.floor(Math.random() * chars.length)); |
|||
} |
|||
this.exchangeCode = code; |
|||
}, |
|||
|
|||
// 生成二维码 |
|||
generateQRCode() { |
|||
const qrData = JSON.stringify({ |
|||
orderId: this.orderId, |
|||
exchangeCode: this.exchangeCode, |
|||
type: "writeOff", |
|||
}); |
|||
|
|||
// 使用weapp-qrcode生成二维码 |
|||
QRCode({ |
|||
canvasId: "qrcode", |
|||
text: qrData, |
|||
width: 200, |
|||
height: 200, |
|||
colorDark: "#000000", |
|||
colorLight: "#ffffff", |
|||
correctLevel: QRCode.CorrectLevel.M, |
|||
component: this, |
|||
}); |
|||
}, |
|||
|
|||
// 复制兑换码 |
|||
copyCode() { |
|||
uni.setClipboardData({ |
|||
data: this.exchangeCode, |
|||
success: () => { |
|||
uni.showToast({ |
|||
title: "兑换码已复制", |
|||
icon: "success", |
|||
}); |
|||
}, |
|||
}); |
|||
}, |
|||
|
|||
// 取消预约 |
|||
cancelReservation() { |
|||
uni.showModal({ |
|||
title: "确认取消", |
|||
content: "确定要取消此次预约吗?取消后将无法恢复。", |
|||
success: (res) => { |
|||
if (res.confirm) { |
|||
this.performCancelReservation(); |
|||
} |
|||
}, |
|||
}); |
|||
}, |
|||
|
|||
// 执行取消预约 |
|||
performCancelReservation() { |
|||
uni.showLoading({ |
|||
title: "取消中...", |
|||
}); |
|||
|
|||
this.Post( |
|||
{ |
|||
orderId: this.orderId, |
|||
}, |
|||
"/framework/order/cancel", |
|||
"DES" |
|||
) |
|||
.then((res) => { |
|||
uni.hideLoading(); |
|||
if (res.code == 200) { |
|||
uni.showToast({ |
|||
title: "取消成功", |
|||
icon: "success", |
|||
}); |
|||
setTimeout(() => { |
|||
uni.redirectTo({ |
|||
url: "/subPackages/orderQy/list", |
|||
}); |
|||
}, 1500); |
|||
} else { |
|||
uni.showToast({ |
|||
title: res.msg || "取消失败", |
|||
icon: "none", |
|||
}); |
|||
} |
|||
}) |
|||
.catch(() => { |
|||
uni.hideLoading(); |
|||
uni.showToast({ |
|||
title: "网络错误", |
|||
icon: "none", |
|||
}); |
|||
}); |
|||
}, |
|||
}, |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
// 主题色彩变量 |
|||
$primary-color: #667eea; |
|||
$secondary-color: #f8f9fa; |
|||
$text-primary: #2d3748; |
|||
$text-secondary: #718096; |
|||
$text-muted: #a0aec0; |
|||
$border-color: #e2e8f0; |
|||
$success-color: #48bb78; |
|||
$warning-color: #ed8936; |
|||
$danger-color: #f56565; |
|||
$bg-light: #f7fafc; |
|||
|
|||
.write-off-container { |
|||
height: 100vh; |
|||
background-color: $bg-light; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.content-scroll { |
|||
flex: 1; |
|||
padding: 20rpx; |
|||
} |
|||
|
|||
.section-title { |
|||
font-size: 28rpx; |
|||
color: $text-primary; |
|||
font-weight: 600; |
|||
margin-bottom: 16rpx; |
|||
} |
|||
|
|||
// 核销码区域 |
|||
.code-section { |
|||
margin-bottom: 24rpx; |
|||
} |
|||
|
|||
.code-card { |
|||
background-color: #ffffff; |
|||
border-radius: 16rpx; |
|||
padding: 40rpx; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
gap: 30rpx; |
|||
} |
|||
|
|||
.qr-code-container { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.qr-code { |
|||
width: 200rpx; |
|||
height: 200rpx; |
|||
border: 2rpx solid $border-color; |
|||
border-radius: 8rpx; |
|||
} |
|||
|
|||
.exchange-code { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
gap: 12rpx; |
|||
width: 100%; |
|||
} |
|||
|
|||
.code-label { |
|||
font-size: 24rpx; |
|||
color: $text-secondary; |
|||
} |
|||
|
|||
.code-value { |
|||
font-size: 36rpx; |
|||
color: $text-primary; |
|||
font-weight: 600; |
|||
font-family: "SF Mono", "Monaco", "Cascadia Code", monospace; |
|||
letter-spacing: 4rpx; |
|||
} |
|||
|
|||
.copy-btn { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 8rpx; |
|||
padding: 12rpx 24rpx; |
|||
background-color: rgba(102, 126, 234, 0.1); |
|||
border-radius: 20rpx; |
|||
transition: all 0.3s ease; |
|||
|
|||
&:active { |
|||
background-color: rgba(102, 126, 234, 0.2); |
|||
transform: scale(0.95); |
|||
} |
|||
|
|||
text { |
|||
font-size: 24rpx; |
|||
color: $primary-color; |
|||
font-weight: 500; |
|||
} |
|||
} |
|||
|
|||
// 商品信息 |
|||
.goods-section { |
|||
margin-bottom: 24rpx; |
|||
} |
|||
|
|||
.goods-card { |
|||
display: flex; |
|||
background-color: #ffffff; |
|||
border-radius: 16rpx; |
|||
padding: 24rpx; |
|||
gap: 20rpx; |
|||
} |
|||
|
|||
.goods-image { |
|||
width: 120rpx; |
|||
height: 120rpx; |
|||
border-radius: 12rpx; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
.goods-info { |
|||
flex: 1; |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 12rpx; |
|||
} |
|||
|
|||
.goods-name { |
|||
font-size: 26rpx; |
|||
color: $text-primary; |
|||
font-weight: 600; |
|||
line-height: 1.4; |
|||
} |
|||
|
|||
.specs-info { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 8rpx; |
|||
} |
|||
|
|||
.spec-item { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 8rpx; |
|||
} |
|||
|
|||
.spec-label { |
|||
font-size: 24rpx; |
|||
color: $text-secondary; |
|||
} |
|||
|
|||
.spec-value { |
|||
font-size: 24rpx; |
|||
color: $text-primary; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
// 预约信息 |
|||
.reservation-section { |
|||
margin-bottom: 24rpx; |
|||
} |
|||
|
|||
.reservation-card { |
|||
background-color: #ffffff; |
|||
border-radius: 16rpx; |
|||
padding: 24rpx; |
|||
} |
|||
|
|||
.reservation-item { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
} |
|||
|
|||
.reservation-label { |
|||
font-size: 26rpx; |
|||
color: $text-secondary; |
|||
} |
|||
|
|||
.reservation-value { |
|||
font-size: 26rpx; |
|||
color: $text-primary; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
// 使用说明 |
|||
.instruction-section { |
|||
margin-bottom: 24rpx; |
|||
} |
|||
|
|||
.instruction-card { |
|||
background-color: #ffffff; |
|||
border-radius: 16rpx; |
|||
padding: 24rpx; |
|||
} |
|||
|
|||
.instruction-item { |
|||
padding: 12rpx 0; |
|||
border-bottom: 1rpx solid $border-color; |
|||
|
|||
&:last-child { |
|||
border-bottom: none; |
|||
} |
|||
} |
|||
|
|||
.instruction-text { |
|||
font-size: 24rpx; |
|||
color: $text-secondary; |
|||
line-height: 1.5; |
|||
} |
|||
|
|||
.bottom-space { |
|||
height: 120rpx; |
|||
} |
|||
|
|||
// 底部操作区域 |
|||
.bottom-actions { |
|||
position: fixed; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
padding: 24rpx; |
|||
background-color: #ffffff; |
|||
border-top: 1rpx solid $border-color; |
|||
box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.04); |
|||
padding-bottom: calc(24rpx + env(safe-area-inset-bottom)); |
|||
z-index: 100; |
|||
.cancel-btn { |
|||
width: 100%; |
|||
background-color: $danger-color; |
|||
color: #ffffff; |
|||
border: none; |
|||
border-radius: 24rpx; |
|||
padding: 16rpx; |
|||
font-size: 28rpx; |
|||
font-weight: 600; |
|||
transition: all 0.3s ease; |
|||
|
|||
&:active { |
|||
transform: scale(0.95); |
|||
background-color: rgba(245, 101, 101, 0.8); |
|||
} |
|||
} |
|||
} |
|||
</style> |
Loading…
Reference in new issue