添加页面

This commit is contained in:
wangxiaowei
2025-12-28 18:29:45 +08:00
parent 62fcdc4cf2
commit 720d532258
13 changed files with 2665 additions and 3484 deletions

4
env/.env vendored
View File

@ -1,8 +1,8 @@
VITE_APP_TITLE = '茶艺师管理端'
VITE_APP_PORT = 9002
VITE_UNI_APPID = '__UNI__D1E5001'
VITE_WX_APPID = 'wx14a689c7c318bea8'
VITE_UNI_APPID = '__UNI__932EB7D'
VITE_WX_APPID = 'wx1a89a1b47d08b83e'
# h5部署网站的base配置到 manifest.config.ts 里的 h5.router.base
VITE_APP_PUBLIC_BASE=/

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "茶管理端",
"appid": "__UNI__D1E5001",
"name": "茶艺师管理端",
"appid": "__UNI__932EB7D",
"description": "",
"versionName": "1.0.0",
"versionCode": "100",
@ -83,7 +83,7 @@
},
"quickapp": {},
"mp-weixin": {
"appid": "wx14a689c7c318bea8",
"appid": "wx1a89a1b47d08b83e",
"setting": {
"urlCheck": false,
"es6": true,

View File

@ -1,272 +0,0 @@
<route lang="jsonc" type="page">
{
"needLogin": true,
"layout": "default",
"style": {
"navigationStyle": "custom"
}
}
</route>
<template>
<view class="pb-254rpx">
<!-- 取消订单 -->
<wd-message-box selector="wd-message-box-slot"></wd-message-box>
<view>
<navbar :title="title" custom-class='!bg-[#F6F7F8]'></navbar>
</view>
<view class="mx-30rpx coupon-bg" >
<view class="flex items-center px-30rpx pt-30rpx pb-40rpx">
<view class="mr-30rpx">
<wd-img width="190rpx" height="190rpx" :src="order.room_msg.img" mode="scaleToFill" radius="10rpx"</wd-img>
</view>
<view class="flex-1">
<view class="flex justify-between items-center">
<view class="flex items-center font-bold text-30rpx leading-42rpx text-[#303133] mr-10rpx">
<view class="line-1 w-300rpx">
{{ order.room_msg.title }}
</view>
</view>
<view class="text-26rpx leading-36rpx text-[#909399]">{{ order.order_amount }}</view>
</view>
<view class="flex justify-between items-center text-26rpx leading-36rpx text-[#909399] mt-18rpx">
<view class="">
{{ order.room_price }}/小时
</view>
<view class="">
x{{ order.hours }}
</view>
</view>
</view>
</view>
<view class="mt-28rpx pb-36rpx">
<view class="text-30rpx leading-42rpx text-[#303133] px-30rpx">预约信息</view>
<view class="font-500 text-26rpx leading-48rpx text-[#606266] mt-20rpx">
<view class="mb-20rpx px-30rpx">预约时间{{ order.day_time }} {{ order?.renew_dtime?.start_time || order.start_time }}-{{ order?.renew_dtime?.end_time || order.end_time }}</view>
<view class="flex justify-between items-center pl-30rpx" >
<view>预约时长{{ order.hours }}小时</view>
</view>
</view>
</view>
</view>
<!-- 订单信息 -->
<view class="bg-white rounded-16rpx px-30rpx py-34rpx mx-30rpx mt-20rpx">
<view class="text-[#303133] text-32rpx leading-44rpx">订单信息</view>
<view class="text-28rpx leading-40rpx text-[#606266] flex items-center justify-between mt-22rpx">
<view>订单编号</view>
<view>
<text>{{ order.order_sn }}</text>
<wd-divider vertical />
<text class="text-[#4C9F44]" @click="copy(order.order_sn)">复制</text>
</view>
</view>
<view class="text-28rpx leading-40rpx text-[#606266] flex items-center justify-between mt-22rpx">
<view>创建时间</view>
<view>{{ order.dtime }}</view>
</view>
<view class="text-28rpx leading-40rpx text-[#606266] flex items-center justify-between mt-22rpx">
<view>支付方式</view>
<view>{{ order.pay_way_title }}</view>
</view>
<view class="text-28rpx leading-40rpx text-[#606266] flex items-center justify-between mt-22rpx">
<view>用户手机号</view>
<view>{{ order.mobile }}</view>
</view>
<view class="my-22rpx">
<wd-gap height="2rpx" bg-color='#F6F7F9'></wd-gap>
</view>
<view class="text-28rpx leading-40rpx text-[#606266] flex items-center justify-between mt-22rpx">
<view>续单时长</view>
<view>{{ order.renew_hour }}</view>
</view>
<view class="text-28rpx leading-40rpx text-[#606266] flex items-center justify-between mt-22rpx">
<view>续单费用</view>
<view>{{ order.renew_price }}</view>
</view>
</view>
<!-- 操作按钮 -->
<view class="w-full fixed bottom-0 left-0 right-0 bg-white h-152rpx" v-if="order.order_status == AdminOrderStatus.ToUse || order.order_status == AdminOrderStatus.Consuming">
<!-- 待使用 -->
<view class="flex items-center justify-between mx-58rpx mt-34rpx" v-if="order.order_status == AdminOrderStatus.ToUse">
<view class="w-630rpx h-90rpx leading-90rpx text-center bg-[#4C9F44] rounded-8rpx text-[#fff]" @click="OrderDetail.handleCancelOrder">取消订单</view>
</view>
<!-- 使用中 -->
<view class="flex items-center justify-between mx-58rpx mt-34rpx" v-if="order.order_status == AdminOrderStatus.Consuming">
<view class="flex items-center">
<view class="text-28rpx leading-40rpx text-[#606266] mr-16rpx" @click="OrderDetail.handleCancelOrder">取消订单</view>
</view>
<view class="w-360rpx h-90rpx leading-90rpx text-center bg-[#4C9F44] rounded-8rpx text-[#fff]" @click="OrderDetail.handleReleaseOrder">释放时间</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { AdminOrderStatus, AdminOrderStatusTextValue } from '@/utils/order'
import { useMessage } from 'wot-design-uni'
import {toast} from '@/utils/toast'
import type { ITeaSpecialistOrderDetailsResult } from '@/api/types/tea'
import { copy } from '@/utils/tools'
import { getOrderStoreDetail } from '@/api/order'
import { handleReleaseTeaRoomOrderHookds, handleCancelOrderHooks } from '@/hooks/useOrder'
const title = ref<string>('')
// 取消订单弹窗
const message = useMessage('wd-message-box-slot')
// 订单
const orderId = ref<number>(0)
const order = ref<ITeaSpecialistOrderDetailsResult>({
order_amount: '',
order_sn: '',
store_id: 0,
mobile: '',
room_msg: {
id: 0,
price: 0,
img: '',
title: '',
},
store_msg: {
id: 0,
address: '',
name: '',
operation_type: 1,
image: '',
latitude: '',
longitude: '',
contact_phone: ''
},
time1: 0,
order_status: 0,
room_all_price: 0,
room_price: 0,
day_time: '',
start_time: '',
end_time: '',
hours: 0,
distance: 0,
pay_way: 0,
pay_way_title: '',
dtime: '',
update_dtime: '',
renew_price: 0,
renew_hour: 0,
renew_dtime: {
start_time: '',
end_time: '',
renew_price: 0
}
})
onLoad(async (args) => {
orderId.value = args.id
// 获取订单详情
OrderDetail.handleInit()
})
onUnload(() => {
uni.$off('refreshOrderDetail')
})
const OrderDetail = {
/**
* 获取订单详情
*/
handleInit: async () => {
const res = await getOrderStoreDetail(orderId.value)
order.value = res.details
title.value = AdminOrderStatusTextValue[order.value.order_status].title || '订单详情'
},
/**
* 取消订单
*/
handleCancelOrder: () => {
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') {
uni.$on('refreshOrderList', () => {
uni.$off('refreshOrderList')
OrderDetail.handleInit()
})
handleCancelOrderHooks(orderId.value)
}
}).catch(() => {
// 点击取消按钮回调事件
})
},
/**
* 释放时间
*/
handleReleaseOrder: () => {
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') {
uni.$on('refreshOrderList', () => {
uni.$off('refreshOrderList')
OrderDetail.handleInit()
})
handleReleaseTeaRoomOrderHookds(orderId.value)
}
}).catch(() => { })
},
}
</script>
<style lang="scss">
page {
background-color: $cz-page-background;
}
.coupon-bg {
background-image: url(#{$OSS}images/order/order_image2.png);
background-repeat: no-repeat;
background-size: 100% 100%;
}
.collapse {
:deep() {
.wd-collapse-item::after,
.wd-collapse-item__header.is-expanded::after {
background: none !important;
}
.wd-collapse-item__body {
padding-top: 0 !important;
padding-bottom: 0 !important;
}
}
}
</style>

View File

@ -1,806 +0,0 @@
<!-- 使用 type="home" 属性设置首页其他页面不需要设置默认为page -->
<route lang="jsonc">
{
"needLogin": false,
"layout": "tabbar",
"style": {
// 'custom' 表示开启自定义导航栏,默认 'default'
"navigationStyle": "custom",
"navigationBarTitleText": "订单"
}
}
</route>
<script lang="ts" setup>
import { useMessage } from 'wot-design-uni'
import PriceFormat from '@/components/PriceFormat.vue'
import { router } from '@/utils/tools'
const OSS = inject('OSS')
// 消息提示框
const message = useMessage('wd-message-box-slot')
// 地图选择弹窗
const showMapSelectPopup = ref(false)
// 当前要导航的订单
const currentNavigateOrder = ref<any>(null)
// 当前选中的标签
const activeTab = ref('all')
// 模拟订单数据
const orders = ref<Array<any>>([
{
id: 1,
serviceName: '苓苑共享茶室空间',
serviceType: '到店服务',
status: 'pending',
statusText: '待接单',
price: 212.20,
appointmentTime: '09/03 08:00-12:00',
address: '上海浦东新区新金桥路58号新银东大厦15楼F室',
timer: '09:52',
},
{
id: 2,
serviceName: '韩梅梅预约单',
serviceType: '上门服务',
status: 'waiting',
statusText: '待服务',
price: 212.20,
appointmentTime: '09/03 08:00-12:00',
address: '上海浦东新区新金桥路58号新银东大厦15楼F室',
},
{
id: 3,
serviceName: '韩梅梅预约单',
serviceType: '上门服务',
status: 'waiting_arrived',
statusText: '待服务',
price: 212.20,
appointmentTime: '09/03 08:00-12:00',
address: '上海浦东新区新金桥路58号新银东大厦15楼F室',
timer: '09:52',
},
{
id: 4,
serviceName: '韩梅梅预约单',
serviceType: '上门服务',
status: 'serving',
statusText: '服务中',
price: 212.20,
appointmentTime: '09/03 08:00-12:00',
address: '上海浦东新区新金桥路58号新银东大厦15楼F室',
timer: '99:52',
},
{
id: 5,
serviceName: '韩梅梅预约单',
serviceType: '上门服务',
status: 'serving',
statusText: '服务中',
price: 212.20,
appointmentTime: '09/03 08:00-12:00',
address: '上海浦东新区新金桥路58号新银东大厦15楼F室',
timer: '00:00',
},
{
id: 6,
serviceName: '韩梅梅预约单',
serviceType: '上门服务',
status: 'completed',
statusText: '完成',
price: 212.20,
appointmentTime: '09/03 08:00-12:00',
address: '上海浦东新区新金桥路58号新银东大厦15楼F室',
},
{
id: 7,
serviceName: '韩梅梅预约单',
serviceType: '上门服务',
status: 'cancelled',
statusText: '订单取消',
price: 212.20,
appointmentTime: '09/03 08:00-12:00',
address: '上海浦东新区新金桥路58号新银东大厦15楼F室',
},
])
// 根据标签过滤订单
const filteredOrders = computed(() => {
if (activeTab.value === 'all') {
return orders.value
}
// 处理待服务状态,包含 waiting 和 waiting_arrived
if (activeTab.value === 'waiting') {
return orders.value.filter(order => order.status === 'waiting' || order.status === 'waiting_arrived')
}
return orders.value.filter(order => order.status === activeTab.value)
})
// 获取状态样式类
function getStatusClass(status: string) {
const statusMap: Record<string, string> = {
pending: 'status-pending',
waiting: 'status-waiting',
serving: 'status-serving',
completed: 'status-completed',
cancelled: 'status-cancelled',
}
return statusMap[status] || ''
}
// 获取服务类型样式类
function getServiceTypeClass(serviceType: string) {
return serviceType === '到店服务' ? 'service-type-store' : 'service-type-home'
}
// 获取订单操作按钮
function getOrderActions(status: string) {
const actionMap: Record<string, Array<any>> = {
pending: [
{ text: '放弃接单', type: 'default', plain: true, customClass: '!border-[#dcdfe6] !text-[#909399] !bg-transparent !border-2rpx !text-28rpx !px-32rpx !h-64rpx !leading-64rpx !rounded-8rpx', action: 'decline' },
{ text: '立即接单', type: 'primary', plain: true, customClass: '!border-[#4c9f44] !text-[#4c9f44] !bg-transparent !border-2rpx !text-28rpx !px-32rpx !h-64rpx !leading-64rpx !rounded-8rpx', action: 'accept' },
],
waiting: [
{ text: '导航', type: 'default', plain: true, customClass: '!border-[#dcdfe6] !text-[#909399] !bg-transparent !border-2rpx !text-28rpx !px-32rpx !h-64rpx !leading-64rpx !rounded-8rpx', action: 'navigate' },
{ text: '立即出发', type: 'primary', plain: true, customClass: '!border-[#4c9f44] !text-[#4c9f44] !bg-transparent !border-2rpx !text-28rpx !px-32rpx !h-64rpx !leading-64rpx !rounded-8rpx', action: 'depart' },
],
waiting_arrived: [
{ text: '导航', type: 'default', plain: true, customClass: '!border-[#dcdfe6] !text-[#909399] !bg-transparent !border-2rpx !text-28rpx !px-32rpx !h-64rpx !leading-64rpx !rounded-8rpx', action: 'navigate' },
{ text: '已到达', type: 'primary', plain: true, customClass: '!border-[#4c9f44] !text-[#4c9f44] !bg-transparent !border-2rpx !text-28rpx !px-32rpx !h-64rpx !leading-64rpx !rounded-8rpx', action: 'arrived' },
],
serving: [
{ text: '完成服务', type: 'primary', plain: true, customClass: '!border-[#4c9f44] !text-[#4c9f44] !bg-transparent !border-2rpx !text-28rpx !px-32rpx !h-64rpx !leading-64rpx !rounded-8rpx', action: 'complete' },
],
completed: [
{ text: '删除订单', type: 'default', plain: true, customClass: '!border-[#dcdfe6] !text-[#909399] !bg-transparent !border-2rpx !text-28rpx !px-32rpx !h-64rpx !leading-64rpx !rounded-8rpx', action: 'delete' },
],
cancelled: [
{ text: '删除订单', type: 'default', plain: true, customClass: '!border-[#dcdfe6] !text-[#909399] !bg-transparent !border-2rpx !text-28rpx !px-32rpx !h-64rpx !leading-64rpx !rounded-8rpx', action: 'delete' },
],
}
return actionMap[status] || []
}
// 处理操作
function handleAction(order: any, action: string) {
console.log('操作:', action, order)
// 这里可以添加具体的操作逻辑
switch (action) {
case 'accept':
// 接单逻辑
break
case 'decline':
// 放弃接单逻辑
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') {
// 确认放弃接单的逻辑
console.log('确认放弃接单', order)
// TODO: 调用放弃接单的 API
}
}).catch(() => {
// 点击取消按钮
console.log('取消放弃接单')
})
break
case 'navigate':
// 导航逻辑 - 显示地图选择弹窗
currentNavigateOrder.value = order
showMapSelectPopup.value = true
break
case 'depart':
console.log('出发逻辑', order)
// 出发逻辑 - 跳转到详情页,状态改为 waiting_arrived已出发等待到达
router.navigateTo(`/pages/order/waiting-service-detail?id=${order.id}&status=waiting_arrived`)
break
case 'arrived':
// 已到达逻辑 - 直接跳转到服务中详情页
router.navigateTo(`/pages/order/serving-detail?id=${order.id}`)
break
case 'complete':
// 完成服务逻辑
break
case 'delete':
// 删除订单逻辑
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') {
// 确认删除订单的逻辑
console.log('确认删除订单', order)
// TODO: 调用删除订单的 API
uni.showToast({
title: '删除订单成功',
icon: 'success',
})
}
}).catch(() => {
// 点击取消按钮
console.log('取消删除订单')
})
break
}
}
// 切换标签
function handleTabChange(e: { index: number, name: string }) {
activeTab.value = e.name
}
// 跳转搜索
function handleToSearch() {
router.navigateTo('/pages/search/search')
}
// 点击订单跳转到详情页
function handleOrderClick(order: any) {
console.log('点击订单', order)
if (order.status === 'pending') {
router.navigateTo(`/pages/order/pending-order-detail?id=${order.id}`)
}
else if (order.status === 'waiting' || order.status === 'waiting_arrived') {
router.navigateTo(`/pages/order/waiting-service-detail?id=${order.id}`)
}
else if (order.status === 'cancelled') {
router.navigateTo(`/pages/order/cancelled-order-detail?id=${order.id}`)
}
}
// 选择地图应用
function handleSelectMap(mapType: string) {
if (!currentNavigateOrder.value) {
return
}
const order = currentNavigateOrder.value
const address = order.address || ''
// 默认使用上海的坐标(如果订单没有经纬度)
const latitude = order.latitude || 31.2304
const longitude = order.longitude || 121.4737
// 关闭弹窗
showMapSelectPopup.value = false
// 根据不同地图类型打开相应的应用
// #ifdef MP-WEIXIN
// 微信小程序使用 uni.openLocation会打开系统默认地图选择器
uni.openLocation({
latitude,
longitude,
name: order.serviceName || '',
address,
})
// #endif
// #ifndef MP-WEIXIN
// H5 和 App 使用 URL scheme
let mapUrl = ''
const encodedAddress = encodeURIComponent(address)
switch (mapType) {
case 'gaode':
// 高德地图
// #ifdef APP-PLUS
// App 使用 scheme
if (uni.getSystemInfoSync().platform === 'ios') {
mapUrl = `iosamap://path?sourceApplication=applicationName&dname=${encodedAddress}&dlat=${latitude}&dlon=${longitude}&dev=0&t=0`
}
else {
mapUrl = `androidamap://route?sourceApplication=amap&dname=${encodedAddress}&dlat=${latitude}&dlon=${longitude}&dev=0&t=0`
}
// #endif
// #ifdef H5
mapUrl = `https://uri.amap.com/navigation?to=${longitude},${latitude}&toName=${encodedAddress}&mode=car&policy=0&src=mypage&coordinate=gaode&callnative=0`
// #endif
break
case 'tencent':
// 腾讯地图
// #ifdef APP-PLUS
if (uni.getSystemInfoSync().platform === 'ios') {
mapUrl = `qqmap://map/routeplan?type=drive&to=${encodedAddress}&tocoord=${latitude},${longitude}`
}
else {
mapUrl = `qqmap://map/routeplan?type=drive&to=${encodedAddress}&tocoord=${latitude},${longitude}`
}
// #endif
// #ifdef H5
mapUrl = `https://apis.map.qq.com/uri/v1/routeplan?type=drive&to=${encodedAddress}&tocoord=${latitude},${longitude}&policy=LEAST_TIME&referer=myapp`
// #endif
break
case 'apple':
// 苹果地图
// #ifdef APP-PLUS
mapUrl = `http://maps.apple.com/?daddr=${latitude},${longitude}&dirflg=d`
// #endif
// #ifdef H5
mapUrl = `http://maps.apple.com/?daddr=${latitude},${longitude}&dirflg=d`
// #endif
break
}
if (mapUrl) {
// #ifdef APP-PLUS
const plusRuntime = (globalThis as any).plus?.runtime
if (plusRuntime) {
plusRuntime.openURL(mapUrl, (error: any) => {
console.error('打开地图失败:', error)
// 如果打开失败,尝试使用系统默认地图
uni.openLocation({
latitude,
longitude,
name: order.serviceName || '',
address,
})
})
}
else {
// 如果 plus 不可用,使用系统默认地图
uni.openLocation({
latitude,
longitude,
name: order.serviceName || '',
address,
})
}
// #endif
// #ifdef H5
window.open(mapUrl, '_blank')
// #endif
}
else {
// 如果没有指定地图 URL使用系统默认地图
uni.openLocation({
latitude,
longitude,
name: order.serviceName || '',
address,
})
}
// #endif
}
</script>
<template>
<view class="order-page">
<!-- 顶部搜索和标签栏 -->
<view class="order-header bg-[#F6F7F8]">
<wd-navbar safe-area-inset-top custom-class="!bg-[#F6F7F8]" :bordered="false" placeholder>
<template #left>
<view class="search-box" @click="handleToSearch">
<wd-search
placeholder="搜索订单信息" hide-cancel disabled :placeholder-left="true"
custom-class="search-input"
/>
</view>
</template>
</wd-navbar>
<view class="tabs-container">
<wd-tabs v-model="activeTab" swipeable @change="handleTabChange">
<wd-tab title="全部" name="all" />
<wd-tab title="待接单" name="pending" />
<wd-tab title="待服务" name="waiting" />
<wd-tab title="服务中" name="serving" />
<wd-tab title="已完成" name="completed" />
</wd-tabs>
</view>
</view>
<!-- 订单列表 -->
<scroll-view class="order-list" scroll-y>
<view v-for="(order, index) in filteredOrders" :key="order.id || index" class="order-item">
<!-- 订单内容区域可点击跳转 -->
<view class="order-content" @click="handleOrderClick(order)">
<!-- 订单头部 -->
<view class="order-header-row">
<view class="order-title-row">
<wd-img
width="28rpx" height="28rpx" :src="`${OSS}images/chayishi/order-icon.png`"
mode="aspectFill"
/>
<text class="order-title">订单</text>
</view>
<view class="order-status-row">
<text class="order-status" :class="getStatusClass(order.status)">
{{ order.statusText }}
</text>
<text v-if="order.timer" class="order-timer">{{ order.timer }}</text>
</view>
</view>
<!-- 服务信息 -->
<view class="order-service-info flex items-center justify-between">
<text class="service-name">{{ order.serviceName }}</text>
<view class="order-price">
<price-format
color="#303133" :first-size="32" :second-size="32" :subscript-size="24"
:price="order.price"
/>
</view>
</view>
<!-- 价格 -->
<view class="mb-24rpx">
<text class="service-type" :class="getServiceTypeClass(order.serviceType)">
{{ order.serviceType }}
</text>
</view>
<!-- 预约时间 -->
<view class="order-info-row">
<text class="info-label">预约时间:</text>
<text class="info-value">{{ order.appointmentTime }}</text>
</view>
<!-- 地址 -->
<view class="order-info-row">
<text class="info-label">地址:</text>
<text class="info-value">{{ order.address }}</text>
</view>
</view>
<!-- 操作按钮 -->
<view class="order-actions" @click.stop>
<wd-button
v-for="(action, actionIndex) in getOrderActions(order.status)" :key="actionIndex"
:type="action.type" :plain="action.plain" :custom-class="action.customClass" size="small"
@click="handleAction(order, action.action)"
>
{{ action.text }}
</wd-button>
</view>
</view>
<!-- 空状态 -->
<view v-if="filteredOrders.length === 0" class="empty-state">
<text class="empty-text">暂无订单</text>
</view>
</scroll-view>
<!-- 消息提示框 -->
<wd-message-box selector="wd-message-box-slot" />
<!-- 地图选择弹窗 -->
<wd-popup
v-model="showMapSelectPopup"
lock-scroll
custom-style="border-radius: 32rpx 32rpx 0rpx 0rpx;"
position="bottom"
:z-index="200"
:close-on-click-overlay="true"
>
<view class="map-select-popup">
<!-- 拖拽指示条 -->
<view class="popup-handle" />
<!-- 地图选项列表 -->
<view class="map-options">
<view class="map-option-item" @click="handleSelectMap('gaode')">
<text class="map-option-text">高德地图</text>
</view>
<view class="map-divider" />
<view class="map-option-item" @click="handleSelectMap('tencent')">
<text class="map-option-text">腾讯地图</text>
</view>
<view class="map-divider" />
<view class="map-option-item" @click="handleSelectMap('apple')">
<text class="map-option-text">苹果地图</text>
</view>
</view>
<!-- 取消按钮 -->
<view class="map-cancel-wrapper">
<view class="map-cancel-divider" />
<view class="map-option-item map-cancel-item" @click="showMapSelectPopup = false">
<text class="map-option-text map-cancel-text">取消</text>
</view>
</view>
</view>
</wd-popup>
</view>
</template>
<style lang="scss">
page {
background-color: #f5f5f5;
height: 100%;
}
.order-page {
display: flex;
flex-direction: column;
height: 100vh;
}
.order-header {
position: sticky;
top: 0;
z-index: 100;
padding-bottom: 20rpx;
}
.search-box {
width: 100%;
padding: 0 30rpx;
margin-top: 20rpx;
:deep() {
.wd-search {
background: transparent !important;
width: 100% !important;
}
.wd-search__block {
background-color: #fff !important;
border-radius: 8rpx;
}
.wd-search__input {
padding-left: 24rpx !important;
font-size: 32rpx !important;
color: #c9c9c9 !important;
}
}
}
.tabs-container {
padding: 0 30rpx;
margin-top: 20rpx;
:deep() {
.wd-tabs,
.wd-tabs__nav {
background-color: transparent !important;
}
.wd-tabs__nav-item {
font-weight: 400 !important;
font-size: 28rpx !important;
color: #606266 !important;
line-height: 40rpx !important;
padding: 0 30rpx !important;
}
.wd-tabs__nav-item.is-active {
font-weight: 500 !important;
color: #303133 !important;
font-size: 32rpx !important;
line-height: 44rpx !important;
}
.wd-tabs__line {
background-color: #4c9f44 !important;
height: 4rpx !important;
}
}
}
// 地图选择弹窗样式
.map-select-popup {
position: relative;
background-color: #fff;
border-radius: 32rpx 32rpx 0 0;
padding-bottom: env(safe-area-inset-bottom);
}
.popup-handle {
width: 80rpx;
height: 8rpx;
background-color: #e5e5e5;
border-radius: 4rpx;
margin: 20rpx auto 30rpx;
}
.map-options {
padding: 0 30rpx;
}
.map-option-item {
display: flex;
align-items: center;
justify-content: center;
padding: 32rpx 0;
cursor: pointer;
transition: background-color 0.2s;
&:active {
background-color: #f5f5f5;
}
}
.map-option-text {
font-size: 32rpx;
color: #303133;
line-height: 44rpx;
}
.map-divider {
height: 2rpx;
background-color: #f6f7f9;
margin: 0;
}
.map-cancel-wrapper {
margin-top: 20rpx;
padding: 0 30rpx 30rpx;
}
.map-cancel-divider {
height: 16rpx;
background-color: #f6f7f9;
margin: 0 -30rpx 20rpx;
}
.map-cancel-item {
border-radius: 16rpx;
background-color: #f6f7f9;
}
.map-cancel-text {
color: #303133;
}
.order-list {
flex: 1;
padding: 20rpx 30rpx;
}
.order-item {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
}
.order-content {
cursor: pointer;
}
.order-header-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
}
.order-title-row {
display: flex;
align-items: center;
gap: 8rpx;
}
.order-title {
font-size: 28rpx;
color: #303133;
font-weight: 500;
}
.order-status-row {
display: flex;
align-items: center;
gap: 12rpx;
}
.order-status {
font-size: 28rpx;
font-weight: 400;
&.status-pending {
color: #ff6b35;
}
&.status-waiting {
color: #4c9f44;
}
&.status-serving {
color: #4c9f44;
}
&.status-completed {
color: #909399;
}
&.status-cancelled {
color: #909399;
}
}
.order-timer {
font-size: 24rpx;
color: #ff5951;
}
.order-service-info {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 20rpx;
}
.service-name {
font-size: 30rpx;
color: #303133;
font-weight: 500;
}
.service-type {
font-size: 22rpx;
padding: 4rpx 12rpx;
border-radius: 4rpx;
white-space: nowrap;
&.service-type-store {
border: 2rpx solid #f55726;
color: #f55726;
}
&.service-type-home {
border: 2rpx solid #b8e6b8;
color: #4c9f44;
}
}
.order-price {
// margin-bottom: 20rpx;
}
.order-info-row {
display: flex;
align-items: flex-start;
margin-bottom: 16rpx;
font-size: 24rpx;
line-height: 34rpx;
}
.info-label {
color: #909399;
margin-right: 8rpx;
white-space: nowrap;
}
.info-value {
color: #606266;
flex: 1;
}
.order-actions {
display: flex;
justify-content: flex-end;
gap: 20rpx;
margin-top: 30rpx;
padding-top: 30rpx;
border-top: 2rpx solid #f6f7f9;
}
.empty-state {
text-align: center;
padding: 100rpx 0;
}
.empty-text {
font-size: 28rpx;
color: #909399;
}
</style>

View File

@ -1,754 +0,0 @@
<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 } 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 orderData = ref({
id: 1,
serviceName: '苓苑共享茶室空间',
serviceType: '到店服务',
price: 212.20,
appointmentTimeFull: '2025-03-18 09:00-12:00',
duration: '3小时',
servicePeople: 1,
teaName: '福鼎白茶 (3泡)',
teawareUsage: '客户自备',
notes: '', // 如果为空则不显示备注卡片
address: '青浦区仓桥路478号',
orderSn: '7327328627526903',
paymentMethod: '微信支付',
createTime: '2019-05-16 12:20:26',
payTime: '2019-05-16 13:20:26',
// 倒计时相关(秒)
countdownSeconds: 9 * 3600 + 30 * 60 + 48, // 09:30:48
})
// 倒计时
const countdownTime = ref('09:30:48')
let countdownTimer: any = null
// 格式化倒计时
function formatCountdown(seconds: number) {
const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
const secs = seconds % 60
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`
}
// 开始倒计时
function startCountdown() {
if (countdownTimer) {
clearInterval(countdownTimer)
}
countdownTimer = setInterval(() => {
if (orderData.value.countdownSeconds > 0) {
orderData.value.countdownSeconds--
countdownTime.value = formatCountdown(orderData.value.countdownSeconds)
}
else {
clearInterval(countdownTimer)
// 倒计时结束,可以触发自动取消逻辑
console.log('订单自动取消')
}
}, 1000)
}
// 复制订单编号
function handleCopyOrderSn() {
copy(orderData.value.orderSn)
uni.showToast({
title: '已复制',
icon: 'success',
})
}
// 导航
function handleNavigate() {
// TODO: 实现导航功能
console.log('导航到:', orderData.value.address)
uni.showToast({
title: '导航功能待实现',
icon: 'none',
})
}
// 价格点击(查看详情)
function handlePriceClick() {
showCostDetailPopup.value = true
}
// 放弃接单
function handleDecline() {
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') {
// TODO: 调用放弃接单的 API
console.log('确认放弃接单')
uni.showToast({
title: '放弃接单成功',
icon: 'success',
})
// 可以返回上一页
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
}).catch(() => {
console.log('取消放弃接单')
})
}
// 立即接单
function handleAccept() {
// TODO: 调用接单的 API
console.log('立即接单')
uni.showToast({
title: '接单成功',
icon: 'success',
})
// 可以返回上一页
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
onLoad((args) => {
// 从路由参数获取订单ID
if (args.id) {
// TODO: 根据ID获取订单详情
console.log('订单ID:', args.id)
}
// 初始化倒计时
countdownTime.value = formatCountdown(orderData.value.countdownSeconds)
startCountdown()
})
onUnload(() => {
// 清理倒计时
if (countdownTimer) {
clearInterval(countdownTimer)
}
})
</script>
<template>
<view class="pending-order-detail-page" :class="{ 'popup-open': showCostDetailPopup }">
<!-- 消息提示框 -->
<wd-message-box selector="wd-message-box-slot" />
<!-- 导航栏 -->
<navbar title="待接单" custom-class="!bg-[#F6F7F8]">
<template #right>
<view class="navbar-right-icons">
<wd-icon name="more" size="40rpx" color="#303133" class="mr-24rpx" />
<wd-icon name="eye" size="40rpx" color="#303133" />
</view>
</template>
</navbar>
<scroll-view class="content-scroll" scroll-y>
<!-- 倒计时提示 -->
<!-- <view class="countdown-tip">
<text class="countdown-text">还剩</text>
<text class="countdown-time">{{ countdownTime }}</text>
<text class="countdown-text">订单自动取消</text>
</view> -->
<!-- 订单卡片 -->
<view class="order-card" style="position: relative;">
<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>
<!-- 预约信息 -->
<view class="info-card">
<view class="info-card-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-card">
<view class="info-card-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 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">
<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="default"
plain
custom-class="!border-[#dcdfe6] !text-[#909399] !bg-[#F6F7F8] !border-2rpx !text-32rpx !px-32rpx !h-88rpx !leading-88rpx !rounded-8rpx !flex-1"
@click.stop="handleDecline"
>
放弃接单
</wd-button>
<wd-button
type="primary"
custom-class="!bg-[#4C9F44] !text-[#fff] !text-32rpx !px-32rpx !h-88rpx !leading-88rpx !rounded-8rpx !flex-1 !ml-20rpx"
@click.stop="handleAccept"
>
立即接单
</wd-button>
</view>
</view>
</template>
<style lang="scss">
page {
background-color: #f5f5f5;
height: 100%;
}
.pending-order-detail-page {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5;
}
.navbar-right-icons {
display: flex;
align-items: center;
}
.content-scroll {
flex: 1;
padding: 0 30rpx;
}
.countdown-tip {
display: flex;
align-items: center;
justify-content: center;
padding: 24rpx 0;
font-size: 28rpx;
color: #303133;
}
.countdown-text {
font-size: 28rpx;
color: #303133;
}
.countdown-time {
font-size: 32rpx;
font-weight: 500;
color: #ff5951;
margin: 0 8rpx;
}
.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-icon-circle {
width: 20rpx;
height: 20rpx;
border: 2rpx solid #4c9f44;
border-radius: 50%;
position: absolute;
top: 0;
left: 0;
}
.order-icon-line {
width: 2rpx;
height: 12rpx;
background-color: #4c9f44;
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
}
.order-card-title {
font-size: 28rpx;
color: #303133;
font-weight: 500;
}
.order-card-content {
display: flex;
align-items: center;
}
.service-name {
font-size: 30rpx;
color: #303133;
font-weight: 500;
flex: 1;
}
.price-btn {
position: absolute;
right: 0rpx;
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;
}
.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;
}
.info-item {
display: flex;
align-items: flex-start;
margin-bottom: 20rpx;
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;
}
.address-wrapper {
display: flex;
align-items: center;
flex: 1;
}
.order-sn-wrapper {
display: flex;
align-items: center;
}
.copy-text {
color: #4c9f44;
margin-left: 8rpx;
}
.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));
display: flex;
align-items: center;
box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.05);
z-index: 100;
}
// 当弹窗打开时,降低底部按钮的 z-index确保被弹窗遮盖
.pending-order-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>

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,7 @@
<view class="mx-30rpx coupon-bg">
<view class="flex items-center px-30rpx pt-30rpx pb-40rpx">
<view class="mr-30rpx">
<wd-img width="190rpx" height="190rpx" :src="order.room_msg.img" mode="scaleToFill" radius="10rpx"</wd-img>
<wd-img width="190rpx" height="190rpx" :src="order.room_msg.img" mode="scaleToFill" radius="10rpx"></wd-img>
</view>
<view class="flex-1">
<view class="flex justify-between items-center">

View File

@ -1,64 +1,154 @@
<!-- 使用 type="home" 属性设置首页其他页面不需要设置默认为page -->
<route lang="jsonc">{
"needLogin": false,
"layout": "tabbar",
"style": {
// 'custom' 表示开启自定义导航栏,默认 'default'
"navigationStyle": "custom",
"navigationBarTitleText": "首页"
"navigationBarTitleText": "订单"
}
}</route>
<template>
<view class="">
<view class="order-list sticky top-0 left-0 z-50 bg-[#F6F7F8] pb-10rpx">
<wd-navbar safeAreaInsetTop custom-class='!bg-[#F6F7F8]' :bordered="false" placeholder>
<view class="order-page pb-100rpx">
<!-- 顶部搜索和标签栏 -->
<view class="order-header bg-[#F6F7F8]">
<wd-navbar safe-area-inset-top custom-class="!bg-[#F6F7F8]" :bordered="false" placeholder>
<template #left>
<!-- <view class="search-box">
<wd-search v-model="keywords" hide-cancel placeholder-left light placeholder="搜索订单信息"></wd-search>
</view> -->
<view class="search-box flex items-center ml-26rpx" @click="Order.handleToSearch">
<view class="search-box" @click="handleToSearch">
<wd-search placeholder="搜索订单信息" hide-cancel disabled :placeholder-left="true"
placeholderStyle="text-align:left;padding-left: 24rpx;line-heigt: 44rpx;color: #C9C9C9; font-size: 32rpx;font-weight: normal;">
</wd-search>
custom-class="search-input" />
</view>
</template>
</wd-navbar>
<view class="tabs">
<wd-tabs v-model="tab" swipeable slidable="always" :lazy="false" @click="Order.handleChangeTabs">
<wd-tab title="全部" :name="AdminOrderStatusText.All"></wd-tab>
<wd-tab title="待使用" :name="AdminOrderStatusText.ToUse"></wd-tab>
<wd-tab title="使用中" :name="AdminOrderStatusText.Consuming"></wd-tab>
<wd-tab title="已完成" :name="AdminOrderStatusText.Finished"></wd-tab>
<wd-tabs v-model="activeTab" swipeable @change="handleTabChange">
<wd-tab title="全部" name="all" />
<wd-tab title="待接单" name="pending" />
<wd-tab title="待服务" name="waiting" />
<wd-tab title="服务中" name="serving" />
<wd-tab title="已完成" name="completed" />
</wd-tabs>
</view>
</view>
<view class="tabs mt-18rpx mx-30rpx">
<!-- 订单列表 -->
<!-- <scroll-view class="order-list" scroll-y> -->
<mescroll-body ref="mescrollItem0" @init="mescrollInit" @down="downCallback" @up="Order.upCallback" :down="downOption" :up="upOption">
<view class="mb-20rpx" v-for="(item, index) in list" :key="index">
<combo-card :type="OrderSource.Admin" :order="item" @refresh="Order.handleResetSearch"></combo-card>
<view v-for="(order, index) in filteredOrders" :key="order.id || index" class="order-item mx-30rpx">
<!-- 订单内容区域可点击跳转 -->
<view class="order-content" @click="handleOrderClick(order)">
<!-- 订单头部 -->
<view class="order-header-row">
<view class="order-title-row">
<wd-img width="28rpx" height="28rpx" :src="`${OSS}images/chayishi/order-icon.png`"
mode="aspectFill" />
<text class="order-title">订单</text>
</view>
<view class="order-status-row flex items-center">
<view class="order-status" :class="getStatusClass(order.status)">
{{ order.statusText }}
</view>
<view class="mt-4rpx">
<wd-count-down :time="30 * 60 * 1000" custom-class="!text-[#FF5951]" />
</view>
</view>
</mescroll-body>
</view>
<!-- 弹出框 -->
<wd-message-box></wd-message-box>
<!-- 服务信息 -->
<view class="order-service-info flex items-center justify-between">
<text class="service-name">{{ order.serviceName }}</text>
<view class="order-price">
<price-format color="#303133" :first-size="32" :second-size="32" :subscript-size="24"
:price="order.price" />
</view>
</view>
<!-- 价格 -->
<view class="mb-24rpx">
<text class="service-type" :class="getServiceTypeClass(order.serviceType)">
{{ order.serviceType }}
</text>
</view>
<!-- 预约时间 -->
<view class="order-info-row">
<text class="info-label">预约时间:</text>
<text class="info-value">{{ order.appointmentTime }}</text>
</view>
<!-- 地址 -->
<view class="order-info-row">
<text class="info-label">地址:</text>
<text class="info-value w-430rpx line-2">{{ order.address }}</text>
</view>
</view>
<!-- 操作按钮 -->
<view class="order-actions" @click.stop>
<wd-button v-for="(action, actionIndex) in getOrderActions(order.status)" :key="actionIndex"
:type="action.type" :plain="action.plain" :custom-class="action.customClass" size="small"
@click="handleAction(order, action.action)">
{{ action.text }}
</wd-button>
</view>
</view>
</mescroll-body>
<!-- 空状态 -->
<!-- <view v-if="filteredOrders.length === 0" class="empty-state">
<text class="empty-text">暂无订单</text>
</view>
</scroll-view> -->
<!-- 消息提示框 -->
<wd-message-box selector="wd-message-box-slot" />
<!-- 地图选择弹窗 -->
<wd-popup v-model="showMapSelectPopup" lock-scroll custom-style="border-radius: 32rpx 32rpx 0rpx 0rpx;"
position="bottom" :z-index="200" :close-on-click-overlay="true">
<view class="map-select-popup">
<!-- 拖拽指示条 -->
<view class="popup-handle" />
<!-- 地图选项列表 -->
<view class="map-options">
<view class="map-option-item" @click="handleSelectMap('gaode')">
<text class="map-option-text">高德地图</text>
</view>
<view class="map-divider" />
<view class="map-option-item" @click="handleSelectMap('tencent')">
<text class="map-option-text">腾讯地图</text>
</view>
<view class="map-divider" />
<view class="map-option-item" @click="handleSelectMap('apple')">
<text class="map-option-text">苹果地图</text>
</view>
</view>
<!-- 取消按钮 -->
<view class="map-cancel-wrapper">
<view class="map-cancel-divider" />
<view class="map-option-item map-cancel-item" @click="showMapSelectPopup = false">
<text class="map-option-text map-cancel-text">取消</text>
</view>
</view>
</view>
</wd-popup>
</view>
</template>
<script lang="ts" setup>
import { getTeaSpecialistOrderList } from '@/api/teaSpecialist-order'
import ComboCard from '@/components/order/ComboCard.vue'
import { useMessage } from 'wot-design-uni'
import PriceFormat from '@/components/PriceFormat.vue'
import { router } from '@/utils/tools'
import { onPageScroll, onReachBottom } from '@dcloudio/uni-app'
import useMescroll from "@/uni_modules/mescroll-uni/hooks/useMescroll.js"
import { OrderSource, AdminOrderStatusText, TeaSpecialistOrderStatusText, TeaSpecialistOrderStatusValue, AdminOrderStatusValue } from '@/utils/order'
import { router } from '@/utils/tools'
import { getStoreOrderList} from '@/api/order'
import { useStoreStore } from '@/store'
const useStore = useStoreStore()
const OSS = inject('OSS')
/* mescroll */
// mescroll
const { mescrollInit, downCallback, getMescroll } = useMescroll(onPageScroll, onReachBottom) // 调用mescroll的hook
const downOption = {
auto: true
@ -67,130 +157,433 @@
auto: true,
textNoMore: '~ 已经到底啦 ~', //无更多数据的提示
}
const list = ref<Array<any>>([]) // 茶艺师列表
const keywords = ref<string>('')
const orderStatus = ref<string>('')
// const canReset = ref<boolean>(false) // 避免onShow重复加载
const list = ref<Array<any>>([]) // 茶室列表
const keywords = ref<string>('') // 搜索关键词
// tab
const tab = ref<string>('all')
// 消息提示框
const message = useMessage('wd-message-box-slot')
// onShow(() => {
// if (canReset.value) {
// list.value = []
// getMescroll().resetUpScroll();
// }
// canReset.value = true
// })
// 地图选择弹窗
const showMapSelectPopup = ref(false)
// 当前要导航的订单
const currentNavigateOrder = ref<any>(null)
onLoad((args) => {
uni.$on('refreshOrderList', () => {
list.value = []
getMescroll().resetUpScroll()
})
// 当前选中的标签
const activeTab = ref('all')
// 根据传过来的参数决定显示哪个tab
if (args.orderStatus) {
tab.value = args.orderStatus
// 模拟订单数据
const orders = ref<Array<any>>([
{
id: 1,
serviceName: '苓苑共享茶室空间',
serviceType: '到店服务',
status: 'pending',
statusText: '待接单',
price: 212.20,
appointmentTime: '09/03 08:00-12:00',
address: '上海浦东新区新金桥路58号新银东大厦15楼F室',
timer: 30 * 60 * 60 * 1000,
},
{
id: 2,
serviceName: '韩梅梅预约单',
serviceType: '上门服务',
status: 'waiting',
statusText: '待服务',
price: 212.20,
appointmentTime: '09/03 08:00-12:00',
address: '上海浦东新区新金桥路58号新银东大厦15楼F室',
},
{
id: 3,
serviceName: '韩梅梅预约单',
serviceType: '上门服务',
status: 'waiting_arrived',
statusText: '待服务',
price: 212.20,
appointmentTime: '09/03 08:00-12:00',
address: '上海浦东新区新金桥路58号新银东大厦15楼F室',
timer: '30 * 60 * 60 * 1000',
},
{
id: 4,
serviceName: '韩梅梅预约单',
serviceType: '上门服务',
status: 'serving',
statusText: '服务中',
price: 212.20,
appointmentTime: '09/03 08:00-12:00',
address: '上海浦东新区新金桥路58号新银东大厦15楼F室',
timer: '99:52',
},
{
id: 5,
serviceName: '韩梅梅预约单',
serviceType: '上门服务',
status: 'serving',
statusText: '服务中',
price: 212.20,
appointmentTime: '09/03 08:00-12:00',
address: '上海浦东新区新金桥路58号新银东大厦15楼F室',
timer: '00:00',
},
{
id: 6,
serviceName: '韩梅梅预约单',
serviceType: '上门服务',
status: 'completed',
statusText: '完成',
price: 212.20,
appointmentTime: '09/03 08:00-12:00',
address: '上海浦东新区新金桥路58号新银东大厦15楼F室',
},
{
id: 7,
serviceName: '韩梅梅预约单',
serviceType: '上门服务',
status: 'cancelled',
statusText: '订单取消',
price: 212.20,
appointmentTime: '09/03 08:00-12:00',
address: '上海浦东新区新金桥路58号新银东大厦15楼F室',
},
])
// 根据标签过滤订单
const filteredOrders = computed(() => {
if (activeTab.value === 'all') {
return orders.value
}
// 处理待服务状态,包含 waiting 和 waiting_arrived
if (activeTab.value === 'waiting') {
return orders.value.filter(order => order.status === 'waiting' || order.status === 'waiting_arrived')
}
return orders.value.filter(order => order.status === activeTab.value)
})
onUnload(() => {
uni.$off('refreshOrderList')
// 获取状态样式类
function getStatusClass(status: string) {
const statusMap: Record<string, string> = {
pending: 'status-pending',
waiting: 'status-waiting',
serving: 'status-serving',
completed: 'status-completed',
cancelled: 'status-cancelled',
}
return statusMap[status] || ''
}
// 获取服务类型样式类
function getServiceTypeClass(serviceType: string) {
return serviceType === '到店服务' ? 'service-type-store' : 'service-type-home'
}
// 获取订单操作按钮
function getOrderActions(status: string) {
const actionMap: Record<string, Array<any>> = {
pending: [
{ text: '放弃接单', type: 'default', plain: true, customClass: '!border-[#9CA3AF] !text-[#303133] !bg-transparent !border-2rpx !text-28rpx !w-176rpx !h-60rpx !leading-64rpx !rounded-8rpx', action: 'decline' },
{ text: '立即接单', type: 'primary', plain: true, customClass: '!border-[#4c9f44] !text-[#4c9f44] !bg-transparent !border-2rpx !text-28rpx !w-176rpx !h-60rpx !leading-64rpx !rounded-8rpx', action: 'accept' },
],
waiting: [
{ text: '导航', type: 'default', plain: true, customClass: '!border-[#9CA3AF] !text-[#303133] !bg-transparent !border-2rpx !text-28rpx !w-176rpx !h-60rpx !leading-64rpx !rounded-8rpx', action: 'navigate' },
{ text: '立即出发', type: 'primary', plain: true, customClass: '!border-[#4c9f44] !text-[#4c9f44] !bg-transparent !border-2rpx !text-28rpx !w-176rpx !h-60rpx !leading-64rpx !rounded-8rpx', action: 'depart' },
],
waiting_arrived: [
{ text: '导航', type: 'default', plain: true, customClass: '!border-[#dcdfe6] !text-[#909399] !bg-transparent !border-2rpx !text-28rpx !w-176rpx !h-60rpx !leading-64rpx !rounded-8rpx', action: 'navigate' },
{ text: '已到达', type: 'primary', plain: true, customClass: '!border-[#4c9f44] !text-[#4c9f44] !bg-transparent !border-2rpx !text-28rpx !w-176rpx !h-60rpx !leading-64rpx !rounded-8rpx', action: 'arrived' },
],
serving: [
{ text: '完成服务', type: 'primary', plain: true, customClass: '!border-[#4c9f44] !text-[#4c9f44] !bg-transparent !border-2rpx !text-28rpx !w-176rpx !h-60rpx !leading-64rpx !rounded-8rpx', action: 'complete' },
],
completed: [
{ text: '删除订单', type: 'default', plain: true, customClass: '!border-[#dcdfe6] !text-[#909399] !bg-transparent !border-2rpx !text-28rpx !w-176rpx !h-60rpx !leading-64rpx !rounded-8rpx', action: 'delete' },
],
cancelled: [
{ text: '删除订单', type: 'default', plain: true, customClass: '!border-[#dcdfe6] !text-[#909399] !bg-transparent !border-2rpx !text-28rpx !w-176rpx !h-60rpx !leading-64rpx !rounded-8rpx', action: 'delete' },
],
}
return actionMap[status] || []
}
// 处理操作
function handleAction(order: any, action: string) {
console.log('操作:', action, order)
// 这里可以添加具体的操作逻辑
switch (action) {
case 'accept':
// 接单逻辑
break
case 'decline':
// 放弃接单逻辑
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') {
// 确认放弃接单的逻辑
console.log('确认放弃接单', order)
// TODO: 调用放弃接单的 API
}
}).catch(() => {
// 点击取消按钮
console.log('取消放弃接单')
})
break
case 'navigate':
// 导航逻辑 - 显示地图选择弹窗
currentNavigateOrder.value = order
showMapSelectPopup.value = true
break
case 'depart':
console.log('出发逻辑', order)
// 出发逻辑 - 跳转到详情页,状态改为 waiting_arrived已出发等待到达
router.navigateTo(`/pages/order/waiting-service-detail?id=${order.id}&status=waiting_arrived`)
break
case 'arrived':
// 已到达逻辑 - 直接跳转到服务中详情页
router.navigateTo(`/pages/order/serving-detail?id=${order.id}`)
break
case 'complete':
// 完成服务逻辑
break
case 'delete':
// 删除订单逻辑
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') {
// 确认删除订单的逻辑
console.log('确认删除订单', order)
// TODO: 调用删除订单的 API
uni.showToast({
title: '删除订单成功',
icon: 'success',
})
}
}).catch(() => {
// 点击取消按钮
console.log('取消删除订单')
})
break
}
}
// 切换标签
function handleTabChange(e: { index: number, name: string }) {
activeTab.value = e.name
}
// 跳转搜索
function handleToSearch() {
router.navigateTo('/pages/search/search')
}
// 点击订单跳转到详情页
function handleOrderClick(order: any) {
console.log('点击订单', order)
if (order.status === 'pending') {
router.navigateTo(`/pages/order/pending-order-detail?id=${order.id}`)
}
else if (order.status === 'waiting' || order.status === 'waiting_arrived') {
router.navigateTo(`/pages/order/waiting-service-detail?id=${order.id}`)
}
else if (order.status === 'cancelled') {
router.navigateTo(`/pages/order/cancelled-order-detail?id=${order.id}`)
}
}
// 选择地图应用
function handleSelectMap(mapType: string) {
if (!currentNavigateOrder.value) {
return
}
const order = currentNavigateOrder.value
const address = order.address || ''
// 默认使用上海的坐标(如果订单没有经纬度)
const latitude = order.latitude || 31.2304
const longitude = order.longitude || 121.4737
// 关闭弹窗
showMapSelectPopup.value = false
// 根据不同地图类型打开相应的应用
// #ifdef MP-WEIXIN
// 微信小程序使用 uni.openLocation会打开系统默认地图选择器
uni.openLocation({
latitude,
longitude,
name: order.serviceName || '',
address,
})
// #endif
// #ifndef MP-WEIXIN
// H5 和 App 使用 URL scheme
let mapUrl = ''
const encodedAddress = encodeURIComponent(address)
switch (mapType) {
case 'gaode':
// 高德地图
// #ifdef APP-PLUS
// App 使用 scheme
if (uni.getSystemInfoSync().platform === 'ios') {
mapUrl = `iosamap://path?sourceApplication=applicationName&dname=${encodedAddress}&dlat=${latitude}&dlon=${longitude}&dev=0&t=0`
}
else {
mapUrl = `androidamap://route?sourceApplication=amap&dname=${encodedAddress}&dlat=${latitude}&dlon=${longitude}&dev=0&t=0`
}
// #endif
// #ifdef H5
mapUrl = `https://uri.amap.com/navigation?to=${longitude},${latitude}&toName=${encodedAddress}&mode=car&policy=0&src=mypage&coordinate=gaode&callnative=0`
// #endif
break
case 'tencent':
// 腾讯地图
// #ifdef APP-PLUS
if (uni.getSystemInfoSync().platform === 'ios') {
mapUrl = `qqmap://map/routeplan?type=drive&to=${encodedAddress}&tocoord=${latitude},${longitude}`
}
else {
mapUrl = `qqmap://map/routeplan?type=drive&to=${encodedAddress}&tocoord=${latitude},${longitude}`
}
// #endif
// #ifdef H5
mapUrl = `https://apis.map.qq.com/uri/v1/routeplan?type=drive&to=${encodedAddress}&tocoord=${latitude},${longitude}&policy=LEAST_TIME&referer=myapp`
// #endif
break
case 'apple':
// 苹果地图
// #ifdef APP-PLUS
mapUrl = `http://maps.apple.com/?daddr=${latitude},${longitude}&dirflg=d`
// #endif
// #ifdef H5
mapUrl = `http://maps.apple.com/?daddr=${latitude},${longitude}&dirflg=d`
// #endif
break
}
if (mapUrl) {
// #ifdef APP-PLUS
const plusRuntime = (globalThis as any).plus?.runtime
if (plusRuntime) {
plusRuntime.openURL(mapUrl, (error: any) => {
console.error('打开地图失败:', error)
// 如果打开失败,尝试使用系统默认地图
uni.openLocation({
latitude,
longitude,
name: order.serviceName || '',
address,
})
})
}
else {
// 如果 plus 不可用,使用系统默认地图
uni.openLocation({
latitude,
longitude,
name: order.serviceName || '',
address,
})
}
// #endif
// #ifdef H5
window.open(mapUrl, '_blank')
// #endif
}
else {
// 如果没有指定地图 URL使用系统默认地图
uni.openLocation({
latitude,
longitude,
name: order.serviceName || '',
address,
})
}
// #endif
}
const Order = {
// 上拉加载的回调: 其中num:当前页 从1开始, size:每页数据条数,默认10
upCallback: (mescroll) => {
const filter = {
page: mescroll.num,
size: mescroll.size,
store_id: useStore.defaultStore.id,
order_status: orderStatus.value,
search: keywords.value,
}
// 需要留一下数据为空的时候显示的空数据图标内容
// const filter = {
// page: mescroll.num,
// size: mescroll.size,
// order_status: orderStatus.value,
// search: keywords.value
// }
getStoreOrderList(filter).then((res) => {
const curPageData = res.list || [] // 当前页数据
if(mescroll.num == 1) list.value = [] // 第一页需手动制空列表
list.value = list.value.concat(curPageData) //追加新数据
mescroll.endSuccess(curPageData.length, Boolean(res.more))
}).catch(() => {
// getTeaRoomOrderList(filter).then((res) => {
// const curPageData = res.list || [] // 当前页数据
// if(mescroll.num == 1) list.value = [] // 第一页需手动制空列表
// list.value = list.value.concat(curPageData) //追加新数据
// mescroll.endSuccess(curPageData.length, Boolean(res.more))
// }).catch(() => {
mescroll.endErr() // 请求失败, 结束加载
})
},
/**
* 跳转茶室搜索
*/
handleToSearch: () => {
uni.$on('refreshTeaRoomList', params => {
keywords.value = params.keywords
Order.handleResetSearch()
uni.$off('refreshTeaRoomList')
})
router.navigateTo(`/pages/search/search?keywords=${keywords.value}`)
},
/**
* 重置搜索
*/
handleResetSearch: () => {
list.value = []
getMescroll().resetUpScroll()
},
/**
* 切换tab
* @param e
*/
handleChangeTabs: (e: {index: number, name: string}) => {
tab.value = e.name
if (e.name === AdminOrderStatusText.Pending) {
orderStatus.value = '0'
} else {
orderStatus.value = AdminOrderStatusValue[e.name] || ''
}
// 切换tab时,重置当前的mescroll
list.value = []
getMescroll().resetUpScroll();
},
// 返回上一页
handleBack: () => {
uni.navigateBack({
delta: 1
})
// })
},
}
/**
* 监听默认店铺变化,刷新列表
*/
watch(
() => useStore.defaultStore.id,
(newId, oldId) => {
if (newId !== oldId) {
// 这里写刷新逻辑,比如重置列表并重新请求
list.value = []
getMescroll().resetUpScroll()
}
}
)
</script>
<style lang="scss">
page {
background-color: $cz-page-background;
background-color: #f5f5f5;
height: 100%;
}
.order-list {
:deep() {
.wd-navbar__left {
// .order-page {
// display: flex;
// flex-direction: column;
// }
.order-header {
position: sticky;
top: 0;
z-index: 100;
padding-bottom: 20rpx;
}
.search-box {
width: 100%;
padding: 0 30rpx;
margin-top: 20rpx;
:deep() {
.wd-search {
background: transparent !important;
width: 100% !important;
}
.wd-search__block {
background-color: #fff !important;
border-radius: 8rpx;
}
.wd-search__input {
padding-left: 24rpx !important;
font-size: 32rpx !important;
color: #c9c9c9 !important;
}
}
}
@ -219,33 +612,212 @@
}
}
.search-box {
// 地图选择弹窗样式
.map-select-popup {
position: relative;
background-color: #fff;
border-radius: 32rpx 32rpx 0 0;
padding-bottom: env(safe-area-inset-bottom);
}
.popup-handle {
width: 80rpx;
height: 8rpx;
background-color: #e5e5e5;
border-radius: 4rpx;
margin: 20rpx auto 30rpx;
}
.map-options {
padding: 0 30rpx;
}
.map-option-item {
display: flex;
height: 100%;
margin-right: 40px;
--wot-search-padding: 0;
--wot-search-side-padding: 0;
align-items: center;
justify-content: center;
padding: 32rpx 0;
cursor: pointer;
transition: background-color 0.2s;
:deep() {
.wd-search {
background: transparent !important;
width: 100% !important;
}
.wd-search__block {
background-color: #fff !important;
}
.wd-search__input {
// #ifdef MP
padding-left: 32px !important;
padding-right: 32px !important;
// #endif
// #ifndef MP
padding-right: 0 !important;
// #endif
&:active {
background-color: #f5f5f5;
}
}
.map-option-text {
font-size: 32rpx;
color: #303133;
line-height: 44rpx;
}
.map-divider {
height: 2rpx;
background-color: #f6f7f9;
margin: 0;
}
.map-cancel-wrapper {
margin-top: 20rpx;
padding: 0 30rpx 30rpx;
}
.map-cancel-divider {
height: 16rpx;
background-color: #f6f7f9;
margin: 0 -30rpx 20rpx;
}
.map-cancel-item {
border-radius: 16rpx;
background-color: #f6f7f9;
}
.map-cancel-text {
color: #303133;
}
.order-list {
flex: 1;
padding: 20rpx 30rpx;
}
.order-item {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
}
.order-content {
cursor: pointer;
}
.order-header-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
}
.order-title-row {
display: flex;
align-items: center;
gap: 8rpx;
}
.order-title {
font-size: 28rpx;
color: #303133;
font-weight: 500;
}
.order-status-row {
display: flex;
align-items: center;
gap: 12rpx;
}
.order-status {
font-size: 28rpx;
font-weight: 400;
&.status-pending {
color: #F29747;
}
&.status-waiting {
color: #4c9f44;
}
&.status-serving {
color: #4c9f44;
}
&.status-completed {
color: #606266;
}
&.status-cancelled {
color: #C9C9C9;
}
}
.order-timer {
font-size: 24rpx;
color: #ff5951;
}
.order-service-info {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 20rpx;
}
.service-name {
font-size: 30rpx;
color: #303133;
font-weight: 500;
}
.service-type {
font-size: 22rpx;
padding: 4rpx 12rpx;
border-radius: 4rpx;
white-space: nowrap;
border-radius: 4rpx;
&.service-type-store {
border: 2rpx solid #f55726;
color: #f55726;
}
&.service-type-home {
border: 2rpx solid #b8e6b8;
color: #4c9f44;
}
}
.order-price {
// margin-bottom: 20rpx;
}
.order-info-row {
display: flex;
align-items: flex-start;
margin-bottom: 16rpx;
font-size: 24rpx;
line-height: 34rpx;
}
.info-label {
color: #909399;
margin-right: 8rpx;
white-space: nowrap;
}
.info-value {
color: #606266;
flex: 1;
}
.order-actions {
display: flex;
justify-content: flex-end;
gap: 20rpx;
margin-top: 30rpx;
padding-top: 30rpx;
border-top: 2rpx solid #f6f7f9;
}
.empty-state {
text-align: center;
padding: 100rpx 0;
}
.empty-text {
font-size: 28rpx;
color: #909399;
}
</style>

View File

@ -0,0 +1,745 @@
<route lang="jsonc" type="page">{
"needLogin": false,
"layout": "default",
"style": {
"navigationStyle": "custom"
}
}</route>
<template>
<view class="pending-order-detail-page" :class="{ 'popup-open': showCostDetailPopup }">
<!-- 消息提示框 -->
<wd-message-box selector="wd-message-box-slot" />
<!-- 导航栏 -->
<navbar title="待接单" custom-class="!bg-[#F6F7F8]">
<template #right>
<view class="navbar-right-icons">
<wd-icon name="more" size="40rpx" color="#303133" class="mr-24rpx" />
<wd-icon name="eye" size="40rpx" color="#303133" />
</view>
</template>
</navbar>
<!-- 倒计时提示 -->
<!-- <view class="countdown-tip">
<text class="countdown-text">还剩</text>
<text class="countdown-time">{{ countdownTime }}</text>
<text class="countdown-text">订单自动取消</text>
</view> -->
<view class="mx-30rpx">
<view class="coupon-bg h-400rpx">
<!-- 订单卡片 -->
<view class="order-card" style="position: relative;">
<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="40rpx" height="40rpx" :src="`${OSS}images/chayishi/order-icon.png`"
mode="aspectFill" />
</view>
<text class="order-card-title ml-10rpx">订单</text>
</view>
<view class="order-card-content ml-50rpx">
<text class="service-name">{{ orderData.serviceName }}</text>
</view>
</view>
<!-- 预约信息 -->
<view class="px-30rpx pt-50rpx pb-40rpx">
<view class="info-card-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>
<!-- 茶艺服务 -->
<view class="info-card">
<view class="info-card-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 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">
<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" />
</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="default" plain
custom-class="!border-[#dcdfe6] !text-[#909399] !bg-[#F6F7F8] !border-2rpx !text-32rpx !px-32rpx !h-88rpx !leading-88rpx !rounded-8rpx !flex-1"
@click.stop="handleDecline">
放弃接单
</wd-button>
<wd-button type="primary"
custom-class="!bg-[#4C9F44] !text-[#fff] !text-32rpx !px-32rpx !h-88rpx !leading-88rpx !rounded-8rpx !flex-1 !ml-20rpx"
@click.stop="handleAccept">
立即接单
</wd-button>
</view>
</view>
</template>
<script lang="ts" setup>
import { useMessage } from 'wot-design-uni'
import PriceFormat from '@/components/PriceFormat.vue'
import { copy } 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 orderData = ref({
id: 1,
serviceName: '苓苑共享茶室空间',
serviceType: '到店服务',
price: 212.20,
appointmentTimeFull: '2025-03-18 09:00-12:00',
duration: '3小时',
servicePeople: 1,
teaName: '福鼎白茶 (3泡)',
teawareUsage: '客户自备',
notes: '', // 如果为空则不显示备注卡片
address: '青浦区仓桥路478号',
orderSn: '7327328627526903',
paymentMethod: '微信支付',
createTime: '2019-05-16 12:20:26',
payTime: '2019-05-16 13:20:26',
// 倒计时相关(秒)
countdownSeconds: 9 * 3600 + 30 * 60 + 48, // 09:30:48
})
// 倒计时
const countdownTime = ref('09:30:48')
let countdownTimer: any = null
// 格式化倒计时
function formatCountdown(seconds: number) {
const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
const secs = seconds % 60
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`
}
// 开始倒计时
function startCountdown() {
if (countdownTimer) {
clearInterval(countdownTimer)
}
countdownTimer = setInterval(() => {
if (orderData.value.countdownSeconds > 0) {
orderData.value.countdownSeconds--
countdownTime.value = formatCountdown(orderData.value.countdownSeconds)
}
else {
clearInterval(countdownTimer)
// 倒计时结束,可以触发自动取消逻辑
console.log('订单自动取消')
}
}, 1000)
}
// 复制订单编号
function handleCopyOrderSn() {
copy(orderData.value.orderSn)
uni.showToast({
title: '已复制',
icon: 'success',
})
}
// 导航
function handleNavigate() {
// TODO: 实现导航功能
console.log('导航到:', orderData.value.address)
uni.showToast({
title: '导航功能待实现',
icon: 'none',
})
}
// 价格点击(查看详情)
function handlePriceClick() {
showCostDetailPopup.value = true
}
// 放弃接单
function handleDecline() {
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') {
// TODO: 调用放弃接单的 API
console.log('确认放弃接单')
uni.showToast({
title: '放弃接单成功',
icon: 'success',
})
// 可以返回上一页
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
}).catch(() => {
console.log('取消放弃接单')
})
}
// 立即接单
function handleAccept() {
// TODO: 调用接单的 API
console.log('立即接单')
uni.showToast({
title: '接单成功',
icon: 'success',
})
// 可以返回上一页
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
onLoad((args) => {
// 从路由参数获取订单ID
if (args.id) {
// TODO: 根据ID获取订单详情
console.log('订单ID:', args.id)
}
// 初始化倒计时
countdownTime.value = formatCountdown(orderData.value.countdownSeconds)
startCountdown()
})
onUnload(() => {
// 清理倒计时
if (countdownTimer) {
clearInterval(countdownTimer)
}
})
</script>
<style lang="scss">
page {
background-color: #f5f5f5;
height: 100%;
}
.coupon-bg {
background-image: url(#{$OSS}images/order/order_image2.png);
background-repeat: no-repeat;
background-size: 100% 100%;
}
.pending-order-detail-page {
background-color: #f5f5f5;
}
.navbar-right-icons {
display: flex;
align-items: center;
}
.content-scroll {
flex: 1;
padding: 0 30rpx;
}
.countdown-tip {
display: flex;
align-items: center;
justify-content: center;
padding: 24rpx 0;
font-size: 28rpx;
color: #303133;
}
.countdown-text {
font-size: 28rpx;
color: #303133;
}
.countdown-time {
font-size: 32rpx;
font-weight: 500;
color: #ff5951;
margin: 0 8rpx;
}
.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-icon-circle {
width: 20rpx;
height: 20rpx;
border: 2rpx solid #4c9f44;
border-radius: 50%;
position: absolute;
top: 0;
left: 0;
}
.order-icon-line {
width: 2rpx;
height: 12rpx;
background-color: #4c9f44;
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
}
.order-card-title {
font-size: 28rpx;
color: #303133;
font-weight: 500;
}
.order-card-content {
display: flex;
align-items: center;
}
.service-name {
font-size: 30rpx;
color: #303133;
font-weight: 500;
flex: 1;
}
.price-btn {
position: absolute;
right: 0rpx;
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;
}
.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;
}
.info-item {
display: flex;
align-items: flex-start;
margin-bottom: 20rpx;
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;
}
.address-wrapper {
display: flex;
align-items: center;
flex: 1;
}
.order-sn-wrapper {
display: flex;
align-items: center;
}
.copy-text {
color: #4c9f44;
margin-left: 8rpx;
}
.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));
display: flex;
align-items: center;
box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.05);
z-index: 100;
}
// 当弹窗打开时,降低底部按钮的 z-index确保被弹窗遮盖
.pending-order-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>