campus-errand/miniapp/pages/food/food-order.vue
18631081161 58b34666bd
All checks were successful
continuous-integration/drone/push Build is passing
佣金
2026-03-30 15:29:56 +08:00

436 lines
9.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="food-order-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">美食街订单</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 订单商品摘要 -->
<view class="order-summary">
<text class="section-title">订单商品</text>
<view v-for="shopGroup in cartStore.shopList" :key="shopGroup.shopInfo.id" class="shop-group">
<text class="shop-group-name">{{ shopGroup.shopInfo.name }}</text>
<view class="summary-item" v-for="item in shopGroup.items" :key="item.dish.id">
<image class="summary-photo" :src="item.dish.photo" mode="aspectFill"></image>
<text class="summary-name">{{ item.dish.name }} × {{ item.quantity }}</text>
<text class="summary-price">¥{{ (item.dish.price * item.quantity).toFixed(2) }}</text>
</view>
</view>
<view class="summary-item" v-if="packingFee > 0">
<text class="summary-name">打包费</text>
<text class="summary-price">¥{{ packingFee.toFixed(2) }}</text>
</view>
<view class="summary-total">
<text class="total-label">餐品总金额</text>
<text class="total-value">¥{{ goodsAmount }}</text>
</view>
</view>
<!-- 订单表单 -->
<view class="form-section">
<!-- 送达地点 -->
<view class="form-item">
<text class="form-label">送达地点</text>
<input class="form-input" v-model="form.deliveryLocation" placeholder="请输入送达地点" />
</view>
<!-- 备注信息 -->
<view class="form-item">
<text class="form-label">备注信息</text>
<textarea class="form-textarea" v-model="form.remark" placeholder="请输入备注信息(选填)" />
</view>
<!-- 手机号 -->
<view class="form-item">
<text class="form-label">手机号</text>
<input class="form-input" v-model="form.phone" type="number" placeholder="请输入联系手机号" maxlength="11" />
</view>
<!-- 跑腿佣金 -->
<view class="form-item">
<text class="form-label">跑腿佣金</text>
<view class="commission-input">
<text class="commission-unit">¥</text>
<input class="form-input commission-field" v-model="form.commission" type="digit" :placeholder="`最低${minCommission}元`" />
</view>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-section">
<text class="pay-amount">支付金额:¥{{ payAmount }}</text>
<button class="submit-btn" @click="onSubmit" :loading="submitting" :disabled="submitting">确定</button>
</view>
</view>
</template>
<script>
import { createOrder, getMinCommission } from '../../utils/api'
import { useCartStore } from '../../stores/cart'
export default {
data() {
return {
form: {
deliveryLocation: '',
remark: '',
phone: '',
commission: ''
},
submitting: false,
statusBarHeight: 0,
minCommission: 1.0
}
},
computed: {
cartStore() {
return useCartStore()
},
packingFee() {
return this.cartStore.packingFee
},
/** 餐品总金额(含打包费) */
goodsAmount() {
return this.cartStore.totalPriceWithFee.toFixed(2)
},
/** 支付金额 = 商品总金额 + 跑腿佣金 */
payAmount() {
const goods = this.cartStore.totalPriceWithFee
const commission = parseFloat(this.form.commission) || 0
return (goods + commission).toFixed(2)
}
},
onLoad() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
this.loadMinCommission()
// 如果购物车为空,返回上一页
if (this.cartStore.totalCount === 0) {
uni.showToast({ title: '购物车为空', icon: 'none' })
setTimeout(() => { uni.navigateBack() }, 1000)
}
},
methods: {
goBack() { uni.navigateBack() },
async loadMinCommission() {
try {
const res = await getMinCommission()
if (res?.value) this.minCommission = parseFloat(res.value) || 1.0
} catch (e) {}
},
/** 校验佣金 */
validateCommission() {
const val = this.form.commission
if (!val) {
uni.showToast({ title: '请输入跑腿佣金', icon: 'none' })
return false
}
const num = parseFloat(val)
if (isNaN(num) || num < this.minCommission) {
uni.showToast({ title: `跑腿佣金不可低于${this.minCommission}`, icon: 'none' })
return false
}
if (val.includes('.') && val.split('.')[1].length > 1) {
uni.showToast({ title: '佣金最多支持小数点后1位', icon: 'none' })
return false
}
return true
},
/** 校验表单 */
validateForm() {
if (!this.form.deliveryLocation.trim()) {
uni.showToast({ title: '请输入送达地点', icon: 'none' })
return false
}
if (!this.form.phone.trim()) {
uni.showToast({ title: '请输入手机号', icon: 'none' })
return false
}
if (!/^1\d{10}$/.test(this.form.phone.trim())) {
uni.showToast({ title: '请输入正确的11位手机号', icon: 'none' })
return false
}
return this.validateCommission()
},
/** 提交订单 */
async onSubmit() {
if (!this.validateForm()) return
this.submitting = true
try {
const commission = parseFloat(this.form.commission)
const goodsAmount = this.cartStore.totalPriceWithFee
// 构建美食街订单数据(多门店)
const foodItems = this.cartStore.getAllFoodItems()
const orderData = {
orderType: 'Food',
deliveryLocation: this.form.deliveryLocation.trim(),
remark: this.form.remark.trim(),
phone: this.form.phone.trim(),
goodsAmount: goodsAmount,
commission: commission,
totalAmount: goodsAmount + commission,
foodItems: foodItems
}
const result = await createOrder(orderData)
if (result.paymentParams) {
await this.wxPay(result.paymentParams)
}
// 下单成功,清空购物车
this.cartStore.clearCart()
uni.showToast({ title: '下单成功', icon: 'success' })
setTimeout(() => {
uni.switchTab({ url: '/pages/index/index' })
}, 1500)
} catch (e) {
// 错误已在 request 中处理
} finally {
this.submitting = false
}
},
/** 调用微信支付 */
wxPay(params) {
return new Promise((resolve, reject) => {
uni.requestPayment({
provider: 'wxpay',
timeStamp: params.timeStamp,
nonceStr: params.nonceStr,
package: params.package_,
signType: params.signType,
paySign: params.paySign,
success: resolve,
fail: (err) => {
if (err.errMsg !== 'requestPayment:fail cancel') {
uni.showToast({ title: '支付失败', icon: 'none' })
}
reject(err)
}
})
})
}
}
}
</script>
<style scoped>
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
.food-order-page {
padding: 24rpx;
padding-bottom: 200rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.order-summary {
background-color: #ffffff;
border-radius: 16rpx;
padding: 20rpx 30rpx;
margin-bottom: 20rpx;
}
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #333333;
margin-bottom: 16rpx;
display: block;
}
.shop-group {
margin-bottom: 12rpx;
}
.shop-group-name {
font-size: 28rpx;
font-weight: bold;
color: #333;
padding: 8rpx 0;
display: block;
}
.summary-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10rpx 0;
}
.summary-photo {
width: 80rpx;
height: 80rpx;
border-radius: 8rpx;
flex-shrink: 0;
margin-right: 16rpx;
}
.summary-name {
font-size: 26rpx;
color: #666666;
flex: 1;
}
.summary-price {
font-size: 26rpx;
color: #333333;
}
.summary-total {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 16rpx;
margin-top: 12rpx;
border-top: 1rpx solid #f0f0f0;
}
.total-label {
font-size: 28rpx;
color: #333333;
font-weight: bold;
}
.total-value {
font-size: 32rpx;
color: #e64340;
font-weight: bold;
}
.form-section {
background-color: #ffffff;
border-radius: 16rpx;
padding: 20rpx 30rpx;
}
.form-item {
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.form-item:last-child {
border-bottom: none;
}
.form-label {
font-size: 28rpx;
color: #333333;
margin-bottom: 12rpx;
display: block;
}
.form-input {
font-size: 28rpx;
color: #333333;
height: 72rpx;
}
.form-textarea {
font-size: 28rpx;
color: #333333;
width: 100%;
height: 160rpx;
}
.commission-input {
display: flex;
align-items: center;
}
.commission-unit {
font-size: 32rpx;
color: #e64340;
margin-right: 8rpx;
font-weight: bold;
}
.commission-field {
flex: 1;
}
.submit-section {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #ffffff;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
display: flex;
align-items: center;
z-index: 999;
justify-content: flex-end;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.pay-amount {
font-size: 32rpx;
color: #e64340;
font-weight: bold;
flex: 1;
}
.submit-btn {
width: 240rpx;
height: 80rpx;
line-height: 80rpx;
background-color: #FAD146;
color: #ffffff;
font-size: 30rpx;
border-radius: 40rpx;
border: none;
margin-right: 10rpx;
}
.submit-btn::after {
border: none;
}
.submit-btn[disabled] {
opacity: 0.6;
}
</style>