feat(order): 迁移和完善配送方式功能,支持全链路入库与展示
- 迁移配送方式选择功能从 orderConfirm 页至 user/ticket/use 页面 - orderConfirm 页面移除配送方式相关状态、UI与校验,取消配送费计算 - user/ticket/use 页面新增配送方式UI组件,支持配送费计算、楼层选择弹窗和提交校验 - 新增录入deliveryMethod、deliveryFloor、deliveryFee字段至订单模型与后端数据库 - 骑手端订单列表新增配送方式、楼层、配送费的详细展示 - 更新环境配置接口地址到正式API,修正测试及开发环境 - 用户页底部组件UI优化,新增版权icon并重构结构样式 - 使用配送方式字段校验下单逻辑,支持编辑模式配送信息回显与费用显示 - 移除orderConfirm中配送方式相关样式和组件,实现代码回滚清理
This commit is contained in:
@@ -63,10 +63,10 @@
|
|||||||
"profession": "高级开发工程师",
|
"profession": "高级开发工程师",
|
||||||
"avatarUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/avatars/02-Engineering/SeniorDeveloper/SeniorDeveloper.png",
|
"avatarUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/avatars/02-Engineering/SeniorDeveloper/SeniorDeveloper.png",
|
||||||
"promptUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/experts/02-Engineering/SeniorDeveloper/SeniorDeveloper_zh.md",
|
"promptUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/experts/02-Engineering/SeniorDeveloper/SeniorDeveloper_zh.md",
|
||||||
"usedAt": 1775756293122,
|
"usedAt": 1775972794982,
|
||||||
"industryId": "all"
|
"industryId": "all"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lastUpdated": 1775757430292
|
"lastUpdated": 1775999935033
|
||||||
}
|
}
|
||||||
@@ -69,6 +69,23 @@
|
|||||||
- 订单创建接口接收并存储这两个字段
|
- 订单创建接口接收并存储这两个字段
|
||||||
- 骑手端/后台展示配送方式和楼层信息
|
- 骑手端/后台展示配送方式和楼层信息
|
||||||
|
|
||||||
|
## 配送方式功能迁移:orderConfirm → user/ticket/use
|
||||||
|
|
||||||
|
### 原因
|
||||||
|
配送方式选择功能从购买下单页(orderConfirm)迁移到水票核销/立即送水页面(user/ticket/use)。
|
||||||
|
|
||||||
|
### 修改的文件
|
||||||
|
1. `src/shop/orderConfirm/index.tsx` - 回滚:移除配送方式状态变量、UI、校验、楼层选择弹窗、配送费计算
|
||||||
|
2. `src/shop/orderConfirm/index.scss` - 回滚:移除配送方式/楼层选择相关样式
|
||||||
|
3. `src/api/glt/gltTicketOrder/model/index.ts` - GltTicketOrder 新增 deliveryMethod、deliveryFloor、deliveryFee 字段
|
||||||
|
4. `src/user/ticket/use.tsx` - 新增:配送方式选择UI、配送费计算、楼层选择弹窗、提交校验、编辑模式回显
|
||||||
|
5. `src/user/ticket/use.scss` - 新增:配送方式/楼层选择样式
|
||||||
|
|
||||||
|
### 关键差异
|
||||||
|
- orderConfirm 页面是付费购买,配送费加到实付金额中
|
||||||
|
- use 页面是水票核销(不付费),配送费以"到付"形式展示在底部栏
|
||||||
|
- use 页面同时支持新建和编辑模式,编辑时回显配送信息
|
||||||
|
|
||||||
## 微信订阅消息配置(补充)
|
## 微信订阅消息配置(补充)
|
||||||
|
|
||||||
### 需要做的配置
|
### 需要做的配置
|
||||||
|
|||||||
25
.workbuddy/memory/2026-04-12.md
Normal file
25
.workbuddy/memory/2026-04-12.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# 2026-04-12 工作日志
|
||||||
|
|
||||||
|
## 配送方式功能完善:全链路入库+展示
|
||||||
|
|
||||||
|
### 问题
|
||||||
|
配送方式(deliveryMethod)、楼层(deliveryFloor)、配送费(deliveryFee)在小程序端用户下单页面已可选择并提交,但后端数据库表没有对应字段,数据实际没有保存。骑手端和后台管理端也没有展示这些信息。
|
||||||
|
|
||||||
|
### 修改的文件
|
||||||
|
|
||||||
|
#### 后端(/Users/gxwebsoft/JAVA/java-10584)
|
||||||
|
1. `src/main/java/com/gxwebsoft/glt/entity/GltTicketOrder.java` - 新增 deliveryMethod(String)、deliveryFloor(Integer)、deliveryFee(BigDecimal) 三个数据库字段
|
||||||
|
2. `sql/glt_ticket_order_delivery_fields.sql` - 新增 ALTER TABLE SQL,给 glt_ticket_order 表添加三个字段
|
||||||
|
|
||||||
|
#### 小程序端(/Users/gxwebsoft/VUE/template-10584)
|
||||||
|
3. `src/rider/orders/index.tsx` - 骑手送水订单页面:新增配送方式中文映射函数,订单卡片中展示配送方式、楼层、配送费
|
||||||
|
|
||||||
|
#### 后台管理端(/Users/gxwebsoft/VUE/mp-10584)
|
||||||
|
4. `src/api/glt/gltTicketOrder/model/index.ts` - GltTicketOrder 接口新增 deliveryMethod、deliveryFloor、deliveryFee 字段
|
||||||
|
5. `src/views/glt/gltTicketOrder/index.vue` - 列表页"配送信息"列中展示配送方式、楼层、配送费
|
||||||
|
6. `src/views/glt/gltTicketOrder/components/gltTicketOrderEdit.vue` - 编辑弹窗中新增配送方式只读展示
|
||||||
|
|
||||||
|
### 部署注意事项
|
||||||
|
- **必须先执行 SQL**:`java-10584/sql/glt_ticket_order_delivery_fields.sql`
|
||||||
|
- MyBatis-Plus 使用驼峰自动映射,`delivery_method` → `deliveryMethod`,无需修改 Mapper XML
|
||||||
|
- 后端 save/updateById 自动包含新字段,无需修改 Controller/Service
|
||||||
@@ -8,22 +8,22 @@ const CURRENT_ENV = 'production'
|
|||||||
export const ENV_CONFIG = {
|
export const ENV_CONFIG = {
|
||||||
// 开发环境
|
// 开发环境
|
||||||
development: {
|
development: {
|
||||||
API_BASE_URL: 'https://glt-dev-api.websoft.top/api',
|
API_BASE_URL: 'https://glt-api.websoft.top/api',
|
||||||
SERVER_API_URL: 'https://glt-dev-server.websoft.top/api',
|
SERVER_API_URL: 'https://glt-server.websoft.top/api',
|
||||||
APP_NAME: '开发环境',
|
APP_NAME: '开发环境',
|
||||||
DEBUG: 'true',
|
DEBUG: 'true',
|
||||||
},
|
},
|
||||||
// 测试环境
|
// 测试环境
|
||||||
test: {
|
test: {
|
||||||
API_BASE_URL: 'https://glt-dev-api.websoft.top/api',
|
API_BASE_URL: 'https://glt-api.websoft.top/api',
|
||||||
SERVER_API_URL: 'https://glt-dev-server.websoft.top/api',
|
SERVER_API_URL: 'https://glt-server.websoft.top/api',
|
||||||
APP_NAME: '测试环境',
|
APP_NAME: '测试环境',
|
||||||
DEBUG: 'true',
|
DEBUG: 'true',
|
||||||
},
|
},
|
||||||
// 生产环境
|
// 生产环境
|
||||||
production: {
|
production: {
|
||||||
API_BASE_URL: 'https://glt-dev-api.websoft.top/api',
|
API_BASE_URL: 'https://glt-api.websoft.top/api',
|
||||||
SERVER_API_URL: 'https://glt-dev-server.websoft.top/api',
|
SERVER_API_URL: 'https://glt-server.websoft.top/api',
|
||||||
APP_NAME: '桂乐淘',
|
APP_NAME: '桂乐淘',
|
||||||
DEBUG: 'false',
|
DEBUG: 'false',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -76,6 +76,12 @@ export interface GltTicketOrder {
|
|||||||
createTime?: string;
|
createTime?: string;
|
||||||
// 修改时间
|
// 修改时间
|
||||||
updateTime?: string;
|
updateTime?: string;
|
||||||
|
// 配送方式:elevator(电梯) / stairs(步梯) / groundFloor(一楼商铺/其他)
|
||||||
|
deliveryMethod?: string;
|
||||||
|
// 楼层(步梯+送上楼时有值,从2开始)
|
||||||
|
deliveryFloor?: number;
|
||||||
|
// 配送费(步梯+送上楼时计算:数量 × (楼层-1))
|
||||||
|
deliveryFee?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import {loginBySms} from "@/api/passport/login";
|
import {loginBySms} from "@/api/passport/login";
|
||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
|
import {View,Text} from '@tarojs/components'
|
||||||
import {Popup} from '@nutui/nutui-react-taro'
|
import {Popup} from '@nutui/nutui-react-taro'
|
||||||
import {UserParam} from "@/api/system/user/model";
|
import {UserParam} from "@/api/system/user/model";
|
||||||
import {Button} from '@nutui/nutui-react-taro'
|
import {Button, Image} from '@nutui/nutui-react-taro'
|
||||||
import {Form, Input} from '@nutui/nutui-react-taro'
|
import {Form, Input} from '@nutui/nutui-react-taro'
|
||||||
import {Copyright} from "@/config/app";
|
import {Copyright} from "@/config/app";
|
||||||
const UserFooter = () => {
|
const UserFooter = () => {
|
||||||
@@ -46,11 +47,14 @@ const UserFooter = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={'text-center py-4 w-full text-gray-300'} onClick={onLoginByPhone}>
|
<View className={'text-center py-4 w-full text-gray-300'} onClick={onLoginByPhone}>
|
||||||
{/*<div className={'text-xs text-gray-400 py-1'}>当前版本:{Version}</div>*/}
|
{/*<View className={'text-xs text-gray-400 py-1'}>当前版本:{Version}</View>*/}
|
||||||
{/*<div className={'text-xs text-gray-400 py-1'}>Copyright © { new Date().getFullYear() } {Copyright}</div>*/}
|
{/*<View className={'text-xs text-gray-400 py-1'}>Copyright © { new Date().getFullYear() } {Copyright}</View>*/}
|
||||||
<div className={'text-xs text-gray-400 py-1'}>{Copyright}</div>
|
<View className={'text-xs text-gray-400 py-1 flex justify-center items-center gap-2'}>
|
||||||
</div>
|
<Image src={'https://oss.wsdns.cn/20260412/7d03ec2a05964c3e926c4eac12ee5835.png'} mode={'aspectFit'} width={20} height={20} />
|
||||||
|
<Text>{Copyright}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
<Popup
|
<Popup
|
||||||
style={{width: '350px', padding: '10px'}}
|
style={{width: '350px', padding: '10px'}}
|
||||||
@@ -66,7 +70,7 @@ const UserFooter = () => {
|
|||||||
labelPosition="left"
|
labelPosition="left"
|
||||||
onFinish={(values) => submitByPhone(values)}
|
onFinish={(values) => submitByPhone(values)}
|
||||||
footer={
|
footer={
|
||||||
<div
|
<View
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
@@ -76,7 +80,7 @@ const UserFooter = () => {
|
|||||||
<Button nativeType="submit" block style={{backgroundColor: '#000000',color: '#ffffff'}}>
|
<Button nativeType="submit" block style={{backgroundColor: '#000000',color: '#ffffff'}}>
|
||||||
提交
|
提交
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</View>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
|||||||
@@ -73,6 +73,14 @@ export default function RiderOrders() {
|
|||||||
return '待派单'
|
return '待派单'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 配送方式中文映射
|
||||||
|
const getDeliveryMethodText = (method?: string) => {
|
||||||
|
if (method === 'elevator') return '电梯'
|
||||||
|
if (method === 'stairs') return '步梯'
|
||||||
|
if (method === 'groundFloor') return '一楼商铺/其他'
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
const getOrderStatusColor = (order: GltTicketOrder) => {
|
const getOrderStatusColor = (order: GltTicketOrder) => {
|
||||||
const text = getOrderStatusText(order)
|
const text = getOrderStatusText(order)
|
||||||
if (text === '已完成') return 'text-green-600'
|
if (text === '已完成') return 'text-green-600'
|
||||||
@@ -383,6 +391,10 @@ export default function RiderOrders() {
|
|||||||
const pickupName = o.warehouseName || o.storeName
|
const pickupName = o.warehouseName || o.storeName
|
||||||
const pickupAddr = o.warehouseAddress || o.storeAddress
|
const pickupAddr = o.warehouseAddress || o.storeAddress
|
||||||
|
|
||||||
|
// 配送方式信息
|
||||||
|
const deliveryMethodText = getDeliveryMethodText(o.deliveryMethod)
|
||||||
|
const hasDeliveryInfo = !!deliveryMethodText
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Cell key={String(o.id)} style={{ padding: '16px' }}>
|
<Cell key={String(o.id)} style={{ padding: '16px' }}>
|
||||||
<View className="w-full">
|
<View className="w-full">
|
||||||
@@ -418,6 +430,24 @@ export default function RiderOrders() {
|
|||||||
<Text>¥{o.price || '-'}</Text>
|
<Text>¥{o.price || '-'}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{hasDeliveryInfo && (
|
||||||
|
<View className="text-sm text-gray-700 mt-1">
|
||||||
|
<Text className="text-gray-500">配送方式:</Text>
|
||||||
|
<Text className={o.deliveryMethod === 'stairs' ? 'text-orange-500' : ''}>
|
||||||
|
{deliveryMethodText}
|
||||||
|
</Text>
|
||||||
|
{o.deliveryMethod === 'stairs' && o.deliveryFloor && o.deliveryFloor > 1 && (
|
||||||
|
<Text className="ml-1 text-orange-500">({o.deliveryFloor}楼)</Text>
|
||||||
|
)}
|
||||||
|
{o.deliveryMethod === 'stairs' && !o.deliveryFloor && (
|
||||||
|
<Text className="ml-1 text-gray-400">(不送上楼)</Text>
|
||||||
|
)}
|
||||||
|
{!!o.deliveryFee && o.deliveryFee > 0 && (
|
||||||
|
<Text className="ml-3 text-red-500">配送费 ¥{o.deliveryFee}</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
<View className="text-sm text-gray-700 mt-1">
|
<View className="text-sm text-gray-700 mt-1">
|
||||||
<Text className="text-gray-500">配送时间:</Text>
|
<Text className="text-gray-500">配送时间:</Text>
|
||||||
<Text>{o.sendTime ? dayjs(o.sendTime).format('YYYY-MM-DD HH:mm') : '-'}</Text>
|
<Text>{o.sendTime ? dayjs(o.sendTime).format('YYYY-MM-DD HH:mm') : '-'}</Text>
|
||||||
|
|||||||
@@ -40,188 +40,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 配送方式选择
|
|
||||||
.delivery-method-group {
|
|
||||||
.delivery-method-section {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delivery-method-label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delivery-method-options {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delivery-method-item {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 16px 8px;
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 2px solid #f0f0f0;
|
|
||||||
background: #fafafa;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
border-color: #07c160;
|
|
||||||
background: rgba(7, 193, 96, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.97);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.delivery-method-icon {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delivery-method-text {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #333;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delivery-method-item.active .delivery-method-text {
|
|
||||||
color: #07c160;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 是否送上楼
|
|
||||||
.carry-upstairs-section {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 12px 0 0;
|
|
||||||
border-top: 1px dashed #eee;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.carry-upstairs-options {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.carry-upstairs-item {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 2px solid #f0f0f0;
|
|
||||||
background: #fafafa;
|
|
||||||
font-size: 18px;
|
|
||||||
color: #666;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
border-color: #07c160;
|
|
||||||
background: rgba(7, 193, 96, 0.05);
|
|
||||||
color: #07c160;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.97);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 楼层选择
|
|
||||||
.floor-select-section {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 12px 0 0;
|
|
||||||
border-top: 1px dashed #eee;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floor-select-btn {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
padding: 8px 14px;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: #f5f5f5;
|
|
||||||
font-size: 18px;
|
|
||||||
transition: background 0.2s;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background: #e8e8e8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.floor-fee-tip {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 楼层选择弹窗
|
|
||||||
.floor-picker-popup {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 16px;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__content {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__footer {
|
|
||||||
padding: 12px 16px;
|
|
||||||
border-top: 1px solid #f0f0f0;
|
|
||||||
text-align: center;
|
|
||||||
background: #fafafa;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.floor-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(5, 1fr);
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floor-grid-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 14px 0;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 2px solid #f0f0f0;
|
|
||||||
background: #fff;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
border-color: #07c160;
|
|
||||||
background: rgba(7, 193, 96, 0.08);
|
|
||||||
color: #07c160;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.95);
|
|
||||||
background: #f5f5f5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.address-bottom-line{
|
.address-bottom-line{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 12rpx 12rpx 0 0;
|
border-radius: 12rpx 12rpx 0 0;
|
||||||
|
|||||||
@@ -81,15 +81,6 @@ const OrderConfirm = () => {
|
|||||||
const [storeLoading, setStoreLoading] = useState(false)
|
const [storeLoading, setStoreLoading] = useState(false)
|
||||||
const [selectedStore, setSelectedStore] = useState<ShopStore | null>(getSelectedStoreFromStorage())
|
const [selectedStore, setSelectedStore] = useState<ShopStore | null>(getSelectedStoreFromStorage())
|
||||||
|
|
||||||
// 配送方式:elevator(电梯) / stairs(步梯) / groundFloor(一楼商铺/其他)
|
|
||||||
const [deliveryMethod, setDeliveryMethod] = useState<string>('')
|
|
||||||
// 步梯是否需要送上楼(null=未选择)
|
|
||||||
const [needCarryUpstairs, setNeedCarryUpstairs] = useState<boolean | null>(null)
|
|
||||||
// 楼层(从2开始,需要送上楼时选择)
|
|
||||||
const [deliveryFloor, setDeliveryFloor] = useState<number>(2)
|
|
||||||
// 楼层选择弹窗
|
|
||||||
const [floorPickerVisible, setFloorPickerVisible] = useState(false)
|
|
||||||
|
|
||||||
const router = Taro.getCurrentInstance().router;
|
const router = Taro.getCurrentInstance().router;
|
||||||
const params = router?.params || ({} as Record<string, any>)
|
const params = router?.params || ({} as Record<string, any>)
|
||||||
const goodsIdParam = params?.goodsId
|
const goodsIdParam = params?.goodsId
|
||||||
@@ -222,20 +213,11 @@ const OrderConfirm = () => {
|
|||||||
return calculateCouponDiscount(selectedCoupon, total)
|
return calculateCouponDiscount(selectedCoupon, total)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算配送费:每桶每层1元,第1层不收费
|
|
||||||
const getDeliveryFee = () => {
|
|
||||||
// 仅步梯 + 需要送上楼 时才有配送费
|
|
||||||
if (deliveryMethod !== 'stairs' || !needCarryUpstairs) return 0
|
|
||||||
if (deliveryFloor <= 1) return 0
|
|
||||||
return quantity * (deliveryFloor - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算实付金额
|
// 计算实付金额
|
||||||
const getFinalPrice = () => {
|
const getFinalPrice = () => {
|
||||||
const total = getGoodsTotal()
|
const total = getGoodsTotal()
|
||||||
const discount = getCouponDiscount()
|
const discount = getCouponDiscount()
|
||||||
const deliveryFee = getDeliveryFee()
|
return Math.max(0, total - discount)
|
||||||
return Math.max(0, total - discount + deliveryFee)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -499,24 +481,6 @@ const OrderConfirm = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 配送方式校验(仅送货上门模式需要选择)
|
|
||||||
if (goods.deliveryMode !== 1 && !deliveryMethod) {
|
|
||||||
Taro.showToast({
|
|
||||||
title: '请选择配送方式',
|
|
||||||
icon: 'error'
|
|
||||||
})
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 步梯场景:必须选择是否送上楼
|
|
||||||
if (deliveryMethod === 'stairs' && needCarryUpstairs === null) {
|
|
||||||
Taro.showToast({
|
|
||||||
title: '请选择是否需要送上楼',
|
|
||||||
icon: 'error'
|
|
||||||
})
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!payment) {
|
if (!payment) {
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '请选择支付方式',
|
title: '请选择支付方式',
|
||||||
@@ -591,9 +555,7 @@ const OrderConfirm = () => {
|
|||||||
buyerRemarks: orderRemark,
|
buyerRemarks: orderRemark,
|
||||||
couponId: parseInt(String(bestCoupon.id), 10),
|
couponId: parseInt(String(bestCoupon.id), 10),
|
||||||
skuId: resolvedSkuId,
|
skuId: resolvedSkuId,
|
||||||
specInfo: orderDataParam?.specInfo,
|
specInfo: orderDataParam?.specInfo
|
||||||
deliveryMethod: deliveryMethod || undefined,
|
|
||||||
deliveryFloor: needCarryUpstairs ? deliveryFloor : undefined
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -646,9 +608,7 @@ const OrderConfirm = () => {
|
|||||||
// 🔧 确保 couponId 是正确的数字类型,且不传递 undefined
|
// 🔧 确保 couponId 是正确的数字类型,且不传递 undefined
|
||||||
couponId: selectedCoupon ? parseInt(String(selectedCoupon.id), 10) : undefined,
|
couponId: selectedCoupon ? parseInt(String(selectedCoupon.id), 10) : undefined,
|
||||||
skuId: resolvedSkuId,
|
skuId: resolvedSkuId,
|
||||||
specInfo: orderDataParam?.specInfo,
|
specInfo: orderDataParam?.specInfo
|
||||||
deliveryMethod: deliveryMethod || undefined,
|
|
||||||
deliveryFloor: needCarryUpstairs ? deliveryFloor : undefined
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1000,88 +960,6 @@ const OrderConfirm = () => {
|
|||||||
{/* />*/}
|
{/* />*/}
|
||||||
{/*</CellGroup>*/}
|
{/*</CellGroup>*/}
|
||||||
|
|
||||||
{/* 配送方式选择(仅送货上门模式显示) */}
|
|
||||||
{goods.deliveryMode !== 1 && (
|
|
||||||
<CellGroup className={'delivery-method-group'}>
|
|
||||||
<Cell>
|
|
||||||
<View className={'delivery-method-section'}>
|
|
||||||
<View className={'delivery-method-label'}>
|
|
||||||
<Text className={'font-medium text-sm'}>配送方式</Text>
|
|
||||||
<Text className={'text-red-500 text-xs ml-1'}>*</Text>
|
|
||||||
</View>
|
|
||||||
<View className={'delivery-method-options'}>
|
|
||||||
{[
|
|
||||||
{ key: 'elevator', label: '电梯', icon: '🏛️' },
|
|
||||||
{ key: 'stairs', label: '步梯', icon: '🚶' },
|
|
||||||
{ key: 'groundFloor', label: '一楼商铺/其他', icon: '🏪' },
|
|
||||||
].map(item => (
|
|
||||||
<View
|
|
||||||
key={item.key}
|
|
||||||
className={`delivery-method-item ${deliveryMethod === item.key ? 'active' : ''}`}
|
|
||||||
onClick={() => {
|
|
||||||
setDeliveryMethod(item.key)
|
|
||||||
// 切换配送方式时重置送上楼选项
|
|
||||||
setNeedCarryUpstairs(null)
|
|
||||||
setDeliveryFloor(2)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text className={'delivery-method-icon'}>{item.icon}</Text>
|
|
||||||
<Text className={'text-sm'}>{item.label}</Text>
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* 步梯:是否需要送上楼 */}
|
|
||||||
{deliveryMethod === 'stairs' && (
|
|
||||||
<View className={'carry-upstairs-section'}>
|
|
||||||
<Text className={'text-sm text-gray-600 mb-2'}>是否需要送上楼?</Text>
|
|
||||||
<View className={'carry-upstairs-options'}>
|
|
||||||
<View
|
|
||||||
className={`carry-upstairs-item ${needCarryUpstairs === true ? 'active' : ''}`}
|
|
||||||
onClick={() => setNeedCarryUpstairs(true)}
|
|
||||||
>
|
|
||||||
<Text>需要送上楼</Text>
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
className={`carry-upstairs-item ${needCarryUpstairs === false ? 'active' : ''}`}
|
|
||||||
onClick={() => {
|
|
||||||
setNeedCarryUpstairs(false)
|
|
||||||
setDeliveryFloor(2)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text>不需要</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 步梯+送上楼:选择楼层 */}
|
|
||||||
{deliveryMethod === 'stairs' && needCarryUpstairs === true && (
|
|
||||||
<View className={'floor-select-section'}>
|
|
||||||
<Text className={'text-sm text-gray-600'}>送至楼层</Text>
|
|
||||||
<View
|
|
||||||
className={'floor-select-btn'}
|
|
||||||
onClick={() => setFloorPickerVisible(true)}
|
|
||||||
>
|
|
||||||
<Text className={deliveryFloor > 1 ? 'text-gray-900' : 'text-gray-400'}>
|
|
||||||
{deliveryFloor > 1 ? `${deliveryFloor}楼` : '请选择楼层'}
|
|
||||||
</Text>
|
|
||||||
<ArrowRight className={'text-gray-400'} size={14}/>
|
|
||||||
</View>
|
|
||||||
{deliveryFloor > 1 && (
|
|
||||||
<View className={'floor-fee-tip'}>
|
|
||||||
<Text className={'text-xs text-orange-500'}>
|
|
||||||
配送费:{quantity}桶 × {deliveryFloor - 1}层 = ¥{getDeliveryFee().toFixed(2)}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</Cell>
|
|
||||||
</CellGroup>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<CellGroup>
|
<CellGroup>
|
||||||
<Cell key={goods.goodsId}>
|
<Cell key={goods.goodsId}>
|
||||||
<View className={'flex w-full justify-between gap-3'}>
|
<View className={'flex w-full justify-between gap-3'}>
|
||||||
@@ -1180,14 +1058,7 @@ const OrderConfirm = () => {
|
|||||||
)}
|
)}
|
||||||
onClick={() => setCouponVisible(true)}
|
onClick={() => setCouponVisible(true)}
|
||||||
/>
|
/>
|
||||||
<Cell title={'配送费'} extra={
|
<Cell title={'配送费'} extra={'¥0.00'}/>
|
||||||
<View className={'flex items-center gap-1'}>
|
|
||||||
<Text>¥{getDeliveryFee().toFixed(2)}</Text>
|
|
||||||
{deliveryMethod === 'stairs' && needCarryUpstairs && deliveryFloor > 1 && (
|
|
||||||
<Text className={'text-xs text-gray-400'}>({quantity}桶×{deliveryFloor - 1}层)</Text>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
}/>
|
|
||||||
<Cell extra={(
|
<Cell extra={(
|
||||||
<View className={'flex items-end gap-2'}>
|
<View className={'flex items-end gap-2'}>
|
||||||
<Text>已优惠</Text>
|
<Text>已优惠</Text>
|
||||||
@@ -1361,49 +1232,6 @@ const OrderConfirm = () => {
|
|||||||
</View>
|
</View>
|
||||||
</Popup>
|
</Popup>
|
||||||
|
|
||||||
{/* 楼层选择弹窗 */}
|
|
||||||
<Popup
|
|
||||||
visible={floorPickerVisible}
|
|
||||||
position="bottom"
|
|
||||||
onClose={() => setFloorPickerVisible(false)}
|
|
||||||
style={{height: '40vh'}}
|
|
||||||
>
|
|
||||||
<View className="floor-picker-popup">
|
|
||||||
<View className="floor-picker-popup__header">
|
|
||||||
<Text className="text-base font-medium">选择楼层</Text>
|
|
||||||
<Text
|
|
||||||
className="text-sm text-gray-500"
|
|
||||||
onClick={() => setFloorPickerVisible(false)}
|
|
||||||
>
|
|
||||||
关闭
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View className="floor-picker-popup__content">
|
|
||||||
<View className="floor-grid">
|
|
||||||
{Array.from({length: 32}, (_, i) => i + 2).map(f => (
|
|
||||||
<View
|
|
||||||
key={f}
|
|
||||||
className={`floor-grid-item ${deliveryFloor === f ? 'active' : ''}`}
|
|
||||||
onClick={() => {
|
|
||||||
setDeliveryFloor(f)
|
|
||||||
setFloorPickerVisible(false)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text>{f}楼</Text>
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
{deliveryFloor > 1 && (
|
|
||||||
<View className="floor-picker-popup__footer">
|
|
||||||
<Text className={'text-sm text-gray-600'}>
|
|
||||||
配送费:{quantity}桶 × {deliveryFloor - 1}层 = <Text className={'text-red-500 font-bold'}>¥{(quantity * (deliveryFloor - 1)).toFixed(2)}</Text>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</Popup>
|
|
||||||
|
|
||||||
<Gap height={50}/>
|
<Gap height={50}/>
|
||||||
|
|
||||||
<div className={'fixed z-50 bg-white w-full bottom-0 left-0 pt-4 pb-10 border-t border-gray-200'}>
|
<div className={'fixed z-50 bg-white w-full bottom-0 left-0 pt-4 pb-10 border-t border-gray-200'}>
|
||||||
@@ -1418,11 +1246,6 @@ const OrderConfirm = () => {
|
|||||||
已优惠 ¥{getCouponDiscount().toFixed(2)}
|
已优惠 ¥{getCouponDiscount().toFixed(2)}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{getDeliveryFee() > 0 && (
|
|
||||||
<View className={'text-xs text-orange-500'}>
|
|
||||||
含配送费 ¥{getDeliveryFee().toFixed(2)}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className={'buy-btn mx-4'}>
|
<div className={'buy-btn mx-4'}>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -58,6 +58,179 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 配送方式选择
|
||||||
|
.delivery-method-group {
|
||||||
|
.delivery-method-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-method-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-method-options {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-method-item {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 16px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 2px solid #f0f0f0;
|
||||||
|
background: #fafafa;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: #07c160;
|
||||||
|
background: rgba(7, 193, 96, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.97);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-method-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否送上楼
|
||||||
|
.carry-upstairs-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 12px 0 0;
|
||||||
|
border-top: 1px dashed #eee;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carry-upstairs-options {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carry-upstairs-item {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 2px solid #f0f0f0;
|
||||||
|
background: #fafafa;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #666;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: #07c160;
|
||||||
|
background: rgba(7, 193, 96, 0.05);
|
||||||
|
color: #07c160;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.97);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 楼层选择
|
||||||
|
.floor-select-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 0 0;
|
||||||
|
border-top: 1px dashed #eee;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floor-select-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
font-size: 18px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: #e8e8e8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.floor-fee-tip {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 楼层选择弹窗
|
||||||
|
.floor-picker-popup {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
text-align: center;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.floor-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floor-grid-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 14px 0;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 2px solid #f0f0f0;
|
||||||
|
background: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: #07c160;
|
||||||
|
background: rgba(7, 193, 96, 0.08);
|
||||||
|
color: #07c160;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 优惠券弹窗样式
|
// 优惠券弹窗样式
|
||||||
.coupon-popup {
|
.coupon-popup {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -89,6 +89,22 @@ const OrderConfirm = () => {
|
|||||||
// Prevent using stale `inDeliveryRange` from a previous address when user switches addresses.
|
// Prevent using stale `inDeliveryRange` from a previous address when user switches addresses.
|
||||||
const [deliveryRangeCheckedAddressId, setDeliveryRangeCheckedAddressId] = useState<number | undefined>(undefined)
|
const [deliveryRangeCheckedAddressId, setDeliveryRangeCheckedAddressId] = useState<number | undefined>(undefined)
|
||||||
|
|
||||||
|
// 配送方式:elevator(电梯) / stairs(步梯) / groundFloor(一楼商铺/其他)
|
||||||
|
const [deliveryMethod, setDeliveryMethod] = useState<string>('')
|
||||||
|
// 步梯是否需要送上楼(null=未选择)
|
||||||
|
const [needCarryUpstairs, setNeedCarryUpstairs] = useState<boolean | null>(null)
|
||||||
|
// 楼层(从2开始,需要送上楼时选择)
|
||||||
|
const [deliveryFloor, setDeliveryFloor] = useState<number>(2)
|
||||||
|
// 楼层选择弹窗
|
||||||
|
const [floorPickerVisible, setFloorPickerVisible] = useState(false)
|
||||||
|
|
||||||
|
// 计算配送费:每桶每层1元,第1层不收费
|
||||||
|
const getDeliveryFee = () => {
|
||||||
|
if (deliveryMethod !== 'stairs' || !needCarryUpstairs) return 0
|
||||||
|
if (deliveryFloor <= 1) return 0
|
||||||
|
return displayQty * (deliveryFloor - 1)
|
||||||
|
}
|
||||||
|
|
||||||
const router = Taro.getCurrentInstance().router;
|
const router = Taro.getCurrentInstance().router;
|
||||||
const goodsId = router?.params?.goodsId;
|
const goodsId = router?.params?.goodsId;
|
||||||
const orderId = router?.params?.orderId;
|
const orderId = router?.params?.orderId;
|
||||||
@@ -594,6 +610,19 @@ const OrderConfirm = () => {
|
|||||||
Taro.showToast({ title: '请选择收货地址', icon: 'none' })
|
Taro.showToast({ title: '请选择收货地址', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 配送方式校验(必选)
|
||||||
|
if (!deliveryMethod) {
|
||||||
|
Taro.showToast({ title: '请选择配送方式', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 步梯场景:必须选择是否送上楼
|
||||||
|
if (deliveryMethod === 'stairs' && needCarryUpstairs === null) {
|
||||||
|
Taro.showToast({ title: '请选择是否需要送上楼', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (!addressHasCoords) {
|
if (!addressHasCoords) {
|
||||||
Taro.showToast({ title: '该收货地址缺少经纬度,请在地址里选择地图定位后重试', icon: 'none' })
|
Taro.showToast({ title: '该收货地址缺少经纬度,请在地址里选择地图定位后重试', icon: 'none' })
|
||||||
return
|
return
|
||||||
@@ -653,11 +682,14 @@ const OrderConfirm = () => {
|
|||||||
const ok = await ensureInDeliveryRange()
|
const ok = await ensureInDeliveryRange()
|
||||||
if (!ok) return
|
if (!ok) return
|
||||||
|
|
||||||
|
const deliveryFee = getDeliveryFee()
|
||||||
|
const confirmContent = isEditMode
|
||||||
|
? `配送时间:${sendTimeText}\n送水数量:${finalQty} 桶\n配送方式:${deliveryMethod === 'elevator' ? '电梯' : deliveryMethod === 'stairs' ? '步梯' : '一楼商铺/其他'}${deliveryMethod === 'stairs' && needCarryUpstairs && deliveryFloor > 1 ? `(${deliveryFloor}楼)` : deliveryMethod === 'stairs' && !needCarryUpstairs ? '(不送上楼)' : ''}\n${deliveryFee > 0 ? `配送费:¥${deliveryFee.toFixed(2)}\n` : ''}是否确认修改?`
|
||||||
|
: `配送时间:${sendTimeText}\n配送方式:${deliveryMethod === 'elevator' ? '电梯' : deliveryMethod === 'stairs' ? '步梯' : '一楼商铺/其他'}${deliveryMethod === 'stairs' && needCarryUpstairs && deliveryFloor > 1 ? `(${deliveryFloor}楼)` : deliveryMethod === 'stairs' && !needCarryUpstairs ? '(不送上楼)' : ''}\n${deliveryFee > 0 ? `配送费:¥${deliveryFee.toFixed(2)}\n` : ''}将使用 ${finalQty} 张水票下单(优先使用可用数量少的水票),送水 ${finalQty} 桶,是否确认?`
|
||||||
|
|
||||||
const confirmRes = await Taro.showModal({
|
const confirmRes = await Taro.showModal({
|
||||||
title: isEditMode ? '确认修改' : '确认下单',
|
title: isEditMode ? '确认修改' : '确认下单',
|
||||||
content: isEditMode
|
content: confirmContent
|
||||||
? `配送时间:${sendTimeText}\n送水数量:${finalQty} 桶\n是否确认修改?`
|
|
||||||
: `配送时间:${sendTimeText}\n将使用 ${finalQty} 张水票下单(优先使用可用数量少的水票),送水 ${finalQty} 桶,是否确认?`
|
|
||||||
})
|
})
|
||||||
if (!confirmRes.confirm) return
|
if (!confirmRes.confirm) return
|
||||||
|
|
||||||
@@ -671,7 +703,10 @@ const OrderConfirm = () => {
|
|||||||
addressId: address.id,
|
addressId: address.id,
|
||||||
totalNum: finalQty,
|
totalNum: finalQty,
|
||||||
buyerRemarks: orderRemark,
|
buyerRemarks: orderRemark,
|
||||||
sendTime: dayjs(sendTime).startOf('day').format('YYYY-MM-DD HH:mm:ss')
|
sendTime: dayjs(sendTime).startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
deliveryMethod,
|
||||||
|
deliveryFloor: deliveryMethod === 'stairs' && needCarryUpstairs ? deliveryFloor : undefined,
|
||||||
|
deliveryFee: getDeliveryFee() || undefined
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Best-effort auto dispatch rider. If it fails, backend/manual dispatch can still handle it.
|
// Best-effort auto dispatch rider. If it fails, backend/manual dispatch can still handle it.
|
||||||
@@ -697,7 +732,11 @@ const OrderConfirm = () => {
|
|||||||
riderId: Number.isFinite(Number(autoRider?.userId)) ? Number(autoRider?.userId) : undefined,
|
riderId: Number.isFinite(Number(autoRider?.userId)) ? Number(autoRider?.userId) : undefined,
|
||||||
riderName: autoRider?.realName,
|
riderName: autoRider?.realName,
|
||||||
riderPhone: autoRider?.mobile,
|
riderPhone: autoRider?.mobile,
|
||||||
comments: goods?.name ? `立即送水:${goods.name}` : '立即送水'
|
comments: goods?.name ? `立即送水:${goods.name}` : '立即送水',
|
||||||
|
// 配送方式信息
|
||||||
|
deliveryMethod,
|
||||||
|
deliveryFloor: deliveryMethod === 'stairs' && needCarryUpstairs ? deliveryFloor : undefined,
|
||||||
|
deliveryFee: getDeliveryFee() || undefined
|
||||||
})
|
})
|
||||||
remain -= useQty
|
remain -= useQty
|
||||||
}
|
}
|
||||||
@@ -785,6 +824,16 @@ const OrderConfirm = () => {
|
|||||||
const st = parseTime(editingOrderRes.sendTime)
|
const st = parseTime(editingOrderRes.sendTime)
|
||||||
if (st) setSendTime(clampSendDateToToday(st).toDate())
|
if (st) setSendTime(clampSendDateToToday(st).toDate())
|
||||||
|
|
||||||
|
// 回显配送方式
|
||||||
|
if (editingOrderRes.deliveryMethod) {
|
||||||
|
setDeliveryMethod(editingOrderRes.deliveryMethod)
|
||||||
|
if (editingOrderRes.deliveryMethod === 'stairs') {
|
||||||
|
const hasFloor = editingOrderRes.deliveryFloor && editingOrderRes.deliveryFloor > 1
|
||||||
|
setNeedCarryUpstairs(hasFloor)
|
||||||
|
if (hasFloor) setDeliveryFloor(editingOrderRes.deliveryFloor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const addrId = Number(editingOrderRes.addressId)
|
const addrId = Number(editingOrderRes.addressId)
|
||||||
const addrIdSafe = Number.isFinite(addrId) && addrId > 0 ? addrId : undefined
|
const addrIdSafe = Number.isFinite(addrId) && addrId > 0 ? addrId : undefined
|
||||||
if (addrIdSafe) {
|
if (addrIdSafe) {
|
||||||
@@ -1051,6 +1100,85 @@ const OrderConfirm = () => {
|
|||||||
)}
|
)}
|
||||||
</CellGroup>
|
</CellGroup>
|
||||||
|
|
||||||
|
{/* 配送方式选择(必选) */}
|
||||||
|
<CellGroup className={'delivery-method-group'}>
|
||||||
|
<Cell>
|
||||||
|
<View className={'delivery-method-section'}>
|
||||||
|
<View className={'delivery-method-label'}>
|
||||||
|
<Text className={'font-medium text-sm'}>配送方式</Text>
|
||||||
|
<Text className={'text-red-500 text-xs ml-1'}>*</Text>
|
||||||
|
</View>
|
||||||
|
<View className={'delivery-method-options'}>
|
||||||
|
{[
|
||||||
|
{ key: 'elevator', label: '电梯', icon: '🏛️' },
|
||||||
|
{ key: 'stairs', label: '步梯', icon: '🚶' },
|
||||||
|
{ key: 'groundFloor', label: '一楼商铺/其他', icon: '🏪' },
|
||||||
|
].map(item => (
|
||||||
|
<View
|
||||||
|
key={item.key}
|
||||||
|
className={`delivery-method-item ${deliveryMethod === item.key ? 'active' : ''}`}
|
||||||
|
onClick={() => {
|
||||||
|
setDeliveryMethod(item.key)
|
||||||
|
setNeedCarryUpstairs(null)
|
||||||
|
setDeliveryFloor(2)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text className={'delivery-method-icon'}>{item.icon}</Text>
|
||||||
|
<Text className={'text-sm'}>{item.label}</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 步梯:是否需要送上楼 */}
|
||||||
|
{deliveryMethod === 'stairs' && (
|
||||||
|
<View className={'carry-upstairs-section'}>
|
||||||
|
<Text className={'text-sm text-gray-600 mb-2'}>是否需要送上楼?</Text>
|
||||||
|
<View className={'carry-upstairs-options'}>
|
||||||
|
<View
|
||||||
|
className={`carry-upstairs-item ${needCarryUpstairs === true ? 'active' : ''}`}
|
||||||
|
onClick={() => setNeedCarryUpstairs(true)}
|
||||||
|
>
|
||||||
|
<Text>需要送上楼</Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
className={`carry-upstairs-item ${needCarryUpstairs === false ? 'active' : ''}`}
|
||||||
|
onClick={() => {
|
||||||
|
setNeedCarryUpstairs(false)
|
||||||
|
setDeliveryFloor(2)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>不需要</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 步梯+送上楼:选择楼层 */}
|
||||||
|
{deliveryMethod === 'stairs' && needCarryUpstairs === true && (
|
||||||
|
<View className={'floor-select-section'}>
|
||||||
|
<Text className={'text-sm text-gray-600'}>送至楼层</Text>
|
||||||
|
<View
|
||||||
|
className={'floor-select-btn'}
|
||||||
|
onClick={() => setFloorPickerVisible(true)}
|
||||||
|
>
|
||||||
|
<Text className={deliveryFloor > 1 ? 'text-gray-900' : 'text-gray-400'}>
|
||||||
|
{deliveryFloor > 1 ? `${deliveryFloor}楼` : '请选择楼层'}
|
||||||
|
</Text>
|
||||||
|
<ArrowRight className={'text-gray-400'} size={14}/>
|
||||||
|
</View>
|
||||||
|
{deliveryFloor > 1 && (
|
||||||
|
<View className={'floor-fee-tip'}>
|
||||||
|
<Text className={'text-xs text-orange-500'}>
|
||||||
|
配送费:{displayQty}桶 x {deliveryFloor - 1}层 = ¥{getDeliveryFee().toFixed(2)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</Cell>
|
||||||
|
</CellGroup>
|
||||||
|
|
||||||
<CellGroup>
|
<CellGroup>
|
||||||
<Cell
|
<Cell
|
||||||
title={'配送时间'}
|
title={'配送时间'}
|
||||||
@@ -1297,6 +1425,49 @@ const OrderConfirm = () => {
|
|||||||
</View>
|
</View>
|
||||||
</Popup>
|
</Popup>
|
||||||
|
|
||||||
|
{/* 楼层选择弹窗 */}
|
||||||
|
<Popup
|
||||||
|
visible={floorPickerVisible}
|
||||||
|
position="bottom"
|
||||||
|
onClose={() => setFloorPickerVisible(false)}
|
||||||
|
style={{height: '40vh'}}
|
||||||
|
>
|
||||||
|
<View className="floor-picker-popup">
|
||||||
|
<View className="floor-picker-popup__header">
|
||||||
|
<Text className="text-base font-medium">选择楼层</Text>
|
||||||
|
<Text
|
||||||
|
className="text-sm text-gray-500"
|
||||||
|
onClick={() => setFloorPickerVisible(false)}
|
||||||
|
>
|
||||||
|
关闭
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View className="floor-picker-popup__content">
|
||||||
|
<View className="floor-grid">
|
||||||
|
{Array.from({length: 32}, (_, i) => i + 2).map(f => (
|
||||||
|
<View
|
||||||
|
key={f}
|
||||||
|
className={`floor-grid-item ${deliveryFloor === f ? 'active' : ''}`}
|
||||||
|
onClick={() => {
|
||||||
|
setDeliveryFloor(f)
|
||||||
|
setFloorPickerVisible(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>{f}楼</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
{deliveryFloor > 1 && (
|
||||||
|
<View className="floor-picker-popup__footer">
|
||||||
|
<Text className={'text-sm text-gray-600'}>
|
||||||
|
配送费:{displayQty}桶 x {deliveryFloor - 1}层 = <Text className={'text-red-500 font-bold'}>¥{(displayQty * (deliveryFloor - 1)).toFixed(2)}</Text>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</Popup>
|
||||||
|
|
||||||
<Gap height={50}/>
|
<Gap height={50}/>
|
||||||
|
|
||||||
<div className={'fixed z-50 bg-white w-full bottom-0 left-0 pt-4 pb-10 border-t border-gray-200'}>
|
<div className={'fixed z-50 bg-white w-full bottom-0 left-0 pt-4 pb-10 border-t border-gray-200'}>
|
||||||
@@ -1309,6 +1480,11 @@ const OrderConfirm = () => {
|
|||||||
</span>
|
</span>
|
||||||
<span className={'text-sm text-gray-500'}>张</span>
|
<span className={'text-sm text-gray-500'}>张</span>
|
||||||
</View>
|
</View>
|
||||||
|
{getDeliveryFee() > 0 && (
|
||||||
|
<View className={'text-xs text-orange-500'}>
|
||||||
|
配送费 ¥{getDeliveryFee().toFixed(2)}(到付)
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={'buy-btn mx-4'}>
|
<div className={'buy-btn mx-4'}>
|
||||||
{noUsableTickets && !isEditMode ? (
|
{noUsableTickets && !isEditMode ? (
|
||||||
@@ -1326,7 +1502,9 @@ const OrderConfirm = () => {
|
|||||||
!addressHasCoords ||
|
!addressHasCoords ||
|
||||||
(deliveryRangeCheckedAddressId === address?.id && inDeliveryRange === false) ||
|
(deliveryRangeCheckedAddressId === address?.id && inDeliveryRange === false) ||
|
||||||
(!isEditMode && availableTicketTotal <= 0) ||
|
(!isEditMode && availableTicketTotal <= 0) ||
|
||||||
!canStartOrder
|
!canStartOrder ||
|
||||||
|
!deliveryMethod ||
|
||||||
|
(deliveryMethod === 'stairs' && needCarryUpstairs === null)
|
||||||
}
|
}
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
>
|
>
|
||||||
@@ -1338,8 +1516,14 @@ const OrderConfirm = () => {
|
|||||||
? '地址缺少定位'
|
? '地址缺少定位'
|
||||||
: ((deliveryRangeCheckedAddressId === address?.id && inDeliveryRange === false)
|
: ((deliveryRangeCheckedAddressId === address?.id && inDeliveryRange === false)
|
||||||
? '不在配送范围'
|
? '不在配送范围'
|
||||||
|
: (!deliveryMethod
|
||||||
|
? '请选配送方式'
|
||||||
|
: (deliveryMethod === 'stairs' && needCarryUpstairs === null
|
||||||
|
? '请选是否送上楼'
|
||||||
: (submitLoading ? '提交中...' : (isEditMode ? '确定修改' : '立即提交'))
|
: (submitLoading ? '提交中...' : (isEditMode ? '确定修改' : '立即提交'))
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user