| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503 |
- <template>
- <view class="detail-container">
- <!-- 加载动画 -->
- <view class="loading-container" v-if="pageLoading">
- <view class="skeleton-header">
- <view class="skeleton-line skeleton-ani" style="width: 30%; height: 36rpx;"></view>
- <view class="skeleton-line skeleton-ani" style="width: 20%; height: 36rpx;"></view>
- </view>
- <view class="skeleton-card" v-for="j in 3" :key="'c' + j">
- <view class="skeleton-line skeleton-ani" style="width: 60%; height: 28rpx; margin-bottom: 20rpx;"></view>
- <view class="skeleton-line skeleton-ani" style="width: 90%; height: 24rpx; margin-bottom: 14rpx;"></view>
- <view class="skeleton-line skeleton-ani" style="width: 75%; height: 24rpx;"></view>
- </view>
- </view>
- <template v-else>
- <!-- 顶部状态区 -->
- <view class="detail-header pre-accept">
- <view class="status-row">
- <text class="status-title">待接单</text>
- <text class="status-price">¥{{ orderDetail.fulfillmentCommission }}</text>
- </view>
- <view class="status-desc">待接单订单,接单后可查看完整联系方式</view>
- </view>
- <scroll-view scroll-y class="detail-content">
- <!-- 宠物档案卡片 -->
- <view class="white-card pet-bar">
- <image class="pb-avatar" :src="orderDetail.petAvatar" mode="aspectFill"></image>
- <view class="pb-info">
- <view class="pb-name-row">
- <text class="pb-name">{{ orderDetail.petName }}</text>
- <text class="pb-breed">{{ orderDetail.petBreed }}</text>
- </view>
- <view class="pb-tags">
- <text class="pb-tag">{{ orderDetail.serviceName }}</text>
- </view>
- </view>
- <view class="pb-actions">
- <view class="view-profile-btn" @click="showPetProfile">
- <text>宠物档案</text>
- <text class="arrow">></text>
- </view>
- </view>
- </view>
- <!-- 用户档案卡片(待接单状态脱敏) -->
- <view class="white-card user-profile-card">
- <view class="tl-title-row">
- <view class="orange-bar"></view>
- <text class="tl-title">用户档案</text>
- <text class="tl-hint">接单后可查看完整信息</text>
- </view>
- <view class="bi-row">
- <image class="bi-icon" src="/static/icons/user.svg"></image>
- <view class="bi-content">
- <text class="bi-label">联系人</text>
- <text class="bi-val bi-blur">接单后可见</text>
- </view>
- </view>
- <view class="bi-row">
- <image class="bi-icon" src="/static/icons/phone.svg"></image>
- <view class="bi-content">
- <text class="bi-label">联系电话</text>
- <text class="bi-val bi-blur">接单后可见</text>
- </view>
- </view>
- <view class="bi-row no-border">
- <image class="bi-icon" src="/static/icons/location.svg"></image>
- <view class="bi-content">
- <text class="bi-label">详细地址</text>
- <text class="bi-val bi-blur">接单后可见</text>
- </view>
- </view>
- </view>
- <!-- 路线及服务信息 -->
- <view class="white-card service-info-card">
- <view class="tl-title-row">
- <view class="orange-bar"></view>
- <text class="tl-title">服务详情</text>
- </view>
- <view class="si-row time-row">
- <image class="si-icon outline" src="/static/icons/clock.svg"></image>
- <view class="si-content">
- <text class="si-label">服务时间</text>
- <text class="si-val">{{ orderDetail.time }}</text>
- </view>
- </view>
- <!-- 接送类型的地址展现 -->
- <template v-if="orderDetail.type === 1">
- <view class="si-row addr-row start-addr">
- <view class="icon-circle start">起</view>
- <view class="route-line-vertical"></view>
- <view class="si-content">
- <text class="si-addr-title">{{ orderDetail.startLocation }}</text>
- <text class="si-addr-desc">{{ orderDetail.startAddress }}</text>
- </view>
- <view class="nav-btn-circle" @click="openNavigation('start')">
- <image class="nav-arrow" src="/static/icons/nav_arrow.svg"></image>
- </view>
- </view>
- <view class="si-row addr-row end-addr">
- <view class="icon-circle end">终</view>
- <view class="si-content">
- <text class="si-addr-title">{{ orderDetail.endLocation }}</text>
- <text class="si-addr-desc">{{ orderDetail.endAddress }}</text>
- </view>
- <view class="nav-btn-circle" @click="openNavigation('end')">
- <image class="nav-arrow" src="/static/icons/nav_arrow.svg"></image>
- </view>
- </view>
- </template>
- <!-- 喂遛/洗护类型的地址展现 -->
- <template v-else>
- <view class="si-row addr-row end-addr">
- <view class="icon-circle service">服</view>
- <view class="si-content">
- <text class="si-addr-title">{{ orderDetail.endLocation }}</text>
- <text class="si-addr-desc">{{ orderDetail.endAddress }}</text>
- </view>
- <view class="nav-btn-circle" @click="openNavigation('end')">
- <image class="nav-arrow" src="/static/icons/nav_arrow.svg"></image>
- </view>
- </view>
- </template>
- <view class="si-row no-border">
- <image class="si-icon outline custom-icon-file" src="/static/icons/file.svg"></image>
- <view class="si-content">
- <text class="si-label">订单备注</text>
- <text class="si-val">{{ orderDetail.remark || '-' }}</text>
- </view>
- </view>
- </view>
- <!-- 订单日志 -->
- <view class="white-card logs-card">
- <view class="tl-title-row">
- <view class="orange-bar"></view>
- <text class="tl-title">订单日志</text>
- </view>
- <view class="tl-list">
- <view class="tl-item" v-for="(log, index) in orderLogs" :key="index">
- <view class="tl-marker" :class="{ 'active': index === 0 }">
- <view class="tl-dot-inner" v-if="index === 0"></view>
- </view>
- <view class="tl-content-row">
- <view class="tl-header">
- <text class="tl-status">{{ log.title || '系统记录' }}</text>
- <text class="tl-time">{{ log.time }}</text>
- </view>
- <text class="tl-remark" v-if="log.content">{{ log.content }}</text>
- </view>
- </view>
- </view>
- </view>
- <view style="height: 60rpx;"></view>
- </scroll-view>
- </template>
- <!-- 宠物档案弹窗 (引用首页逻辑) -->
- <view class="pet-modal-mask" v-if="showPetModal" @click="closePetProfile">
- <view class="pet-modal-content" @click.stop>
- <view class="pet-modal-header">
- <text class="pet-modal-title">宠物档案</text>
- <view class="close-icon-btn" @click="closePetProfile">×</view>
- </view>
- <scroll-view scroll-y class="pet-modal-scroll">
- <view class="pet-base-info">
- <image class="pm-avatar" :src="orderDetail.petAvatar" mode="aspectFill"></image>
- <view class="pm-info-text">
- <view class="pm-name-row">
- <text class="pm-name">{{ orderDetail.petName }}</text>
- <view class="pm-gender" v-if="orderDetail.petGender === 'M'">
- <text class="gender-icon">♂</text><text>公</text>
- </view>
- <view class="pm-gender female" v-else-if="orderDetail.petGender === 'F'">
- <text class="gender-icon">♀</text><text>母</text>
- </view>
- </view>
- <text class="pm-breed">品种:{{ orderDetail.petBreed }}</text>
- </view>
- </view>
- <view class="pm-detail-grid">
- <view class="pm-grid-item half">
- <text class="pm-label">年龄</text>
- <text class="pm-val">{{ orderDetail.petAge || '未知' }}</text>
- </view>
- <view class="pm-grid-item half">
- <text class="pm-label">体重</text>
- <text class="pm-val">{{ orderDetail.petWeight || '未知' }}</text>
- </view>
- <view class="pm-grid-item full">
- <text class="pm-label">性格</text>
- <text class="pm-val">{{ orderDetail.petPersonality || '暂无' }}</text>
- </view>
- <view class="pm-grid-item full">
- <text class="pm-label">备注/禁忌</text>
- <text class="pm-val">{{ orderDetail.petNotes || '暂无说明' }}</text>
- </view>
- </view>
- <view style="height: 40rpx;"></view>
- <button class="pm-bottom-close" @click="closePetProfile">关闭</button>
- <view style="height: 20rpx;"></view>
- </scroll-view>
- </view>
- </view>
- <!-- 导航地图选择 -->
- <view class="nav-modal-mask" v-if="showNavModal" @click="closeNavModal">
- <view class="nav-action-sheet" @click.stop>
- <view class="nav-sheet-title">选择地图进行导航</view>
- <view class="nav-sheet-item" @click="chooseMap('高德')">高德地图</view>
- <view class="nav-sheet-item" @click="chooseMap('腾讯')">腾讯地图</view>
- <view class="nav-sheet-item" @click="chooseMap('百度')">百度地图</view>
- <view class="nav-sheet-gap"></view>
- <view class="nav-sheet-item cancel" @click="closeNavModal">取消</view>
- </view>
- </view>
- </view>
- </template>
- <script>
- import { getOrderInfo } from '@/api/order/subOrder'
- import { getOrderLogs } from '@/api/order/subOrderLog'
- import { listAllService } from '@/api/service/list'
- import { getPetDetail } from '@/api/archieves/pet'
- import { reportGps } from '@/utils/gps'
- /**
- * 订单大厅详情页 (数据修正版)
- * @Author: Antigravity
- */
- export default {
- data() {
- return {
- orderId: null,
- pageLoading: true,
- orderDetail: {
- type: 1, fulfillmentCommission: '0.00', time: '',
- petAvatar: '/static/dog.png', petName: '', petBreed: '',
- serviceName: '', startLocation: '', startAddress: '', endLocation: '', endAddress: '',
- remark: '', orderNo: '', createTime: '', fromLat: null, fromLng: null, toLat: null, toLng: null,
- ownerName: '', ownerPhone: '', petAge: '', petWeight: '', petGender: 'M', petPersonality: '', petNotes: ''
- },
- orderLogs: [],
- serviceList: [],
- showNavModal: false,
- navTargetPointType: '',
- showPetModal: false
- }
- },
- async onLoad(options) {
- if (options.id) this.orderId = options.id
- this.pageLoading = true
- try {
- await this.loadServiceList()
- await this.loadOrderDetail()
- } finally {
- this.pageLoading = false
- }
- },
- methods: {
- async loadServiceList() {
- try {
- const res = await listAllService()
- this.serviceList = res.data || []
- } catch (err) { console.error('获取服务类型失败:', err); uni.showToast({ title: err.message || err.msg || '请求失败', icon: 'none' }) }
- },
- async loadOrderDetail() {
- if (!this.orderId) return
- try {
- const res = await getOrderInfo(this.orderId)
- const order = res.data
- if (!order) return
-
- const serviceInfo = this.serviceList.find(s => s.id === order.service)
- const mode = serviceInfo?.mode || 0
- this.orderDetail = {
- id: order.id,
- type: mode === 1 ? 1 : 2,
- fulfillmentCommission: (order.fulfillmentCommission / 100).toFixed(2),
- time: order.serviceTime || '',
- petAvatar: order.petAvatar || '/static/dog.png',
- petName: order.petName || '宠物',
- petBreed: order.breed || '未知品种',
- serviceName: serviceInfo?.name || '未知服务',
- startLocation: order.fromAddress || '暂无起点',
- startAddress: order.fromAddress || '',
- fromLat: order.fromLat, fromLng: order.fromLng,
- endLocation: order.toAddress || '查看详情可见',
- endAddress: order.toAddress || '',
- toLat: order.toLat, toLng: order.toLng,
- remark: order.remark || '',
- orderNo: order.code || 'T' + order.id,
- createTime: order.createDateTime || order.serviceTime,
- ownerName: order.contact || '匿名用户',
- ownerPhone: order.contactPhoneNumber || ''
- }
-
- // 1. 加载宠物详情
- if (order.usrPet) {
- this.loadPetInfo(order.usrPet)
- }
- // 2. 加载时间轴日志
- this.loadLogs()
- } catch (err) { console.error('获取详情失败:', err); uni.showToast({ title: err.message || err.msg || '请求失败', icon: 'none' }) }
- },
- async loadPetInfo(petId) {
- try {
- const res = await getPetDetail(petId)
- const pet = res.data
- if (pet) {
- this.orderDetail.petAge = pet.age || '未知'
- this.orderDetail.petWeight = pet.weight || '未知'
- this.orderDetail.petGender = pet.sex || 'M'
- this.orderDetail.petPersonality = pet.personality || '暂无标签'
- this.orderDetail.petNotes = pet.remark || '暂无说明'
- }
- } catch (err) { console.error('获取宠物详情失败:', err); uni.showToast({ title: err.message || err.msg || '请求失败', icon: 'none' }) }
- },
- async loadLogs() {
- try {
- const res = await getOrderLogs(this.orderId)
- const list = Array.isArray(res.data) ? res.data : (res.rows || [])
- this.orderLogs = list.map(item => ({
- title: item.title || '状态更新',
- time: item.createTime || '',
- content: item.content || ''
- }))
- } catch (err) { console.error('获取日志失败:', err); uni.showToast({ title: err.message || err.msg || '请求失败', icon: 'none' }) }
- },
- showPetProfile() { this.showPetModal = true },
- closePetProfile() { this.showPetModal = false },
- openNavigation(type) { this.navTargetPointType = type; this.showNavModal = true },
- closeNavModal() { this.showNavModal = false },
- chooseMap(mapType) {
- let pointType = this.navTargetPointType;
- let name = pointType === 'start' ? (this.orderDetail.startAddress || '起点') : (this.orderDetail.endAddress || '终点');
- let address = name;
- let latitude = pointType === 'start' ? Number(this.orderDetail.fromLat) : Number(this.orderDetail.toLat);
- let longitude = pointType === 'start' ? Number(this.orderDetail.fromLng) : Number(this.orderDetail.toLng);
- this.showNavModal = false;
-
- const navigateTo = (lat, lng) => {
- uni.openLocation({ latitude: lat, longitude: lng, name: name, address: address });
- };
- if (latitude && longitude && !isNaN(latitude)) {
- navigateTo(latitude, longitude);
- } else {
- uni.showLoading({ title: '定位中...' });
- reportGps(true).then(res => {
- uni.hideLoading(); navigateTo(res.latitude, res.longitude);
- }).catch(() => uni.hideLoading());
- }
- }
- }
- }
- </script>
- <style lang="scss">
- .detail-container {
- min-height: 100vh;
- background-color: #f8f8f8;
- display: flex;
- flex-direction: column;
- }
- .detail-header {
- padding: 40rpx 40rpx 80rpx;
- background: linear-gradient(135deg, #FF9800 0%, #FFB74D 100%);
- color: #fff;
- .status-row {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20rpx;
- .status-title { font-size: 44rpx; font-weight: bold; }
- .status-price { font-size: 40rpx; font-weight: bold; }
- }
- .status-desc { font-size: 26rpx; opacity: 0.9; }
- }
- .detail-content {
- flex: 1;
- padding: 20rpx;
- margin-top: -40rpx;
- }
- .white-card {
- background: #fff;
- border-radius: 24rpx;
- padding: 30rpx;
- margin-bottom: 24rpx;
- box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.03);
- }
- /* 宠物栏 */
- .pet-bar {
- display: flex;
- align-items: center;
- .pb-avatar { width: 110rpx; height: 110rpx; border-radius: 50%; margin-right: 24rpx; background: #f0f0f0; border: 4rpx solid #fff; box-shadow: 0 4rpx 10rpx rgba(0,0,0,0.05); }
- .pb-info {
- flex: 1;
- .pb-name { font-size: 34rpx; font-weight: bold; color: #333; margin-right: 12rpx; }
- .pb-breed { font-size: 24rpx; color: #999; }
- .pb-tags { margin-top: 10rpx; .pb-tag { font-size: 22rpx; color: #FF9800; background: rgba(255,152,0,0.1); padding: 4rpx 16rpx; border-radius: 6rpx; } }
- }
- .view-profile-btn {
- display: flex; align-items: center; background: #FFF3E0; padding: 10rpx 20rpx; border-radius: 30rpx;
- text { font-size: 24rpx; color: #FF9800; font-weight: bold; }
- .arrow { margin-left: 6rpx; font-size: 20rpx; }
- }
- }
- /* 用户档案 */
- .user-profile-card {
- .bi-row {
- display: flex; align-items: flex-start; padding: 20rpx 0; border-bottom: 1rpx solid #f5f5f5;
- &.no-border { border-bottom: none; }
- .bi-icon { width: 32rpx; height: 32rpx; margin-right: 20rpx; margin-top: 6rpx; opacity: 0.6; }
- .bi-content {
- flex: 1;
- .bi-label { font-size: 24rpx; color: #999; margin-bottom: 4rpx; display: block; }
- .bi-val { font-size: 28rpx; color: #333; font-weight: 500; }
- &.bi-blur { color: #bbb; font-weight: normal; }
- }
- }
- }
- /* 服务详情 */
- .service-info-card {
- .si-row {
- display: flex; padding: 24rpx 0; border-bottom: 1rpx solid #f5f5f5;
- &.no-border { border-bottom: none; }
- .si-icon { width: 36rpx; height: 36rpx; margin-right: 20rpx; margin-top: 4rpx; }
- .si-content {
- flex: 1;
- .si-label { font-size: 24rpx; color: #999; display: block; margin-bottom: 6rpx; }
- .si-val { font-size: 28rpx; color: #333; }
- .si-addr-title { font-size: 30rpx; font-weight: bold; color: #333; display: block; margin-bottom: 8rpx; }
- .si-addr-desc { font-size: 24rpx; color: #666; }
- }
- }
- }
- .addr-row {
- position: relative;
- .icon-circle {
- width: 44rpx; height: 44rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center;
- font-size: 22rpx; color: #fff; margin-right: 20rpx; flex-shrink: 0; margin-top: 6rpx;
- &.start { background: #FF9800; }
- &.end { background: #4CAF50; }
- &.service { background: #2196F3; }
- }
- .route-line-vertical { position: absolute; left: 22rpx; top: 56rpx; bottom: -10rpx; width: 2rpx; border-left: 2rpx dashed #ddd; }
- }
- .nav-btn-circle { width: 56rpx; height: 56rpx; border-radius: 50%; background: #FFF3E0; display: flex; align-items: center; justify-content: center; margin-left: 20rpx; .nav-arrow { width: 28rpx; height: 28rpx; } }
- /* 日志板块 */
- .tl-title-row { display: flex; align-items: center; margin-bottom: 30rpx; }
- .orange-bar { width: 8rpx; height: 32rpx; background-color: #FF9800; margin-right: 16rpx; border-radius: 4rpx; }
- .tl-title { font-size: 30rpx; font-weight: bold; color: #333; }
- .tl-hint { font-size: 22rpx; color: #FF9800; margin-left: auto; }
- .tl-list { padding-left: 10rpx; }
- .tl-item { display: flex; position: relative; padding-bottom: 40rpx; }
- .tl-item:last-child { padding-bottom: 0; }
- .tl-marker { width: 16rpx; height: 16rpx; border-radius: 50%; background-color: #E0E0E0; position: absolute; left: 0; top: 12rpx; z-index: 2; display: flex; justify-content: center; align-items: center; }
- .tl-marker.active { background-color: #fff; border: 3rpx solid #FF9800; width: 18rpx; height: 18rpx; left: -1rpx; }
- .tl-dot-inner { width: 10rpx; height: 10rpx; border-radius: 50%; background-color: #FF9800; }
- .tl-item:not(:last-child)::after { content: ''; position: absolute; left: 7rpx; top: 32rpx; bottom: -10rpx; width: 2rpx; background-color: #FFE0B2; z-index: 1; }
- .tl-content-row { margin-left: 44rpx; display: flex; flex-direction: column; width: 100%; }
- .tl-header { display: flex; justify-content: space-between; align-items: center; width: 100%; margin-bottom: 12rpx; }
- .tl-status { font-size: 28rpx; color: #333; font-weight: bold; }
- .tl-time { font-size: 24rpx; color: #999; }
- .tl-remark { font-size: 24rpx; color: #666; background-color: #F9F9F9; padding: 16rpx; border-radius: 12rpx; line-height: 1.5; }
- /* 弹窗通用 */
- .pet-modal-mask { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); z-index: 999; display: flex; align-items: center; justify-content: center; }
- .pet-modal-content { width: 680rpx; background: #fff; border-radius: 24rpx; overflow: hidden; }
- .pet-modal-header { padding: 30rpx; display: flex; justify-content: space-between; align-items: center; border-bottom: 1rpx solid #f5f5f5; .pet-modal-title { font-size: 34rpx; font-weight: bold; } .close-icon-btn { font-size: 40rpx; color: #999; } }
- .pet-modal-scroll { padding: 30rpx; height: 75vh; box-sizing: border-box; }
- .pet-base-info { display: flex; align-items: center; margin-bottom: 40rpx; .pm-avatar { width: 120rpx; height: 120rpx; border-radius: 50%; margin-right: 30rpx; } }
- .pm-name-row { display: flex; align-items: center; margin-bottom: 12rpx; .pm-name { font-size: 36rpx; font-weight: bold; margin-right: 20rpx; } .pm-gender { background: #E3F2FD; padding: 4rpx 16rpx; border-radius: 20rpx; text { font-size: 22rpx; color: #1E88E5; } &.female { background: #FCE4EC; text { color: #D81B60; } } } }
- .pm-breed { font-size: 26rpx; color: #999; }
- .pm-detail-grid { display: flex; flex-wrap: wrap; gap: 20rpx; .pm-grid-item { background: #f8f8f8; padding: 24rpx; border-radius: 16rpx; display: flex; flex-direction: column; &.half { width: calc(50% - 10rpx); box-sizing: border-box; } &.full { width: 100%; box-sizing: border-box; } .pm-label { font-size: 24rpx; color: #999; margin-bottom: 8rpx; } .pm-val { font-size: 28rpx; color: #333; font-weight: 500; } } }
- .pm-bottom-close { margin-top: 40rpx; background: #f5f5f5; color: #666; border-radius: 40rpx; font-size: 30rpx; }
- .nav-modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.4); z-index: 1000; }
- .nav-action-sheet { position: absolute; bottom: 0; left: 0; right: 0; background: #fff; border-radius: 24rpx 24rpx 0 0; padding-bottom: env(safe-area-inset-bottom);
- .nav-sheet-title { padding: 30rpx; text-align: center; font-size: 28rpx; color: #999; border-bottom: 1rpx solid #eee; }
- .nav-sheet-item { padding: 30rpx; text-align: center; font-size: 32rpx; color: #333; border-bottom: 1rpx solid #eee; &.cancel { color: #f26d6d; border-bottom: none; } }
- .nav-sheet-gap { height: 12rpx; background: #f5f5f5; }
- }
- .loading-container { padding: 100rpx 40rpx; .skeleton-header { display: flex; justify-content: space-between; margin-bottom: 60rpx; } .skeleton-card { background: #fff; border-radius: 20rpx; padding: 30rpx; margin-bottom: 30rpx; } .skeleton-line { background: #f0f0f0; border-radius: 4rpx; } .skeleton-ani { animation: skeleton-loading 1.4s ease infinite; } }
- @keyframes skeleton-loading { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } }
- </style>
|