Files
chazhi_teaspecialist_manage/src/order/waiting-service-detail.vue
wangxiaowei 9266b6b80d 更新文件
2025-12-28 14:23:16 +08:00

807 lines
20 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.

<route lang="jsonc" type="page">
{
"needLogin": false,
"layout": "default",
"style": {
"navigationStyle": "custom"
}
}
</route>
<script lang="ts" setup>
import { useMessage } from 'wot-design-uni'
import PriceFormat from '@/components/PriceFormat.vue'
import { copy, router } from '@/utils/tools'
const OSS = inject('OSS')
// 消息提示框
const message = useMessage('wd-message-box-slot')
// 费用明细弹窗
const showCostDetailPopup = ref(false)
// 费用明细数据
const costDetail = ref({
orderTotal: 828.90,
serviceFee: 640.00,
serviceFeePerHour: 160,
serviceHours: 4,
travelFee: 30.90,
travelFeePerKm: 3.00,
distance: 10.3,
teaServiceFee: 178.00,
teaName: '红茶/绿茶/福鼎白茶/铁观音',
teaPrice: 158,
teawarePrice: 20,
discount: 20.00,
couponDiscount: 20,
deductFee: 130.00,
platformFee: 130.00,
actualIncome: 698.90,
})
// 订单状态
const orderStatus = ref('waiting') // waiting 或 waiting_arrived
// 订单数据
const orderData = ref({
id: 2,
serviceName: '苓苑共享茶室空间',
serviceType: '到店服务',
price: 212.20,
appointmentTimeFull: '2025-03-18 09:00-12:00',
duration: '3小时',
servicePeople: 1,
teaName: '福鼎白茶 (3泡)',
teawareUsage: '客户自备',
notes: '这里是客户留言信息部分,客户未留言,隐藏这块信息', // 如果有备注则显示
address: '青浦区仓桥路478号',
latitude: 31.2304, // 纬度
longitude: 121.4737, // 经度
distance: 5, // 距离(公里)
estimatedTime: 20, // 预计时间(分钟)
orderSn: '7327328627526903',
paymentMethod: '微信支付',
createTime: '2019-05-16 12:20:26',
payTime: '2019-05-16 13:20:26',
})
// 价格点击(查看详情)
function handlePriceClick() {
showCostDetailPopup.value = true
}
// 复制订单编号
function handleCopyOrderSn() {
copy(orderData.value.orderSn)
uni.showToast({
title: '已复制',
icon: 'success',
})
}
// 导航
function handleNavigate() {
if (orderData.value.latitude && orderData.value.longitude) {
uni.openLocation({
latitude: orderData.value.latitude,
longitude: orderData.value.longitude,
name: orderData.value.serviceName,
address: orderData.value.address,
})
}
}
// 查看订单记录
function handleViewOrderRecord() {
// TODO: 跳转到订单记录页面
console.log('查看订单记录')
uni.showToast({
title: '订单记录功能待实现',
icon: 'none',
})
}
// 立即出发/已到达
function handleDepart() {
if (orderStatus.value === 'waiting') {
// TODO: 调用出发的 API
console.log('立即出发')
uni.showToast({
title: '出发成功',
icon: 'success',
})
// 可以返回上一页或跳转到服务中页面
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
else if (orderStatus.value === 'waiting_arrived') {
// 弹出确认到达对话框
message.confirm({
title: '确定到达',
msg: '确认到达后需拍照记录,否则无法开始服务计时',
confirmButtonText: '去拍照',
cancelButtonText: '取消',
cancelButtonProps: {
customClass: '!bg-[#F6F7F8] !text-[#303133] !text-32rpx !leading-44rpx !rounded-8rpx',
},
confirmButtonProps: {
customClass: '!bg-[#4C9F44] !text-[#fff] !text-32rpx !leading-44rpx !rounded-8rpx',
},
}).then((res) => {
if (res.action === 'confirm') {
// 调用拍照功能
handleTakePhoto()
}
}).catch(() => {
// 点击取消按钮
console.log('取消到达确认')
})
}
}
// 拍照功能
function handleTakePhoto() {
// #ifdef MP-WEIXIN
// 微信小程序使用 chooseMedia
uni.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['camera'], // 只使用相机拍照
success: (res) => {
const file = res.tempFiles[0]
if (file) {
console.log('拍照成功:', file.tempFilePath)
// TODO: 上传照片并调用已到达的 API
// 这里可以上传照片到服务器
uni.showToast({
title: '拍照成功',
icon: 'success',
})
// 跳转到服务中页面,传递照片路径
setTimeout(() => {
router.navigateTo(`/pages/order/serving-detail?id=${orderData.value.id}&photoPath=${encodeURIComponent(file.tempFilePath)}`)
}, 1500)
}
},
fail: (err) => {
console.error('拍照失败:', err)
if (err.errMsg && !err.errMsg.includes('cancel')) {
uni.showToast({
title: '拍照失败',
icon: 'none',
})
}
},
})
// #endif
// #ifndef MP-WEIXIN
// 非微信小程序使用 chooseImage
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['camera'], // 只使用相机拍照
success: (res) => {
if (res.tempFilePaths && res.tempFilePaths.length > 0) {
console.log('拍照成功:', res.tempFilePaths[0])
// TODO: 上传照片并调用已到达的 API
// 这里可以上传照片到服务器
uni.showToast({
title: '拍照成功',
icon: 'success',
})
// 跳转到服务中页面,传递照片路径
setTimeout(() => {
router.navigateTo(`/pages/order/serving-detail?id=${orderData.value.id}&photoPath=${encodeURIComponent(res.tempFilePaths[0])}`)
}, 1500)
}
},
fail: (err) => {
console.error('拍照失败:', err)
if (err.errMsg && !err.errMsg.includes('cancel')) {
uni.showToast({
title: '拍照失败',
icon: 'none',
})
}
},
})
// #endif
}
onLoad((args) => {
console.log('args', args)
// 从路由参数获取订单ID和状态
if (args.id) {
// TODO: 根据ID获取订单详情
console.log('订单ID:', args.id)
}
if (args.status) {
orderStatus.value = args.status
console.log('订单状态:', args.status)
}
})
</script>
<template>
<view class="waiting-service-detail-page" :class="{ 'popup-open': showCostDetailPopup }">
<!-- 消息提示框 -->
<wd-message-box selector="wd-message-box-slot" />
<!-- 导航栏 -->
<navbar title="待服务" custom-class="!bg-[#F6F7F8]" />
<scroll-view class="content-scroll" scroll-y>
<!-- 提示文字 -->
<view class="tip-text">
请务必提前确认服务日程,妥善安排好时间
</view>
<!-- 订单卡片 -->
<view style="position: relative;" class="order-card">
<view class="price-btn" @click.stop="handlePriceClick">
<price-format
color="#FFFFFF" :first-size="32" :second-size="32" :subscript-size="24"
:price="orderData.price"
/>
<wd-icon name="arrow-right" size="24rpx" color="#FFFFFF" class="ml-8rpx" />
</view>
<view class="order-card-header">
<view class="order-icon-wrapper">
<wd-img
width="28rpx" height="28rpx" :src="`${OSS}images/chayishi/order-icon.png`"
mode="aspectFill"
/>
</view>
<text class="order-card-title">订单</text>
</view>
<view class="order-card-content">
<text class="service-name">{{ orderData.serviceName }}</text>
</view>
<!-- 预约信息 -->
<view class="info-section">
<view class="section-title">
预约信息
</view>
<view class="info-item">
<text class="info-label">预约时间:</text>
<text class="info-value">{{ orderData.appointmentTimeFull }}</text>
</view>
<view class="info-item">
<text class="info-label">预约时长:</text>
<text class="info-value">{{ orderData.duration }}</text>
</view>
</view>
<!-- 茶艺服务 -->
<view class="info-section">
<view class="section-title">
茶艺服务
</view>
<view class="info-item">
<text class="info-label">服务人数</text>
<text class="info-value">{{ orderData.servicePeople }}</text>
</view>
<view class="info-item">
<text class="info-label">预定茶叶</text>
<text class="info-value">{{ orderData.teaName }}</text>
</view>
<view class="info-item">
<text class="info-label">茶具使用</text>
<text class="info-value">{{ orderData.teawareUsage }}</text>
</view>
</view>
</view>
<!-- 订单备注 -->
<view v-if="orderData.notes" class="info-card">
<view class="info-card-title">
<wd-icon name="chat" size="32rpx" color="#303133" class="mr-8rpx" />
订单备注
</view>
<view class="info-item">
<text class="info-label">备注信息</text>
<text class="info-value">{{ orderData.notes }}</text>
</view>
</view>
<!-- 服务方式 -->
<view class="info-card">
<view class="info-card-title">
服务方式
</view>
<view class="info-item">
<text class="info-label">服务方式</text>
<text class="info-value">{{ orderData.serviceType }}</text>
</view>
<view class="info-item">
<text class="info-label">服务地址</text>
<view class="address-wrapper">
<text class="info-value">{{ orderData.address }}</text>
<wd-icon name="location" size="32rpx" color="#4C9F44" class="ml-16rpx" @click.stop="handleNavigate" />
</view>
</view>
</view>
<!-- 订单记录 -->
<view class="info-card order-record-card" @click="handleViewOrderRecord">
<view class="order-record-wrapper">
<text class="order-record-text">订单记录</text>
<wd-icon name="arrow-right" size="24rpx" color="#909399" />
</view>
</view>
<!-- 订单信息 -->
<view class="info-card">
<view class="info-card-title">
订单信息
</view>
<view class="info-item">
<text class="info-label">订单编号</text>
<view class="order-sn-wrapper">
<text class="info-value">{{ orderData.orderSn }}</text>
<text class="copy-text" @click.stop="handleCopyOrderSn">|复制</text>
</view>
</view>
<view class="info-item">
<text class="info-label">交易方式</text>
<text class="info-value">{{ orderData.paymentMethod }}</text>
</view>
<view class="info-item">
<text class="info-label">创建时间</text>
<text class="info-value">{{ orderData.createTime }}</text>
</view>
<view class="info-item">
<text class="info-label">付款时间</text>
<text class="info-value">{{ orderData.payTime }}</text>
</view>
</view>
<!-- 底部占位避免内容被按钮遮挡 -->
<view class="bottom-placeholder" />
</scroll-view>
<!-- 费用明细弹窗 -->
<wd-popup
v-model="showCostDetailPopup"
lock-scroll
custom-style="border-radius: 32rpx 32rpx 0rpx 0rpx;"
position="bottom"
:z-index="200"
:close-on-click-overlay="true"
>
<view class="cost-detail-popup">
<!-- 关闭按钮 -->
<view class="close-btn" @click="showCostDetailPopup = false">
<wd-icon name="close" size="40rpx" color="#303133" />
</view>
<!-- 标题 -->
<view class="popup-title">
费用明细
</view>
<scroll-view class="cost-content" scroll-y>
<!-- 订单总额 -->
<view class="cost-item">
<text class="cost-label">订单总额</text>
<text class="cost-value">¥ {{ costDetail.orderTotal.toFixed(2) }}</text>
</view>
<!-- 服务费 -->
<view class="cost-item">
<text class="cost-label">服务费</text>
<text class="cost-value">¥ {{ costDetail.serviceFee.toFixed(2) }}</text>
</view>
<view class="cost-sub-item">
<text class="cost-sub-label">服务费 (¥ {{ costDetail.serviceFeePerHour }}/小时)</text>
<text class="cost-sub-value">x{{ costDetail.serviceHours }}</text>
</view>
<!-- 车马费 -->
<view class="cost-item">
<text class="cost-label">车马费</text>
<text class="cost-value">¥ {{ costDetail.travelFee.toFixed(2) }}</text>
</view>
<view class="cost-sub-item">
<text class="cost-sub-label">车马费 (¥ {{ costDetail.travelFeePerKm }}/公里)</text>
<text class="cost-sub-value">{{ costDetail.distance }}公里</text>
</view>
<!-- 茶艺服务 -->
<view class="cost-item">
<text class="cost-label">茶艺服务</text>
<text class="cost-value">¥ {{ costDetail.teaServiceFee.toFixed(2) }}</text>
</view>
<view class="cost-sub-item">
<text class="cost-sub-label">{{ costDetail.teaName }}</text>
<text class="cost-sub-value">¥ {{ costDetail.teaPrice }}</text>
</view>
<view class="cost-sub-item">
<text class="cost-sub-label">茶具使用</text>
<text class="cost-sub-value">¥ {{ costDetail.teawarePrice }}</text>
</view>
<!-- 优惠 -->
<view class="cost-item">
<text class="cost-label">优惠</text>
<text class="cost-value discount">- ¥ {{ costDetail.discount.toFixed(2) }}</text>
</view>
<view class="cost-sub-item">
<text class="cost-sub-label">优惠券</text>
<text class="cost-sub-value discount">- ¥ {{ costDetail.couponDiscount }}</text>
</view>
<!-- 分隔线 -->
<view class="cost-divider" />
<!-- 扣除费用区域 -->
<view class="deduct-section">
<view class="cost-item">
<text class="cost-label">扣除费用</text>
<text class="cost-value">¥ {{ costDetail.deductFee.toFixed(2) }}</text>
</view>
<view class="cost-sub-item">
<text class="cost-sub-label">平台服务费</text>
<text class="cost-sub-value">¥ {{ costDetail.platformFee.toFixed(2) }}</text>
</view>
</view>
<!-- 实际收入 -->
<view class="actual-income">
<text class="actual-income-label">实际收入</text>
<text class="actual-income-value">¥ {{ costDetail.actualIncome.toFixed(2) }}</text>
</view>
</scroll-view>
</view>
</wd-popup>
<!-- 底部操作按钮 -->
<view class="bottom-actions" @click.stop>
<wd-button
type="primary"
custom-class="!bg-[#4C9F44] !text-[#fff] !text-32rpx !px-32rpx !h-88rpx !leading-88rpx !rounded-8rpx !w-full"
@click.stop="handleDepart"
>
{{ orderStatus === 'waiting_arrived' ? '已到达' : '立即出发' }}
</wd-button>
</view>
</view>
</template>
<style lang="scss">
page {
background-color: #f5f5f5;
height: 100%;
}
.waiting-service-detail-page {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5;
}
.price-btn {
position: absolute;
right: 30rpx;
top: 0rpx;
width: 200rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #ff5951;
padding: 12rpx 24rpx;
border-radius: 8rpx;
box-shadow: 0 2rpx 8rpx rgba(255, 89, 81, 0.3);
min-width: 140rpx;
}
.content-scroll {
flex: 1;
padding: 0 30rpx;
}
.tip-text {
font-size: 24rpx;
color: #909399;
line-height: 34rpx;
padding: 20rpx 0;
text-align: center;
}
.order-card {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-top: 20rpx;
}
.order-card-header {
display: flex;
align-items: center;
margin-bottom: 24rpx;
}
.order-icon-wrapper {
position: relative;
width: 28rpx;
height: 28rpx;
margin-right: 8rpx;
}
.order-card-title {
font-size: 28rpx;
color: #303133;
font-weight: 500;
}
.order-card-content {
margin-bottom: 24rpx;
}
.service-name {
font-size: 30rpx;
color: #303133;
font-weight: 500;
}
.info-section {
margin-top: 24rpx;
padding-top: 24rpx;
border-top: 2rpx solid #f6f7f9;
&:first-of-type {
margin-top: 0;
padding-top: 0;
border-top: none;
}
}
.section-title {
font-size: 30rpx;
color: #303133;
font-weight: 500;
margin-bottom: 20rpx;
}
.info-item {
display: flex;
align-items: flex-start;
margin-bottom: 16rpx;
font-size: 28rpx;
line-height: 40rpx;
&:last-child {
margin-bottom: 0;
}
}
.info-label {
color: #909399;
margin-right: 16rpx;
white-space: nowrap;
}
.info-value {
color: #606266;
flex: 1;
}
.info-card {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-top: 20rpx;
}
.info-card-title {
font-size: 30rpx;
color: #303133;
font-weight: 500;
margin-bottom: 24rpx;
display: flex;
align-items: center;
}
.address-wrapper {
display: flex;
align-items: center;
flex: 1;
}
.order-sn-wrapper {
display: flex;
align-items: center;
}
.copy-text {
color: #4c9f44;
margin-left: 8rpx;
}
.map-section {
margin-top: 20rpx;
background-color: #fff;
border-radius: 16rpx;
overflow: hidden;
}
.map-container {
width: 100%;
height: 400rpx;
}
.map-info {
padding: 20rpx 30rpx;
background-color: #fff;
}
.map-info-text {
font-size: 24rpx;
color: #909399;
line-height: 34rpx;
}
.order-record-card {
padding: 30rpx;
}
.order-record-wrapper {
display: flex;
align-items: center;
justify-content: space-between;
}
.order-record-text {
font-size: 30rpx;
color: #303133;
font-weight: 500;
}
.bottom-placeholder {
height: 120rpx;
}
.bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.05);
z-index: 100;
}
// 当弹窗打开时,降低底部按钮的 z-index确保被弹窗遮盖
.waiting-service-detail-page.popup-open .bottom-actions {
z-index: 50;
}
// 费用明细弹窗样式
.cost-detail-popup {
position: relative;
background-color: #fff;
border-radius: 32rpx 32rpx 0 0;
max-height: 80vh;
display: flex;
flex-direction: column;
z-index: 201;
}
.close-btn {
position: absolute;
top: 18rpx;
right: 30rpx;
z-index: 10;
padding: 10rpx;
}
.popup-title {
text-align: center;
font-size: 36rpx;
font-weight: 500;
color: #121212;
line-height: 50rpx;
padding: 50rpx 0 40rpx;
}
.cost-content {
flex: 1;
padding: 0 30rpx 30rpx;
max-height: calc(80vh - 140rpx);
}
.cost-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.cost-label {
font-size: 30rpx;
font-weight: 500;
color: #303133;
line-height: 42rpx;
}
.cost-value {
font-size: 30rpx;
font-weight: 500;
color: #303133;
line-height: 42rpx;
&.discount {
color: #4c9f44;
}
}
.cost-sub-item {
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 20rpx;
margin-bottom: 16rpx;
}
.cost-sub-label {
font-size: 28rpx;
font-weight: 400;
color: #909399;
line-height: 40rpx;
}
.cost-sub-value {
font-size: 28rpx;
font-weight: 400;
color: #303133;
line-height: 40rpx;
&.discount {
color: #4c9f44;
}
}
.cost-divider {
height: 2rpx;
background-color: #f6f7f9;
margin: 30rpx 0;
}
.deduct-section {
background-color: #f6f7f9;
border-radius: 16rpx;
padding: 20rpx;
margin: 20rpx 0;
}
.actual-income {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 40rpx;
padding-top: 30rpx;
}
.actual-income-label {
font-size: 30rpx;
font-weight: 500;
color: #303133;
line-height: 42rpx;
}
.actual-income-value {
font-size: 30rpx;
font-weight: 500;
color: #303133;
line-height: 42rpx;
}
</style>