586 lines
15 KiB
Vue
586 lines
15 KiB
Vue
<template>
|
|
<view class="order-submit">
|
|
<!-- 自定义导航栏 -->
|
|
<view class="navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
|
<view class="navbar__content" :style="{ height: navBarHeight + 'px' }">
|
|
<view class="navbar__back" @click="goBack">
|
|
<image src="/static/ic_back.png" class="navbar__back-icon" mode="aspectFit" />
|
|
</view>
|
|
<text class="navbar__title">订单提交</text>
|
|
<view class="navbar__placeholder"></view>
|
|
</view>
|
|
</view>
|
|
<view :style="{ height: (statusBarHeight + navBarHeight) + 'px' }"></view>
|
|
|
|
<!-- 公司地址 -->
|
|
<view class="address-section">
|
|
<view class="address-section__header">
|
|
<image src="/static/ic_address.png" class="address-section__icon" mode="aspectFit" />
|
|
<text class="address-section__title">公司地址</text>
|
|
</view>
|
|
<view class="address-section__content">
|
|
<text class="address-section__name">{{ companyInfo.name }}</text>
|
|
<text class="address-section__detail">{{ companyInfo.address }}</text>
|
|
<text class="address-section__phone">{{ companyInfo.phone }}</text>
|
|
<text class="address-section__contact">{{ companyInfo.contact }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 您的信息 -->
|
|
<view class="info-section">
|
|
<view class="info-section__header">
|
|
<image src="/static/ic_tip.png" class="info-section__icon" mode="aspectFit" />
|
|
<text class="info-section__title">您的信息</text>
|
|
<text class="info-section__subtitle">请留下您的联系方式</text>
|
|
</view>
|
|
|
|
<!-- 地址选择 -->
|
|
<view v-if="addressList.length > 0" class="address-picker">
|
|
<view class="address-picker__label">选择已有地址:</view>
|
|
<view class="address-picker__list">
|
|
<view
|
|
v-for="addr in addressList" :key="addr.id"
|
|
class="address-picker__item"
|
|
:class="{ 'address-picker__item--active': selectedAddrId === addr.id }"
|
|
@click="selectAddress(addr)"
|
|
>
|
|
<view class="address-picker__item-top">
|
|
<text class="address-picker__name">{{ addr.name }}</text>
|
|
<text class="address-picker__phone">{{ addr.phone }}</text>
|
|
<text v-if="addr.isDefault" class="address-picker__default">默认</text>
|
|
</view>
|
|
<text class="address-picker__detail">{{ addr.province }}{{ addr.city }}{{ addr.district }}{{ addr.detail }}</text>
|
|
</view>
|
|
</view>
|
|
<view class="address-picker__manual" @click="clearSelected">
|
|
<text>{{ selectedAddrId ? '手动填写新地址' : '当前为手动填写' }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="form-item">
|
|
<text class="form-item__label required">姓名</text>
|
|
<input class="form-item__input" v-model="receiverName" placeholder="请输入真实姓名" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-item__label required">电话</text>
|
|
<input class="form-item__input" v-model="receiverPhone" type="number" placeholder="请输入电话" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-item__label required">地址</text>
|
|
<input class="form-item__input" v-model="receiverAddress" placeholder="请输入收货地址" />
|
|
</view>
|
|
<view class="form-item">
|
|
<text class="form-item__label">备注</text>
|
|
<input class="form-item__input" v-model="remark" placeholder="请输入备注信息" />
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 发货时间 -->
|
|
<view class="delivery-section">
|
|
<view class="delivery-section__header">
|
|
<image src="/static/ic_time.png" class="delivery-section__icon" mode="aspectFit" />
|
|
<text class="delivery-section__title">发货时间</text>
|
|
</view>
|
|
<text class="delivery-section__content">{{ deliveryTimeText }}</text>
|
|
</view>
|
|
|
|
<!-- 商品列表 -->
|
|
<view class="product-section">
|
|
<view class="product-item" v-for="item in orderItems" :key="item.id">
|
|
<image class="product-item__img" :src="fullUrl(item.product.thumb || item.product.bannerImages?.[0] || '')" mode="aspectFill" />
|
|
<view class="product-item__info">
|
|
<text class="product-item__name">{{ item.product.name }}</text>
|
|
<view class="product-item__specs">
|
|
<text class="product-item__spec">款号:{{ item.specData.modelName || 'B2022' }}</text>
|
|
<text class="product-item__spec">商品型号:{{ item.specData.fineness || '2606' }}</text>
|
|
<text class="product-item__spec">成色:{{ item.specData.fineness || '30' }}</text>
|
|
<text class="product-item__spec">主石:{{ item.specData.mainStone || '13.00#' }}</text>
|
|
<text class="product-item__spec" v-if="item.specData.subStone">副石:{{ item.specData.subStone }}</text>
|
|
<text class="product-item__spec">手寸:{{ item.specData.ringSize || '13.00#' }}</text>
|
|
<text class="product-item__spec">金料总重:{{ item.specData.goldTotalWeight || '236' }}</text>
|
|
</view>
|
|
<text class="product-item__price">¥{{ item.specData.totalPrice }}元</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 底部固定区域:协议勾选 + 提交栏 -->
|
|
<view class="bottom-fixed">
|
|
<view class="agreement" @click="agreed = !agreed">
|
|
<image :src="agreed ? '/static/ic_check_s.png' : '/static/ic_check.png'" class="agreement__checkbox" mode="aspectFit" />
|
|
<text class="agreement__text">因珠宝产品属于贵重物品,一旦出货,产品无质量问题不支持退换!</text>
|
|
</view>
|
|
<view class="submit-bar">
|
|
<view class="submit-bar__left">
|
|
<text class="submit-bar__label">合计:</text>
|
|
<text class="submit-bar__price">¥{{ totalPrice }}</text>
|
|
</view>
|
|
<view
|
|
class="submit-bar__btn"
|
|
:class="{ 'submit-bar__btn--disabled': !canSubmit }"
|
|
@click="handleSubmit"
|
|
>
|
|
开始下单
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, onMounted } from 'vue'
|
|
import { useCartStore } from '../../store/cart'
|
|
import { createOrder } from '../../api/order'
|
|
import { getAddressList } from '../../api/user'
|
|
import { BASE_URL, get } from '../../utils/request'
|
|
import type { CartItem, Address } from '../../types'
|
|
|
|
const cartStore = useCartStore()
|
|
|
|
const statusBarHeight = ref(0)
|
|
const navBarHeight = ref(44)
|
|
const receiverName = ref('')
|
|
const receiverPhone = ref('')
|
|
const receiverAddress = ref('')
|
|
const remark = ref('')
|
|
const agreed = ref(false)
|
|
const submitting = ref(false)
|
|
const companyInfo = ref({ name: '', address: '', phone: '', contact: '' })
|
|
const deliveryTimeText = ref('')
|
|
const addressList = ref<Address[]>([])
|
|
const selectedAddrId = ref<number | null>(null)
|
|
|
|
function selectAddress(addr: Address) {
|
|
selectedAddrId.value = addr.id
|
|
receiverName.value = addr.name
|
|
receiverPhone.value = addr.phone
|
|
receiverAddress.value = `${addr.province}${addr.city}${addr.district}${addr.detail}`
|
|
}
|
|
|
|
function clearSelected() {
|
|
selectedAddrId.value = null
|
|
receiverName.value = ''
|
|
receiverPhone.value = ''
|
|
receiverAddress.value = ''
|
|
}
|
|
|
|
function fullUrl(path: string): string {
|
|
if (!path) return ''
|
|
if (path.startsWith('http')) return path
|
|
return BASE_URL + path
|
|
}
|
|
|
|
function goBack() {
|
|
uni.navigateBack()
|
|
}
|
|
|
|
/** 从购物车获取已勾选的商品作为订单项 */
|
|
const orderItems = computed<CartItem[]>(() => cartStore.checkedItems)
|
|
|
|
const totalPrice = computed(() =>
|
|
orderItems.value.reduce((sum, item) => sum + item.specData.totalPrice * item.quantity, 0),
|
|
)
|
|
|
|
const canSubmit = computed(() => agreed.value && !submitting.value)
|
|
|
|
async function handleSubmit() {
|
|
if (!canSubmit.value) return
|
|
|
|
if (!receiverName.value.trim()) {
|
|
uni.showToast({ title: '请输入收货人姓名', icon: 'none' })
|
|
return
|
|
}
|
|
if (!receiverPhone.value.trim()) {
|
|
uni.showToast({ title: '请输入联系电话', icon: 'none' })
|
|
return
|
|
}
|
|
if (!receiverAddress.value.trim()) {
|
|
uni.showToast({ title: '请输入收货地址', icon: 'none' })
|
|
return
|
|
}
|
|
|
|
submitting.value = true
|
|
try {
|
|
const order: any = await createOrder({
|
|
items: orderItems.value.map((item) => ({
|
|
productId: item.productId,
|
|
specDataId: item.specDataId,
|
|
quantity: item.quantity,
|
|
unitPrice: item.specData.totalPrice,
|
|
})),
|
|
receiverName: receiverName.value.trim(),
|
|
receiverPhone: receiverPhone.value.trim(),
|
|
receiverAddress: receiverAddress.value.trim(),
|
|
})
|
|
|
|
// 先保存要删除的购物车项ID
|
|
const itemIdsToRemove = orderItems.value.map((item) => item.id)
|
|
|
|
uni.redirectTo({ url: `/pages/order/detail?id=${order.orderId}` })
|
|
|
|
// 跳转后再清除购物车
|
|
itemIdsToRemove.forEach((id) => cartStore.removeFromCart(id))
|
|
} catch {
|
|
uni.showToast({ title: '提交订单失败,请重试', icon: 'none' })
|
|
} finally {
|
|
submitting.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
const sysInfo = uni.getSystemInfoSync()
|
|
statusBarHeight.value = sysInfo.statusBarHeight || 0
|
|
const menuBtn = uni.getMenuButtonBoundingClientRect()
|
|
navBarHeight.value = menuBtn.height + (menuBtn.top - (sysInfo.statusBarHeight || 0)) * 2
|
|
|
|
// 获取公司地址配置
|
|
get('/api/config/company_address').then((data: any) => {
|
|
if (data && typeof data === 'object') companyInfo.value = { ...companyInfo.value, ...data }
|
|
}).catch(() => {})
|
|
|
|
// 获取发货时间配置
|
|
get('/api/config/delivery_time').then((data: any) => {
|
|
if (data) deliveryTimeText.value = data
|
|
}).catch(() => {})
|
|
|
|
// 获取用户地址列表
|
|
getAddressList().then((list: Address[]) => {
|
|
addressList.value = list || []
|
|
// 自动选择默认地址
|
|
const defaultAddr = list.find(a => a.isDefault)
|
|
if (defaultAddr) selectAddress(defaultAddr)
|
|
}).catch(() => {})
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.order-submit {
|
|
min-height: 100vh;
|
|
background: #f5f5f5;
|
|
padding-bottom: 220rpx;
|
|
}
|
|
|
|
/* 导航栏 */
|
|
.navbar {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
z-index: 100;
|
|
background: linear-gradient(135deg, #FFCFDE 0%, #FFA6C4 100%);
|
|
}
|
|
.navbar__content {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 0 24rpx;
|
|
}
|
|
.navbar__back {
|
|
padding: 10rpx;
|
|
}
|
|
.navbar__back-icon {
|
|
width: 40rpx;
|
|
height: 40rpx;
|
|
}
|
|
.navbar__title {
|
|
font-size: 34rpx;
|
|
font-weight: 600;
|
|
color: #000;
|
|
}
|
|
.navbar__placeholder {
|
|
width: 60rpx;
|
|
}
|
|
|
|
/* 公司地址 */
|
|
.address-section {
|
|
background: #fff;
|
|
margin: 16rpx 24rpx;
|
|
padding: 24rpx;
|
|
border-radius: 16rpx;
|
|
}
|
|
.address-section__header {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 16rpx;
|
|
}
|
|
.address-section__icon {
|
|
width: 32rpx;
|
|
height: 32rpx;
|
|
margin-right: 8rpx;
|
|
}
|
|
.address-section__title {
|
|
font-size: 28rpx;
|
|
color: #e91e63;
|
|
font-weight: 600;
|
|
}
|
|
.address-section__content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8rpx;
|
|
}
|
|
.address-section__name {
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
font-weight: 600;
|
|
}
|
|
.address-section__detail,
|
|
.address-section__phone,
|
|
.address-section__contact {
|
|
font-size: 24rpx;
|
|
color: #666;
|
|
line-height: 36rpx;
|
|
}
|
|
|
|
/* 您的信息 */
|
|
.info-section {
|
|
background: #fff;
|
|
margin: 0 24rpx 16rpx;
|
|
padding: 24rpx;
|
|
border-radius: 16rpx;
|
|
}
|
|
.info-section__header {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
.info-section__icon {
|
|
width: 32rpx;
|
|
height: 32rpx;
|
|
margin-right: 8rpx;
|
|
}
|
|
.info-section__title {
|
|
font-size: 28rpx;
|
|
color: #e91e63;
|
|
font-weight: 600;
|
|
margin-right: 12rpx;
|
|
}
|
|
.info-section__subtitle {
|
|
font-size: 24rpx;
|
|
color: #999;
|
|
}
|
|
.form-item {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 20rpx 0;
|
|
border-bottom: 1rpx solid #f0f0f0;
|
|
}
|
|
.form-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
/* 地址选择器 */
|
|
.address-picker {
|
|
margin-bottom: 16rpx;
|
|
padding-bottom: 16rpx;
|
|
border-bottom: 1rpx solid #f0f0f0;
|
|
}
|
|
.address-picker__label {
|
|
font-size: 26rpx;
|
|
color: #666;
|
|
margin-bottom: 12rpx;
|
|
}
|
|
.address-picker__list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12rpx;
|
|
}
|
|
.address-picker__item {
|
|
padding: 16rpx 20rpx;
|
|
border: 2rpx solid #eee;
|
|
border-radius: 12rpx;
|
|
background: #fafafa;
|
|
}
|
|
.address-picker__item--active {
|
|
border-color: #FF6D9B;
|
|
background: #fff5f8;
|
|
}
|
|
.address-picker__item-top {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12rpx;
|
|
margin-bottom: 6rpx;
|
|
}
|
|
.address-picker__name {
|
|
font-size: 26rpx;
|
|
color: #333;
|
|
font-weight: 600;
|
|
}
|
|
.address-picker__phone {
|
|
font-size: 24rpx;
|
|
color: #666;
|
|
}
|
|
.address-picker__default {
|
|
font-size: 20rpx;
|
|
color: #FF6D9B;
|
|
border: 1rpx solid #FF6D9B;
|
|
border-radius: 4rpx;
|
|
padding: 2rpx 8rpx;
|
|
}
|
|
.address-picker__detail {
|
|
font-size: 24rpx;
|
|
color: #999;
|
|
line-height: 36rpx;
|
|
}
|
|
.address-picker__manual {
|
|
margin-top: 12rpx;
|
|
text-align: center;
|
|
font-size: 24rpx;
|
|
color: #FF6D9B;
|
|
}
|
|
|
|
.form-item__label {
|
|
font-size: 26rpx;
|
|
color: #333;
|
|
width: 100rpx;
|
|
flex-shrink: 0;
|
|
}
|
|
.form-item__label.required::before {
|
|
content: '*';
|
|
color: #e91e63;
|
|
margin-right: 4rpx;
|
|
}
|
|
.form-item__input {
|
|
flex: 1;
|
|
font-size: 26rpx;
|
|
color: #333;
|
|
}
|
|
|
|
/* 发货时间 */
|
|
.delivery-section {
|
|
background: #fff;
|
|
margin: 0 24rpx 16rpx;
|
|
padding: 24rpx;
|
|
border-radius: 16rpx;
|
|
}
|
|
.delivery-section__header {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 12rpx;
|
|
}
|
|
.delivery-section__icon {
|
|
width: 32rpx;
|
|
height: 32rpx;
|
|
margin-right: 8rpx;
|
|
}
|
|
.delivery-section__title {
|
|
font-size: 28rpx;
|
|
color: #e91e63;
|
|
font-weight: 600;
|
|
}
|
|
.delivery-section__content {
|
|
font-size: 24rpx;
|
|
color: #e91e63;
|
|
line-height: 36rpx;
|
|
}
|
|
|
|
/* 商品列表 */
|
|
.product-section {
|
|
background: #fff;
|
|
margin: 0 24rpx 16rpx;
|
|
padding: 24rpx;
|
|
border-radius: 16rpx;
|
|
}
|
|
.product-item {
|
|
display: flex;
|
|
gap: 20rpx;
|
|
}
|
|
.product-item__img {
|
|
width: 160rpx;
|
|
height: 160rpx;
|
|
border-radius: 12rpx;
|
|
flex-shrink: 0;
|
|
background: #f5f5f5;
|
|
}
|
|
.product-item__info {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.product-item__name {
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
font-weight: 600;
|
|
margin-bottom: 8rpx;
|
|
}
|
|
.product-item__specs {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 8rpx;
|
|
margin-bottom: 12rpx;
|
|
}
|
|
.product-item__spec {
|
|
font-size: 22rpx;
|
|
color: #666;
|
|
}
|
|
.product-item__price {
|
|
font-size: 30rpx;
|
|
color: #e91e63;
|
|
font-weight: 700;
|
|
}
|
|
|
|
/* 底部固定区域 */
|
|
.bottom-fixed {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
background: #fff;
|
|
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
|
|
padding-bottom: env(safe-area-inset-bottom);
|
|
z-index: 50;
|
|
}
|
|
|
|
/* 协议 */
|
|
.agreement {
|
|
background: #fffbe6;
|
|
padding: 16rpx 24rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
border-bottom: 1rpx solid #f0f0f0;
|
|
}
|
|
.agreement__checkbox {
|
|
width: 32rpx;
|
|
height: 32rpx;
|
|
margin-right: 12rpx;
|
|
flex-shrink: 0;
|
|
}
|
|
.agreement__text {
|
|
font-size: 22rpx;
|
|
color: #333;
|
|
line-height: 34rpx;
|
|
}
|
|
|
|
/* 底部提交栏 */
|
|
.submit-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 16rpx 24rpx;
|
|
}
|
|
.submit-bar__left {
|
|
display: flex;
|
|
align-items: baseline;
|
|
}
|
|
.submit-bar__label {
|
|
font-size: 26rpx;
|
|
color: #333;
|
|
}
|
|
.submit-bar__price {
|
|
font-size: 36rpx;
|
|
color: #e91e63;
|
|
font-weight: 700;
|
|
}
|
|
.submit-bar__btn {
|
|
background: linear-gradient(135deg, #FF6D9B, #FF4081);
|
|
color: #fff;
|
|
padding: 20rpx 60rpx;
|
|
border-radius: 44rpx;
|
|
font-size: 28rpx;
|
|
font-weight: 600;
|
|
}
|
|
.submit-bar__btn--disabled {
|
|
background: #ccc;
|
|
pointer-events: none;
|
|
}
|
|
</style>
|