| | |
| | | <template> |
| | | <view style="padding: 40rpx;"> |
| | | <button @click="speak('订单已收到,开始播报')">开始播报</button> |
| | | <view class="hall-page"> |
| | | <view v-if="showOrderDetail && isStatusDetail" class="order-detail-map-layer" :style="{ top: statusBarHeight + 'px' }"> |
| | | <map |
| | | class="order-detail-map-layer__map" |
| | | :latitude="detailMap.center.latitude" |
| | | :longitude="detailMap.center.longitude" |
| | | :markers="detailMap.markers" |
| | | :polyline="detailMap.polyline" |
| | | :include-points="detailMap.includePoints" |
| | | :scale="detailMap.scale" |
| | | :enable-zoom="true" |
| | | :enable-scroll="true" |
| | | ></map> |
| | | </view> |
| | | |
| | | <view class="hall-page__header" :style="{ paddingTop: statusBarHeight + 'px' }"> |
| | | <view class="hall-page__user-row"> |
| | | <view class="hall-page__user"> |
| | | <image class="hall-page__avatar" src="/static/image/login_bg@2x.png" mode="aspectFill"></image> |
| | | <text class="hall-page__name">汤子新</text> |
| | | </view> |
| | | <view class="hall-page__status"> |
| | | <view class="hall-page__status-dot"></view> |
| | | <text class="hall-page__status-text">接单中</text> |
| | | <text class="hall-page__status-arrow">▼</text> |
| | | </view> |
| | | <view class="hall-page__user" style="opacity: 0;"> |
| | | <image class="hall-page__avatar" src="/static/image/login_bg@2x.png" mode="aspectFill"></image> |
| | | <text class="hall-page__name">汤子新</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="hall-page__stats"> |
| | | <view v-for="item in stats" :key="item.label" class="hall-page__stat-item"> |
| | | <text class="hall-page__stat-value">{{ item.value }}</text> |
| | | <text class="hall-page__stat-label">{{ item.label }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="hall-page__tabs"> |
| | | <view v-for="tab in tabs" :key="tab.value" class="hall-page__tab" :class="{ 'hall-page__tab--active': activeTab === tab.value }" @click="activeTab = tab.value"> |
| | | <text class="hall-page__tab-text">{{ tab.label }}</text> |
| | | <text v-if="tab.count" class="hall-page__tab-count">{{ tab.count }}</text> |
| | | <view v-if="activeTab === tab.value" class="hall-page__tab-line"></view> |
| | | </view> |
| | | <view class="hall-page__filter" @click="toggleFilterPopup(true)"> |
| | | <text class="hall-page__filter-text" :class="{ 'hall-page__filter-text--active': showFilterPopup }">筛选</text> |
| | | <image :src="showFilterPopup ? '/static/image/ic_shaixuan_sel@2x.png' : '/static/image/ic_shaixuan@2x.png'" mode="widthFix" class="hall-page__filter-icon"></image> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-if="showFilterPopup" class="filter-popup" :style="{ top: headerHeight + 'px', bottom: tabbarHeight + 'px' }" @click="toggleFilterPopup(false)"> |
| | | <view class="filter-popup__panel" @click.stop> |
| | | <view v-for="section in filterSections" :key="section.key" class="filter-popup__section"> |
| | | <text class="filter-popup__title">{{ section.title }}</text> |
| | | <view class="filter-popup__options"> |
| | | <view |
| | | v-for="option in section.options" |
| | | :key="option" |
| | | class="filter-popup__option" |
| | | :class="{ 'filter-popup__option--active': selectedFilters[section.key] === option }" |
| | | @click="selectFilter(section.key, option)" |
| | | > |
| | | <text class="filter-popup__option-text">{{ option }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="filter-popup__actions"> |
| | | <button class="filter-popup__button filter-popup__button--reset" hover-class="filter-popup__button--hover" @click="resetFilters">重置</button> |
| | | <button class="filter-popup__button filter-popup__button--confirm" hover-class="filter-popup__button--hover" @click="confirmFilters">确认</button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <scroll-view class="hall-page__body" scroll-y :style="bodyStyle"> |
| | | <view v-if="currentOrderList.length" class="hall-page__list"> |
| | | <view v-for="item in currentOrderList" :key="item.id" class="order-card" @click="openDetailPopup(item)"> |
| | | <view class="order-card__head"> |
| | | <view class="order-card__time"> |
| | | <text class="order-card__time-main">{{ item.time }}</text> |
| | | <text class="order-card__time-sub">送达</text> |
| | | </view> |
| | | <view v-if="activeTab === 'hall'" class="order-card__price-wrap"> |
| | | <text v-if="item.serialNo" class="order-card__serial">#{{ item.serialNo }}</text> |
| | | <text class="order-card__price">{{ item.price }}</text> |
| | | </view> |
| | | <view v-else class="order-card__price-wrap order-card__price-wrap--serial-only"> |
| | | <text v-if="item.serialNo" class="order-card__serial">#{{ item.serialNo }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="order-card__meta"> |
| | | <view class="order-card__tags"> |
| | | <view v-for="tag in item.tags" :key="tag.text" class="order-card__tag-wrap"> |
| | | <image |
| | | v-if="getTagImage(tag.text)" |
| | | class="order-card__tag-icon" |
| | | :src="getTagImage(tag.text)" |
| | | mode="widthFix" |
| | | ></image> |
| | | <text v-else class="order-card__tag" :class="tag.type ? 'order-card__tag--' + tag.type : ''">{{ tag.text }}</text> |
| | | </view> |
| | | </view> |
| | | <text v-if="activeTab === 'hall'" class="order-card__extra">含加急¥{{ item.extra }}</text> |
| | | </view> |
| | | |
| | | <view class="order-card__route"> |
| | | <view class="order-card__route-side"> |
| | | <text class="order-card__distance-top">{{ item.distanceTop }}</text> |
| | | <view class="order-card__line"></view> |
| | | <text class="order-card__distance-bottom">{{ item.distanceBottom }}</text> |
| | | </view> |
| | | <view class="order-card__route-main"> |
| | | <view class="order-card__route-item"> |
| | | <view class="order-card__route-texts"> |
| | | <text class="order-card__route-title">{{ item.fromName }}</text> |
| | | <text class="order-card__route-desc">{{ item.fromAddress }}</text> |
| | | </view> |
| | | <image src="/static/image/ic_daohang@2x.png" mode="widthFix" class="order-card__nav"></image> |
| | | </view> |
| | | <view class="order-card__route-item order-card__route-item--destination"> |
| | | <view class="order-card__route-texts"> |
| | | <text class="order-card__route-title">{{ item.toName }}</text> |
| | | <text class="order-card__route-desc">{{ item.toAddress }}</text> |
| | | </view> |
| | | <image src="/static/image/ic_daohang@2x.png" mode="widthFix" class="order-card__nav"></image> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="order-card__goods"> |
| | | <text class="order-card__goods-text">{{ item.goods }}</text> |
| | | <text class="order-card__goods-arrow">⌄</text> |
| | | </view> |
| | | |
| | | <view class="order-card__actions" :class="'order-card__actions--' + activeTab"> |
| | | <template v-if="activeTab === 'pickup'"> |
| | | <view class="order-card__icon-actions"> |
| | | <view class="order-card__icon-action" @click.stop="show = true"> |
| | | <image class="order-card__action-icon" src="/static/image/ic_cancle@2x.png" mode="aspectFit"></image> |
| | | <text class="order-card__action-text">取消</text> |
| | | </view> |
| | | <view class="order-card__icon-action" @click.stop> |
| | | <image class="order-card__action-icon" src="/static/image/ic_call@2x.png" mode="aspectFit"></image> |
| | | <text class="order-card__action-text">联系</text> |
| | | </view> |
| | | </view> |
| | | <button class="order-card__button order-card__button--code" hover-class="order-card__button--hover" @click.stop="show1 = true">取货码</button> |
| | | </template> |
| | | <template v-else-if="activeTab === 'delivering'"> |
| | | <view class="order-card__icon-actions order-card__icon-actions--single"> |
| | | <view class="order-card__icon-action" @click.stop> |
| | | <image class="order-card__action-icon" src="/static/image/ic_call@2x.png" mode="aspectFit"></image> |
| | | <text class="order-card__action-text">联系</text> |
| | | </view> |
| | | </view> |
| | | <button class="order-card__button order-card__button--code" hover-class="order-card__button--hover" @click.stop>存件码</button> |
| | | </template> |
| | | <button v-else class="order-card__button" hover-class="order-card__button--hover" @click.stop="openDetailPopup(item)">立即抢单</button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-else class="hall-page__empty"> |
| | | <image class="hall-page__empty-icon" src="/static/image/default_nodata_grey@2x.png" mode="aspectFit"></image> |
| | | </view> |
| | | </scroll-view> |
| | | |
| | | <!-- 取消订单 --> |
| | | <u-modal |
| | | :show="show" |
| | | showCancelButton |
| | | @cancel="show = false" |
| | | cancelColor="#666666" |
| | | confirmColor="#0055FF" |
| | | title="取消订单确认"> |
| | | <view style="text-align: center;color: #333333;font-size: 28rpx;font-weight: 400;"> |
| | | 您今日还可取消 X 次订单,次数用尽后今日将无法接单,是否确认取消? |
| | | </view> |
| | | </u-modal> |
| | | |
| | | <!-- 取货码 --> |
| | | <u-popup :show="show1" round="20" mode="bottom"> |
| | | <view class="qrcode"> |
| | | <view class="qrcode-title"> |
| | | <image src="/static/image/ic_close@2x.png" mode="widthFix" style="opacity: 0;"></image> |
| | | <text>取货码</text> |
| | | <image src="/static/image/ic_close@2x.png" mode="widthFix" @click="show1 = false"></image> |
| | | </view> |
| | | <view class="qrcode-image"> |
| | | <image src="/static/logo.png" mode="widthFix"></image> |
| | | </view> |
| | | <view class="qrcode-btn" hover-class="qrcode-btn--hover" @click="show1 = false">关闭</view> |
| | | </view> |
| | | </u-popup> |
| | | |
| | | <!-- 订单详情 --> |
| | | <u-popup :show="showOrderDetail" round="20" mode="bottom" :overlayStyle="{ background: 'rgba(0, 0, 0, 0.32)' }" @close="showOrderDetail = false"> |
| | | <view class="order-detail" :style="{ height: 'calc(100vh - ' + statusBarHeight + 'px)' }"> |
| | | <view v-if="isStatusDetail" class="order-detail__map-section"> |
| | | <view class="order-detail__map"> |
| | | <view class="order-detail__map-placeholder"></view> |
| | | <view class="order-detail__map-bubble">{{ detailMap.tips }}</view> |
| | | </view> |
| | | <view class="order-detail__status-bar"> |
| | | <view class="order-detail__status-left"> |
| | | <image class="order-detail__status-icon" mode="aspectFit"></image> |
| | | <text class="order-detail__status-name">{{ detailOrder.statusText }}</text> |
| | | </view> |
| | | <view class="order-detail__status-right"> |
| | | <text v-if="detailOrder.showCancelTag" class="order-detail__cancel-tag">取消订单</text> |
| | | <text class="order-detail__status-no">#{{ detailOrder.serialNo }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <scroll-view class="order-detail__scroll" scroll-y> |
| | | |
| | | <view class="order-detail__content"> |
| | | <view class="order-detail__head"> |
| | | <view class="order-detail__time-wrap"> |
| | | <text class="order-detail__time">{{ detailOrder.time }}</text> |
| | | <text class="order-detail__time-sub">送达</text> |
| | | </view> |
| | | <view class="order-detail__price-wrap"> |
| | | <text class="order-detail__price">{{ detailOrder.price }}</text> |
| | | <text class="order-detail__extra">含加急¥{{ detailOrder.extra }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-if="isStatusDetail" class="order-detail__qrcode-section"> |
| | | <view class="order-detail__qrcode-box"> |
| | | <image class="order-detail__qrcode-image" src="/static/logo.png" mode="aspectFit"></image> |
| | | </view> |
| | | <text class="order-detail__qrcode-value">{{ detailOrder.qrcodeValue }}</text> |
| | | <text class="order-detail__qrcode-label">{{ detailOrder.qrcodeLabel }}</text> |
| | | </view> |
| | | |
| | | <view class="order-detail__tags"> |
| | | <view v-for="tag in detailOrder.tags" :key="tag.text" class="order-detail__tag-wrap"> |
| | | <image |
| | | v-if="getTagImage(tag.text)" |
| | | class="order-detail__tag-icon" |
| | | :src="getTagImage(tag.text)" |
| | | mode="widthFix" |
| | | ></image> |
| | | <text v-else class="order-detail__tag" :class="tag.type ? 'order-detail__tag--' + tag.type : ''">{{ tag.text }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="order-detail__route"> |
| | | <view class="order-detail__route-side"> |
| | | <text class="order-detail__distance-top">{{ detailOrder.distanceTop }}</text> |
| | | <view class="order-detail__line"></view> |
| | | <text class="order-detail__distance-bottom">{{ detailOrder.distanceBottom }}</text> |
| | | </view> |
| | | <view class="order-detail__route-main"> |
| | | <view class="order-detail__route-item"> |
| | | <view class="order-detail__route-texts"> |
| | | <text class="order-detail__route-title">{{ detailOrder.fromName }}</text> |
| | | <text class="order-detail__route-desc">{{ detailOrder.fromAddress }}</text> |
| | | </view> |
| | | <view class="order-detail__route-actions"> |
| | | <image class="order-detail__route-icon" src="/static/image/ic_c1all@2x.png" mode="aspectFit"></image> |
| | | <image class="order-detail__route-icon" src="/static/image/ic_daohang@2x.png" mode="aspectFit"></image> |
| | | </view> |
| | | </view> |
| | | <view class="order-detail__route-item order-detail__route-item--destination"> |
| | | <view class="order-detail__route-texts"> |
| | | <text class="order-detail__route-title">{{ detailOrder.toName }}</text> |
| | | <text class="order-detail__route-desc">{{ detailOrder.toAddress }}</text> |
| | | </view> |
| | | <view class="order-detail__route-actions"> |
| | | <image class="order-detail__route-icon order-detail__route-icon--phone" src="/static/image/ic_c1all@2x.png" mode="aspectFit"></image> |
| | | <image class="order-detail__route-icon" src="/static/image/ic_daohang@2x.png" mode="aspectFit"></image> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="order-detail__section"> |
| | | <text class="order-detail__section-title">客户信息</text> |
| | | <view class="order-detail__customer"> |
| | | <text class="order-detail__customer-text">{{ detailOrder.customer.name }}(手机号{{ detailOrder.customer.phone }})</text> |
| | | <image class="order-detail__customer-icon" src="/static/image/ic_c1all@2x.png" mode="aspectFit"></image> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="order-detail__section"> |
| | | <text class="order-detail__section-title">物品清单(共{{ detailOrder.goodsList.length }}件)</text> |
| | | <view class="order-detail__goods-list"> |
| | | <view v-for="goods in detailOrder.goodsList" :key="goods.name" class="order-detail__goods-item"> |
| | | <text class="order-detail__goods-name">{{ goods.name }}</text> |
| | | <text class="order-detail__goods-count">x{{ goods.count }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="order-detail__section order-detail__section--photos"> |
| | | <text class="order-detail__section-title">物品信息</text> |
| | | <text class="order-detail__goods-category">{{ detailOrder.goodsCategory }}</text> |
| | | <view class="order-detail__photos"> |
| | | <view v-for="(photo, index) in detailOrder.photos" :key="index" class="order-detail__photo-item"> |
| | | <image class="order-detail__photo" :src="photo" mode="aspectFill"></image> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </scroll-view> |
| | | |
| | | <view class="order-detail__footer"> |
| | | <view class="order-detail__cancel" @click="showOrderDetail = false"> |
| | | <image class="order-detail__cancel-icon" src="/static/image/ic_close2@2x.png" mode="aspectFit"></image> |
| | | </view> |
| | | <button v-if="!isStatusDetail" class="order-detail__confirm" hover-class="order-detail__confirm--hover">确认抢单</button> |
| | | <button v-else class="order-detail__confirm order-detail__confirm--status" hover-class="order-detail__confirm--hover" @click="handleStatusAction"> |
| | | <image class="order-detail__confirm-icon" src="/static/image/ic_photo@2x.png" mode="aspectFit"></image> |
| | | <text>{{ detailPopupType === 'pickup' ? '拍照取货' : '拍照送达' }}</text> |
| | | </button> |
| | | </view> |
| | | </view> |
| | | </u-popup> |
| | | |
| | | <u-popup :show="showPhotoDeliverPopup" round="20" mode="bottom"> |
| | | <view class="photo-deliver"> |
| | | <view class="photo-deliver__header"> |
| | | <image class="photo-deliver__close-placeholder" mode="aspectFit"></image> |
| | | <text class="photo-deliver__title">拍照送达</text> |
| | | <image class="photo-deliver__close" mode="aspectFit" @click="showPhotoDeliverPopup = false"></image> |
| | | </view> |
| | | |
| | | <view class="photo-deliver__section"> |
| | | <view class="photo-deliver__label-row"> |
| | | <text class="photo-deliver__label">拍摄送达照片</text> |
| | | <text class="photo-deliver__required">*</text> |
| | | <text class="photo-deliver__hint">最多3张照片</text> |
| | | </view> |
| | | |
| | | <view class="photo-deliver__photos"> |
| | | <view class="photo-deliver__upload-card"> |
| | | <image class="photo-deliver__upload-icon" mode="aspectFit"></image> |
| | | <text class="photo-deliver__upload-text">点击拍照</text> |
| | | </view> |
| | | |
| | | <view class="photo-deliver__preview-card"> |
| | | <image class="photo-deliver__preview-image" mode="aspectFill"></image> |
| | | <view class="photo-deliver__preview-mask"> |
| | | <text class="photo-deliver__preview-delete">删除</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="photo-deliver__section photo-deliver__section--remark"> |
| | | <text class="photo-deliver__remark-title">备注信息</text> |
| | | <textarea class="photo-deliver__textarea" maxlength="200" placeholder="请输入" placeholder-style="color: #c7cbd3;" /> |
| | | </view> |
| | | |
| | | <button class="photo-deliver__submit" hover-class="photo-deliver__submit--hover" @click="showPhotoDeliverPopup = false">确认送达</button> |
| | | </view> |
| | | </u-popup> |
| | | </view> |
| | | </template> |
| | | |
| | |
| | | export default { |
| | | data() { |
| | | return { |
| | | tts: null // 原生TTS对象 |
| | | tts: null, |
| | | show: false, |
| | | show1: false, |
| | | showPhotoDeliverPopup: false, |
| | | showOrderDetail: false, |
| | | detailPopupType: 'hall', |
| | | routeInfo: null, |
| | | statusBarHeight: 0, |
| | | headerHeight: 0, |
| | | tabbarHeight: 0, |
| | | scrollHeight: 0, |
| | | showFilterPopup: false, |
| | | activeTab: 'hall', |
| | | filterSections: [ |
| | | { key: 'sort', title: '排序', options: ['综合排序', '离我最近'] }, |
| | | { key: 'level', title: '物品等级', options: ['不限', '普通物品', '贵重物品', '大件物品', '特殊物品', '其他物品'] }, |
| | | { key: 'distance', title: '位置范围', options: ['不限', '500m', '1km', '2km', '3km', '4km', '5km', '6km', '7km'] } |
| | | ], |
| | | selectedFilters: { |
| | | sort: '综合排序', |
| | | level: '不限', |
| | | distance: '不限' |
| | | }, |
| | | detailOrder: { |
| | | time: '45分钟内', |
| | | price: '¥20.5', |
| | | extra: '3.0', |
| | | serialNo: 1, |
| | | statusText: '抢单大厅', |
| | | qrcodeValue: '767889', |
| | | qrcodeLabel: '取货码', |
| | | mapTips: '', |
| | | showCancelTag: false, |
| | | startPoint: { |
| | | latitude: 31.8269, |
| | | longitude: 117.2334 |
| | | }, |
| | | endPoint: { |
| | | latitude: 31.8435, |
| | | longitude: 117.2852 |
| | | }, |
| | | tags: [ |
| | | { text: '标速达', type: 'blue' }, |
| | | { text: '贵重物品', type: 'orange' } |
| | | ], |
| | | distanceTop: '349m', |
| | | distanceBottom: '12.5km', |
| | | fromName: '中铁快运南站旗舰店', |
| | | fromAddress: '莲花路200号莲花产业园F栋401', |
| | | toName: '佳苑巴黎都市3期10栋301室', |
| | | toAddress: '洞庭湖路与湖北路交叉口西150米', |
| | | customer: { |
| | | name: '刘先生', |
| | | phone: '2878' |
| | | }, |
| | | goodsList: [ |
| | | { name: '大件行李', count: 1 }, |
| | | { name: '中件行李', count: 2 }, |
| | | { name: '小件行李', count: 3 }, |
| | | { name: '背包', count: 2 } |
| | | ], |
| | | goodsCategory: '文件', |
| | | photos: ['/static/logo.png', '/static/logo.png', '/static/logo.png'] |
| | | }, |
| | | stats: [ |
| | | { value: '4.2', label: '服务分' }, |
| | | { value: '234.3', label: '今日预计佣金' }, |
| | | { value: '13', label: '今日接单' } |
| | | ], |
| | | tabs: [ |
| | | { label: '抢单大厅', value: 'hall' }, |
| | | { label: '待取货', value: 'pickup', count: 2 }, |
| | | { label: '配送中', value: 'delivering', count: 2 } |
| | | ], |
| | | orderList: [ |
| | | { |
| | | id: 1, |
| | | time: '45分钟内', |
| | | price: '¥20.5', |
| | | extra: '3.0', |
| | | startPoint: { |
| | | latitude: 31.829512, |
| | | longitude: 117.239211 |
| | | }, |
| | | endPoint: { |
| | | latitude: 31.841268, |
| | | longitude: 117.278695 |
| | | }, |
| | | tags: [ |
| | | { text: '极速达', type: 'blue' }, |
| | | { text: '贵重物品', type: 'orange' } |
| | | ], |
| | | distanceTop: '349m', |
| | | distanceBottom: '12.5km', |
| | | fromName: '中铁快运南站旗舰店', |
| | | fromAddress: '莲花路200号莲花产业园F栋401', |
| | | toName: '佳苑巴黎都市3期10栋301室', |
| | | toAddress: '洞庭湖路与湖北路交叉口西150米', |
| | | goods: '大件行李*1、中件行李*2、小件行李*3、背包*1' |
| | | }, |
| | | { |
| | | id: 2, |
| | | time: '45分钟内', |
| | | price: '¥20.5', |
| | | extra: '3.0', |
| | | startPoint: { |
| | | latitude: 31.827106, |
| | | longitude: 117.232884 |
| | | }, |
| | | endPoint: { |
| | | latitude: 31.847331, |
| | | longitude: 117.289762 |
| | | }, |
| | | tags: [ |
| | | { text: '极速达', type: 'red' }, |
| | | { text: '大件物品', type: 'blue-light' } |
| | | ], |
| | | distanceTop: '349m', |
| | | distanceBottom: '12.5km', |
| | | fromName: '中铁快运南站旗舰店(合肥南站北广场', |
| | | fromAddress: '莲花路200号莲花产业园F栋401', |
| | | toName: '佳苑巴黎都市3期10栋301室', |
| | | toAddress: '洞庭湖路与湖北路交叉口西150米', |
| | | goods: '大件行李*1' |
| | | }, |
| | | { |
| | | id: 3, |
| | | time: '45分钟内', |
| | | price: '¥20.5', |
| | | extra: '3.0', |
| | | startPoint: { |
| | | latitude: 31.823761, |
| | | longitude: 117.228947 |
| | | }, |
| | | endPoint: { |
| | | latitude: 31.838473, |
| | | longitude: 117.272513 |
| | | }, |
| | | tags: [ |
| | | { text: '极速达', type: 'red' }, |
| | | { text: '大件物品', type: 'blue-light' } |
| | | ], |
| | | distanceTop: '349m', |
| | | distanceBottom: '12.5km', |
| | | fromName: '中铁快运南站旗舰店(合肥南站北广场', |
| | | fromAddress: '莲花路200号莲花产业园F栋401', |
| | | toName: '佳苑巴黎都市3期10栋301室', |
| | | toAddress: '洞庭湖路与湖北路交叉口西150米', |
| | | goods: '大件行李*1' |
| | | } |
| | | ], |
| | | pickupOrderList: [ |
| | | { |
| | | id: 101, |
| | | serialNo: 1, |
| | | statusText: '待取货', |
| | | qrcodeValue: '767889', |
| | | qrcodeLabel: '取货码', |
| | | mapTips: '', |
| | | showCancelTag: true, |
| | | startPoint: { |
| | | latitude: 31.829512, |
| | | longitude: 117.239211 |
| | | }, |
| | | endPoint: { |
| | | latitude: 31.841268, |
| | | longitude: 117.278695 |
| | | }, |
| | | time: '45分钟内', |
| | | price: '¥20.5', |
| | | extra: '3.0', |
| | | tags: [ |
| | | { text: '标速达', type: 'blue' }, |
| | | { text: '贵重物品', type: 'orange' } |
| | | ], |
| | | distanceTop: '349m', |
| | | distanceBottom: '12.5km', |
| | | fromName: '中铁快运南站旗舰店', |
| | | fromAddress: '莲花路200号莲花产业园F栋401', |
| | | toName: '小铁无忧存', |
| | | toAddress: '洞庭湖路与湖北路交叉口西150米', |
| | | goods: '大件行李*1、中件行李*2、小件行李*3、背包*1' |
| | | } |
| | | ], |
| | | deliveringOrderList: [ |
| | | { |
| | | id: 201, |
| | | serialNo: 1, |
| | | statusText: '配送中', |
| | | qrcodeValue: '767889', |
| | | qrcodeLabel: '存件码', |
| | | mapTips: '', |
| | | showCancelTag: false, |
| | | startPoint: { |
| | | latitude: 31.827106, |
| | | longitude: 117.232884 |
| | | }, |
| | | endPoint: { |
| | | latitude: 31.847331, |
| | | longitude: 117.289762 |
| | | }, |
| | | time: '45分钟内', |
| | | price: '¥20.5', |
| | | extra: '3.0', |
| | | tags: [ |
| | | { text: '标速达', type: 'blue' }, |
| | | { text: '贵重物品', type: 'orange' } |
| | | ], |
| | | distanceTop: '349m', |
| | | distanceBottom: '12.5km', |
| | | fromName: '中铁快运南站旗舰店', |
| | | fromAddress: '莲花路200号莲花产业园F栋401', |
| | | toName: '小铁无忧存', |
| | | toAddress: '洞庭湖路与湖北路交叉口西150米', |
| | | goods: '大件行李*1、中件行李*2、小件行李*3、背包*1' |
| | | } |
| | | ] |
| | | } |
| | | }, |
| | | |
| | | onLoad() { |
| | | const systemInfo = uni.getSystemInfoSync() |
| | | const safeBottom = systemInfo.safeAreaInsets ? systemInfo.safeAreaInsets.bottom || 0 : 0 |
| | | const windowHeight = systemInfo.windowHeight || 0 |
| | | this.statusBarHeight = systemInfo.statusBarHeight || 0 |
| | | this.headerHeight = this.statusBarHeight + uni.upx2px(308) |
| | | this.tabbarHeight = uni.upx2px(100) + safeBottom |
| | | this.scrollHeight = Math.max(windowHeight - this.headerHeight, 0) |
| | | }, |
| | | |
| | | computed: { |
| | | isStatusDetail() { |
| | | return this.detailPopupType === 'pickup' || this.detailPopupType === 'delivering' |
| | | }, |
| | | |
| | | detailMap() { |
| | | const fallbackPoint = { |
| | | latitude: 31.8269, |
| | | longitude: 117.2334 |
| | | } |
| | | const startPoint = this.detailOrder.startPoint || fallbackPoint |
| | | const endPoint = this.detailOrder.endPoint || fallbackPoint |
| | | const routePoints = this.routeInfo && this.routeInfo.points && this.routeInfo.points.length ? this.routeInfo.points : [] |
| | | const center = { |
| | | latitude: (startPoint.latitude + endPoint.latitude) / 2, |
| | | longitude: (startPoint.longitude + endPoint.longitude) / 2 |
| | | } |
| | | const distanceKm = this.routeInfo && this.routeInfo.distanceKm ? this.routeInfo.distanceKm : 0 |
| | | const durationMinutes = this.routeInfo && this.routeInfo.durationMinutes ? this.routeInfo.durationMinutes : 0 |
| | | const tips = this.detailOrder.mapTips || (distanceKm ? `剩余${distanceKm.toFixed(1)}km,约${durationMinutes}分钟` : '路线规划中...') |
| | | |
| | | return { |
| | | center, |
| | | markers: [ |
| | | { |
| | | id: 1, |
| | | latitude: startPoint.latitude, |
| | | longitude: startPoint.longitude, |
| | | iconPath: '/static/image/map_marker_start.svg', |
| | | width: 32, |
| | | height: 38, |
| | | anchor: { |
| | | x: 0.5, |
| | | y: 1 |
| | | } |
| | | }, |
| | | { |
| | | id: 2, |
| | | latitude: endPoint.latitude, |
| | | longitude: endPoint.longitude, |
| | | iconPath: '/static/image/map_marker_end.svg', |
| | | width: 32, |
| | | height: 38, |
| | | anchor: { |
| | | x: 0.5, |
| | | y: 1 |
| | | } |
| | | } |
| | | ], |
| | | polyline: [ |
| | | ...(routePoints.length ? [{ |
| | | points: routePoints, |
| | | color: '#2A7FFF', |
| | | width: 8, |
| | | dottedLine: false, |
| | | arrowLine: true |
| | | }] : []) |
| | | ], |
| | | includePoints: routePoints.length ? routePoints : [startPoint, endPoint], |
| | | scale: 12, |
| | | tips |
| | | } |
| | | }, |
| | | |
| | | currentOrderList() { |
| | | const orderMap = { |
| | | hall: this.orderList, |
| | | pickup: this.pickupOrderList, |
| | | delivering: this.deliveringOrderList |
| | | } |
| | | |
| | | return orderMap[this.activeTab] || [] |
| | | }, |
| | | |
| | | bodyStyle() { |
| | | return { |
| | | marginTop: this.headerHeight + 'px', |
| | | height: this.scrollHeight + 'px' |
| | | } |
| | | } |
| | | }, |
| | | |
| | |
| | | }, |
| | | |
| | | methods: { |
| | | // ========== 初始化安卓原生语音 ========== |
| | | calculateDistance(startPoint, endPoint) { |
| | | if (!startPoint || !endPoint) { |
| | | return 0 |
| | | } |
| | | |
| | | const toRad = (value) => value * Math.PI / 180 |
| | | const earthRadius = 6371 |
| | | const deltaLat = toRad(endPoint.latitude - startPoint.latitude) |
| | | const deltaLng = toRad(endPoint.longitude - startPoint.longitude) |
| | | const lat1 = toRad(startPoint.latitude) |
| | | const lat2 = toRad(endPoint.latitude) |
| | | const a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLng / 2) * Math.sin(deltaLng / 2) |
| | | const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) |
| | | |
| | | return earthRadius * c |
| | | }, |
| | | |
| | | estimateDuration(distanceKm) { |
| | | if (!distanceKm) { |
| | | return 1 |
| | | } |
| | | |
| | | const averageSpeedKmPerHour = 35 |
| | | return Math.max(1, Math.round(distanceKm / averageSpeedKmPerHour * 60)) |
| | | }, |
| | | |
| | | handleStatusAction() { |
| | | if (this.detailPopupType === 'delivering') { |
| | | this.showOrderDetail = false |
| | | this.showPhotoDeliverPopup = true |
| | | } |
| | | }, |
| | | |
| | | openDetailPopup(item) { |
| | | this.detailPopupType = this.activeTab |
| | | this.routeInfo = null |
| | | this.detailOrder = { |
| | | ...this.detailOrder, |
| | | ...item, |
| | | customer: item.customer || this.detailOrder.customer, |
| | | goodsList: item.goodsList || this.detailOrder.goodsList, |
| | | goodsCategory: item.goodsCategory || this.detailOrder.goodsCategory, |
| | | photos: item.photos || this.detailOrder.photos |
| | | } |
| | | this.showOrderDetail = true |
| | | |
| | | if (this.activeTab === 'pickup' || this.activeTab === 'delivering') { |
| | | this.fetchDrivingRoute(this.detailOrder) |
| | | } |
| | | }, |
| | | |
| | | fetchDrivingRoute(order) { |
| | | if (!order || !order.startPoint || !order.endPoint) { |
| | | return |
| | | } |
| | | |
| | | const origin = `${order.startPoint.longitude},${order.startPoint.latitude}` |
| | | const destination = `${order.endPoint.longitude},${order.endPoint.latitude}` |
| | | |
| | | uni.request({ |
| | | url: 'https://restapi.amap.com/v3/direction/driving', |
| | | method: 'GET', |
| | | data: { |
| | | key: 'e4d46c87adf151dca20060317592b1b6', |
| | | origin, |
| | | destination, |
| | | extensions: 'all', |
| | | strategy: 0, |
| | | output: 'json' |
| | | }, |
| | | success: (response) => { |
| | | const path = response.data && response.data.route && response.data.route.paths && response.data.route.paths[0] |
| | | if (!path) { |
| | | this.routeInfo = this.buildMockRouteInfo(order.startPoint, order.endPoint) |
| | | return |
| | | } |
| | | |
| | | const points = this.parseRoutePoints(path.steps || [], order.startPoint, order.endPoint) |
| | | const distanceKm = Number(path.distance || 0) / 1000 |
| | | const durationMinutes = Math.max(1, Math.round(Number(path.duration || 0) / 60)) |
| | | |
| | | this.routeInfo = { |
| | | points, |
| | | distanceKm: distanceKm || this.calculateDistance(order.startPoint, order.endPoint), |
| | | durationMinutes |
| | | } |
| | | }, |
| | | fail: () => { |
| | | this.routeInfo = this.buildMockRouteInfo(order.startPoint, order.endPoint) |
| | | } |
| | | }) |
| | | }, |
| | | |
| | | buildMockRouteInfo(startPoint, endPoint) { |
| | | const middleLatitude = (startPoint.latitude + endPoint.latitude) / 2 |
| | | const middleLongitude = (startPoint.longitude + endPoint.longitude) / 2 |
| | | const points = [ |
| | | startPoint, |
| | | { |
| | | latitude: startPoint.latitude + (middleLatitude - startPoint.latitude) * 0.65, |
| | | longitude: startPoint.longitude + 0.008 |
| | | }, |
| | | { |
| | | latitude: middleLatitude + 0.004, |
| | | longitude: middleLongitude + 0.012 |
| | | }, |
| | | { |
| | | latitude: endPoint.latitude - 0.003, |
| | | longitude: endPoint.longitude - 0.008 |
| | | }, |
| | | endPoint |
| | | ] |
| | | const distanceKm = this.calculatePathDistance(points) |
| | | const durationMinutes = this.estimateDuration(distanceKm) |
| | | |
| | | return { |
| | | points, |
| | | distanceKm, |
| | | durationMinutes |
| | | } |
| | | }, |
| | | |
| | | parseRoutePoints(steps, startPoint, endPoint) { |
| | | const points = [] |
| | | |
| | | steps.forEach((step) => { |
| | | const polyline = step.polyline || '' |
| | | polyline.split(';').forEach((pointText) => { |
| | | const [longitude, latitude] = pointText.split(',').map(Number) |
| | | if (!Number.isNaN(latitude) && !Number.isNaN(longitude)) { |
| | | points.push({ latitude, longitude }) |
| | | } |
| | | }) |
| | | }) |
| | | |
| | | if (!points.length) { |
| | | return [startPoint, endPoint] |
| | | } |
| | | |
| | | return points |
| | | }, |
| | | |
| | | calculatePathDistance(points) { |
| | | if (!points || points.length < 2) { |
| | | return 0 |
| | | } |
| | | |
| | | let totalDistance = 0 |
| | | for (let index = 1; index < points.length; index += 1) { |
| | | totalDistance += this.calculateDistance(points[index - 1], points[index]) |
| | | } |
| | | |
| | | return totalDistance |
| | | }, |
| | | |
| | | toggleFilterPopup(show) { |
| | | this.showFilterPopup = show |
| | | }, |
| | | |
| | | selectFilter(key, option) { |
| | | this.selectedFilters = { |
| | | ...this.selectedFilters, |
| | | [key]: option |
| | | } |
| | | }, |
| | | |
| | | resetFilters() { |
| | | this.selectedFilters = { |
| | | sort: '综合排序', |
| | | level: '不限', |
| | | distance: '不限' |
| | | } |
| | | }, |
| | | |
| | | confirmFilters() { |
| | | this.showFilterPopup = false |
| | | }, |
| | | |
| | | getTagImage(tagText) { |
| | | const tagImageMap = { |
| | | '极速达': '/static/image/ic_jisuda@2x.png', |
| | | '标速达': '/static/image/ic_biaosuda@2x.png' |
| | | } |
| | | |
| | | return tagImageMap[tagText] || '' |
| | | }, |
| | | |
| | | initTTS() { |
| | | if (uni.getSystemInfoSync().platform !== 'android') { |
| | | console.log('仅支持安卓') |
| | |
| | | } |
| | | }, |
| | | |
| | | // ========== 语音播报(核心方法) ========== |
| | | speak(text) { |
| | | if (!this.tts) { |
| | | uni.showToast({ |
| | |
| | | } |
| | | }, |
| | | |
| | | // 停止播报 |
| | | stopSpeak() { |
| | | if (this.tts) this.tts.stop() |
| | | } |
| | | }, |
| | | |
| | | // 页面销毁时关闭语音 |
| | | onUnload() { |
| | | if (this.tts) { |
| | | this.tts.stop() |
| | |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .hall-page { |
| | | position: relative; |
| | | height: 100vh; |
| | | background: #f5f6f8; |
| | | overflow: hidden; |
| | | |
| | | .order-detail-map-layer { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | height: 330rpx; |
| | | z-index: 10081; |
| | | overflow: hidden; |
| | | border-top-left-radius: 28rpx; |
| | | border-top-right-radius: 28rpx; |
| | | |
| | | &__map { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | } |
| | | |
| | | .qrcode { |
| | | padding: 36rpx 30rpx; |
| | | box-sizing: border-box; |
| | | .qrcode-title { |
| | | width: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | image { |
| | | width: 28rpx; |
| | | height: 28rpx; |
| | | } |
| | | text { |
| | | font-weight: 600; |
| | | font-size: 32rpx; |
| | | color: #111111; |
| | | } |
| | | } |
| | | .qrcode-image { |
| | | width: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-top: 60rpx; |
| | | image { |
| | | width: 400rpx; |
| | | height: 400rpx; |
| | | } |
| | | } |
| | | .qrcode-btn { |
| | | width: 100%; |
| | | height: 88rpx; |
| | | line-height: 88rpx; |
| | | text-align: center; |
| | | background: #106EFA; |
| | | border-radius: 50rpx; |
| | | font-weight: bold; |
| | | font-size: 32rpx; |
| | | color: #FFFFFF; |
| | | margin-top: 68rpx; |
| | | |
| | | &--hover { |
| | | opacity: 0.92; |
| | | transform: translateY(2rpx); |
| | | } |
| | | } |
| | | } |
| | | |
| | | .photo-deliver { |
| | | padding: 32rpx 28rpx calc(env(safe-area-inset-bottom) + 28rpx); |
| | | background: #ffffff; |
| | | box-sizing: border-box; |
| | | border-top-left-radius: 20rpx; |
| | | border-top-right-radius: 20rpx; |
| | | overflow: hidden; |
| | | |
| | | &__header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | &__title { |
| | | font-size: 34rpx; |
| | | font-weight: 700; |
| | | color: #111111; |
| | | } |
| | | |
| | | &__close, |
| | | &__close-placeholder { |
| | | width: 36rpx; |
| | | height: 36rpx; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | &__close-placeholder { |
| | | opacity: 0; |
| | | } |
| | | |
| | | &__section { |
| | | margin-top: 56rpx; |
| | | |
| | | &--remark { |
| | | margin-top: 46rpx; |
| | | } |
| | | } |
| | | |
| | | &__label-row { |
| | | display: flex; |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | &__label, |
| | | &__remark-title { |
| | | font-size: 28rpx; |
| | | font-weight: 700; |
| | | color: #23262d; |
| | | } |
| | | |
| | | &__required { |
| | | margin-left: 4rpx; |
| | | font-size: 28rpx; |
| | | font-weight: 700; |
| | | color: #ff3b30; |
| | | } |
| | | |
| | | &__hint { |
| | | margin-left: 12rpx; |
| | | font-size: 24rpx; |
| | | color: #a8adb7; |
| | | } |
| | | |
| | | &__photos { |
| | | display: flex; |
| | | gap: 18rpx; |
| | | margin-top: 30rpx; |
| | | } |
| | | |
| | | &__upload-card, |
| | | &__preview-card { |
| | | position: relative; |
| | | width: 160rpx; |
| | | height: 160rpx; |
| | | border-radius: 8rpx; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | &__upload-card { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | border: 2rpx dashed #c9ced6; |
| | | background: #ffffff; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | &__upload-icon { |
| | | width: 52rpx; |
| | | height: 52rpx; |
| | | } |
| | | |
| | | &__upload-text { |
| | | margin-top: 14rpx; |
| | | font-size: 26rpx; |
| | | color: #9da3ae; |
| | | } |
| | | |
| | | &__preview-card { |
| | | background: #eef1f5; |
| | | } |
| | | |
| | | &__preview-image { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | &__preview-mask { |
| | | position: absolute; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | height: 48rpx; |
| | | background: rgba(0, 0, 0, 0.46); |
| | | } |
| | | |
| | | &__preview-delete { |
| | | font-size: 26rpx; |
| | | color: #ffffff; |
| | | } |
| | | |
| | | &__textarea { |
| | | width: 100%; |
| | | height: 110rpx; |
| | | margin-top: 24rpx; |
| | | padding: 28rpx 24rpx; |
| | | border-radius: 12rpx; |
| | | background: #f7f8fa; |
| | | box-sizing: border-box; |
| | | font-size: 30rpx; |
| | | color: #2c3139; |
| | | } |
| | | |
| | | &__submit { |
| | | width: 100%; |
| | | height: 88rpx; |
| | | line-height: 88rpx; |
| | | margin-top: 86rpx; |
| | | border-radius: 50rpx; |
| | | background: #106efa; |
| | | font-size: 32rpx; |
| | | font-weight: 700; |
| | | color: #ffffff; |
| | | border: 0; |
| | | padding: 0; |
| | | |
| | | &::after { |
| | | border: 0; |
| | | } |
| | | |
| | | &--hover { |
| | | opacity: 0.92; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .order-detail { |
| | | display: flex; |
| | | flex-direction: column; |
| | | background: #ffffff; |
| | | border-top-left-radius: 28rpx; |
| | | border-top-right-radius: 28rpx; |
| | | overflow: hidden; |
| | | |
| | | &__scroll { |
| | | flex: 1; |
| | | min-height: 0; |
| | | } |
| | | |
| | | &__map-section { |
| | | background: #ffffff; |
| | | } |
| | | |
| | | &__map { |
| | | position: relative; |
| | | height: 330rpx; |
| | | background: #eef5ff; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | &__map-view { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | &__map-placeholder { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | &__map-bubble { |
| | | position: absolute; |
| | | left: 26rpx; |
| | | bottom: 44rpx; |
| | | padding: 12rpx 18rpx; |
| | | border-radius: 12rpx; |
| | | background: rgba(255, 255, 255, 0.96); |
| | | box-shadow: 0 10rpx 20rpx rgba(33, 92, 182, 0.08); |
| | | font-size: 26rpx; |
| | | font-weight: 500; |
| | | color: #2f6ff2; |
| | | } |
| | | |
| | | &__status-bar { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 0 24rpx; |
| | | height: 92rpx; |
| | | background: #d9e8ff; |
| | | } |
| | | |
| | | &__status-left, |
| | | &__status-right { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | &__status-icon { |
| | | width: 28rpx; |
| | | height: 28rpx; |
| | | margin-right: 12rpx; |
| | | border-radius: 6rpx; |
| | | background: #7ea7ef; |
| | | } |
| | | |
| | | &__status-name, |
| | | &__status-no { |
| | | font-size: 34rpx; |
| | | font-weight: 700; |
| | | color: #2b3139; |
| | | } |
| | | |
| | | &__cancel-tag { |
| | | padding: 8rpx 16rpx; |
| | | margin-right: 18rpx; |
| | | border: 2rpx solid #6ea6ff; |
| | | border-radius: 999rpx; |
| | | font-size: 24rpx; |
| | | color: #1d73ff; |
| | | background: rgba(255, 255, 255, 0.72); |
| | | } |
| | | |
| | | &__content { |
| | | padding: 28rpx 24rpx 36rpx; |
| | | } |
| | | |
| | | &__head { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | } |
| | | |
| | | &__time-wrap { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | &__time { |
| | | font-size: 42rpx; |
| | | font-weight: 700; |
| | | color: #ff8d27; |
| | | } |
| | | |
| | | &__time-sub { |
| | | margin-left: 8rpx; |
| | | font-size: 28rpx; |
| | | color: #a4a9b2; |
| | | } |
| | | |
| | | &__price-wrap { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: flex-end; |
| | | } |
| | | |
| | | &__price { |
| | | font-size: 44rpx; |
| | | font-weight: 700; |
| | | color: #ff3b30; |
| | | } |
| | | |
| | | &__extra { |
| | | margin-top: 6rpx; |
| | | font-size: 24rpx; |
| | | color: #a0a5af; |
| | | } |
| | | |
| | | &__tags { |
| | | display: flex; |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | gap: 10rpx; |
| | | margin-top: 14rpx; |
| | | } |
| | | |
| | | &__tag-wrap { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | &__tag-icon { |
| | | width: 108rpx; |
| | | height: 40rpx; |
| | | } |
| | | |
| | | &__tag { |
| | | padding: 4rpx 10rpx; |
| | | border-radius: 8rpx; |
| | | font-size: 22rpx; |
| | | line-height: 1.2; |
| | | border: 1rpx solid #2473f5; |
| | | color: #2473f5; |
| | | |
| | | &--orange { |
| | | border-color: #ff9c45; |
| | | background: #ff9c45; |
| | | color: #ffffff; |
| | | } |
| | | } |
| | | |
| | | &__route { |
| | | display: flex; |
| | | margin-top: 20rpx; |
| | | padding-bottom: 26rpx; |
| | | border-bottom: 1rpx solid #edf0f3; |
| | | } |
| | | |
| | | &__route-side { |
| | | width: 64rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | &__distance-top, |
| | | &__distance-bottom { |
| | | font-size: 22rpx; |
| | | font-weight: 600; |
| | | color: #555b66; |
| | | text-align: center; |
| | | } |
| | | |
| | | &__line { |
| | | position: relative; |
| | | width: 4rpx; |
| | | flex: 1; |
| | | min-height: 92rpx; |
| | | margin: 10rpx 0; |
| | | border-radius: 999rpx; |
| | | background: #d8dbe1; |
| | | |
| | | &::before, |
| | | &::after { |
| | | content: ''; |
| | | position: absolute; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | width: 14rpx; |
| | | height: 14rpx; |
| | | border-radius: 50%; |
| | | background: #6a6f79; |
| | | } |
| | | |
| | | &::before { |
| | | top: -4rpx; |
| | | } |
| | | |
| | | &::after { |
| | | bottom: -4rpx; |
| | | } |
| | | } |
| | | |
| | | &__route-main { |
| | | flex: 1; |
| | | } |
| | | |
| | | &__route-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | gap: 18rpx; |
| | | |
| | | &--destination { |
| | | margin-top: 24rpx; |
| | | } |
| | | } |
| | | |
| | | &__route-texts { |
| | | flex: 1; |
| | | min-width: 0; |
| | | } |
| | | |
| | | &__route-title { |
| | | display: block; |
| | | font-size: 40rpx; |
| | | font-weight: 700; |
| | | color: #2d3139; |
| | | line-height: 1.3; |
| | | } |
| | | |
| | | &__route-desc { |
| | | display: block; |
| | | margin-top: 8rpx; |
| | | font-size: 28rpx; |
| | | color: #9ea4ae; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | &__route-actions { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12rpx; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | &__route-icon, |
| | | &__customer-icon { |
| | | width: 48rpx; |
| | | height: 48rpx; |
| | | border: 2rpx dashed #c9ced6; |
| | | border-radius: 8rpx; |
| | | background: #f7f8fa; |
| | | } |
| | | |
| | | &__cancel-icon { |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | } |
| | | |
| | | &__qrcode-section { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | padding: 34rpx 0 8rpx; |
| | | border-bottom: 1rpx solid #edf0f3; |
| | | } |
| | | |
| | | &__qrcode-box { |
| | | width: 260rpx; |
| | | height: 260rpx; |
| | | padding: 16rpx; |
| | | border: 2rpx solid #edf0f3; |
| | | border-radius: 12rpx; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | &__qrcode-image { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | &__qrcode-value { |
| | | margin-top: 18rpx; |
| | | font-size: 44rpx; |
| | | font-weight: 700; |
| | | color: #31363f; |
| | | } |
| | | |
| | | &__qrcode-label { |
| | | margin-top: 8rpx; |
| | | font-size: 26rpx; |
| | | color: #b0b4bc; |
| | | } |
| | | |
| | | &__section { |
| | | padding: 28rpx 0; |
| | | border-bottom: 1rpx solid #edf0f3; |
| | | |
| | | &--photos { |
| | | border-bottom: 0; |
| | | } |
| | | } |
| | | |
| | | &__section-title { |
| | | display: block; |
| | | font-size: 32rpx; |
| | | font-weight: 700; |
| | | color: #2b3139; |
| | | } |
| | | |
| | | &__customer { |
| | | margin-top: 28rpx; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | gap: 20rpx; |
| | | } |
| | | |
| | | &__customer-text { |
| | | flex: 1; |
| | | font-size: 32rpx; |
| | | color: #41464f; |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | &__goods-list { |
| | | margin-top: 22rpx; |
| | | } |
| | | |
| | | &__goods-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 10rpx 0; |
| | | } |
| | | |
| | | &__goods-name { |
| | | font-size: 32rpx; |
| | | color: #41464f; |
| | | } |
| | | |
| | | &__goods-count { |
| | | font-size: 30rpx; |
| | | color: #a1a6af; |
| | | } |
| | | |
| | | &__goods-category { |
| | | display: block; |
| | | margin-top: 24rpx; |
| | | font-size: 32rpx; |
| | | color: #41464f; |
| | | } |
| | | |
| | | &__photos { |
| | | display: flex; |
| | | gap: 16rpx; |
| | | margin-top: 22rpx; |
| | | } |
| | | |
| | | &__photo-item { |
| | | width: 120rpx; |
| | | height: 120rpx; |
| | | border-radius: 12rpx; |
| | | overflow: hidden; |
| | | background: #f3f5f8; |
| | | } |
| | | |
| | | &__photo { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | &__footer { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 18rpx; |
| | | padding: 18rpx 24rpx calc(env(safe-area-inset-bottom) + 18rpx); |
| | | background: #ffffff; |
| | | box-shadow: 0 -8rpx 24rpx rgba(16, 27, 49, 0.04); |
| | | } |
| | | |
| | | &__cancel { |
| | | width: 176rpx; |
| | | height: 100rpx; |
| | | border-radius: 50rpx; |
| | | background: #E5E5E5; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | &__confirm { |
| | | flex: 1; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | gap: 14rpx; |
| | | height: 100rpx; |
| | | line-height: 100rpx; |
| | | border-radius: 50rpx; |
| | | background: #106EFA; |
| | | font-size: 36rpx; |
| | | font-weight: 600; |
| | | color: #ffffff; |
| | | border: 0; |
| | | padding: 0; |
| | | |
| | | &::after { |
| | | border: 0; |
| | | } |
| | | |
| | | &--hover { |
| | | opacity: 0.92; |
| | | } |
| | | } |
| | | |
| | | &__confirm-icon { |
| | | width: 44rpx; |
| | | height: 44rpx; |
| | | flex-shrink: 0; |
| | | } |
| | | } |
| | | |
| | | &__header { |
| | | position: fixed; |
| | | left: 0; |
| | | top: 0; |
| | | right: 0; |
| | | z-index: 10; |
| | | background: linear-gradient(180deg, #2473f5 0%, #1e6fef 100%); |
| | | box-shadow: 0 12rpx 24rpx rgba(36, 115, 245, 0.08); |
| | | } |
| | | |
| | | &__user-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 20rpx 24rpx 0; |
| | | } |
| | | |
| | | &__user { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 14rpx; |
| | | } |
| | | |
| | | &__avatar { |
| | | width: 42rpx; |
| | | height: 42rpx; |
| | | border-radius: 50%; |
| | | border: 2rpx solid rgba(255, 255, 255, 0.7); |
| | | } |
| | | |
| | | &__name { |
| | | font-size: 28rpx; |
| | | font-weight: 500; |
| | | color: #ffffff; |
| | | } |
| | | |
| | | &__status { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 10rpx 16rpx; |
| | | border-radius: 999rpx; |
| | | background: rgba(255, 255, 255, 0.16); |
| | | backdrop-filter: blur(10rpx); |
| | | } |
| | | |
| | | &__status-dot { |
| | | width: 14rpx; |
| | | height: 14rpx; |
| | | border-radius: 50%; |
| | | background: #32d74b; |
| | | margin-right: 10rpx; |
| | | } |
| | | |
| | | &__status-text, |
| | | &__status-arrow { |
| | | font-size: 24rpx; |
| | | color: #ffffff; |
| | | } |
| | | |
| | | &__status-arrow { |
| | | font-size: 18rpx; |
| | | margin-left: 8rpx; |
| | | } |
| | | |
| | | &__stats { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | padding: 34rpx 36rpx 28rpx; |
| | | } |
| | | |
| | | &__stat-item { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | min-width: 160rpx; |
| | | } |
| | | |
| | | &__stat-value { |
| | | font-size: 52rpx; |
| | | line-height: 1; |
| | | font-weight: 700; |
| | | color: #ffffff; |
| | | } |
| | | |
| | | &__stat-label { |
| | | margin-top: 12rpx; |
| | | font-size: 26rpx; |
| | | color: rgba(255, 255, 255, 0.86); |
| | | } |
| | | |
| | | &__tabs { |
| | | display: flex; |
| | | align-items: center; |
| | | height: 88rpx; |
| | | padding: 0 18rpx; |
| | | background: #ffffff; |
| | | } |
| | | |
| | | &__tab { |
| | | position: relative; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | height: 100%; |
| | | padding: 0 18rpx; |
| | | font-size: 32rpx; |
| | | color: #8b9099; |
| | | } |
| | | |
| | | &__tab--active { |
| | | color: #242933; |
| | | font-weight: 700; |
| | | } |
| | | |
| | | &__tab-text { |
| | | font-size: inherit; |
| | | color: inherit; |
| | | } |
| | | |
| | | &__tab-count { |
| | | margin-left: 6rpx; |
| | | font-size: 28rpx; |
| | | color: #8b9099; |
| | | } |
| | | |
| | | &__tab-line { |
| | | position: absolute; |
| | | left: 18rpx; |
| | | right: 18rpx; |
| | | bottom: 0; |
| | | height: 5rpx; |
| | | border-radius: 999rpx; |
| | | background: #2473f5; |
| | | } |
| | | |
| | | &__filter { |
| | | margin-left: auto; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 6rpx; |
| | | padding-right: 10rpx; |
| | | } |
| | | |
| | | &__filter-text { |
| | | font-size: 28rpx; |
| | | color: #9aa1ab; |
| | | |
| | | &--active { |
| | | color: #106efa; |
| | | } |
| | | } |
| | | &__filter-icon { |
| | | width: 28rpx; |
| | | height: 28rpx; |
| | | } |
| | | |
| | | &__body { |
| | | box-sizing: border-box; |
| | | background-color: #F6F9FF; |
| | | } |
| | | |
| | | &__list { |
| | | padding: 22rpx 30rpx calc(22rpx + env(safe-area-inset-bottom)) 30rpx; |
| | | } |
| | | |
| | | &__empty { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | height: 100%; |
| | | padding-bottom: env(safe-area-inset-bottom); |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | &__empty-icon { |
| | | width: 320rpx; |
| | | height: 320rpx; |
| | | } |
| | | } |
| | | |
| | | .filter-popup { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | z-index: 20; |
| | | background: rgba(0, 0, 0, 0.24); |
| | | |
| | | &__panel { |
| | | height: 100%; |
| | | padding: 22rpx 0 26rpx; |
| | | background: #ffffff; |
| | | border-bottom-left-radius: 28rpx; |
| | | border-bottom-right-radius: 28rpx; |
| | | box-sizing: border-box; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | &__section { |
| | | padding: 0 18rpx; |
| | | margin-bottom: 28rpx; |
| | | } |
| | | |
| | | &__title { |
| | | display: block; |
| | | margin-bottom: 20rpx; |
| | | font-size: 28rpx; |
| | | font-weight: 700; |
| | | color: #252b33; |
| | | } |
| | | |
| | | &__options { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 18rpx 20rpx; |
| | | } |
| | | |
| | | &__option { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | width: 226rpx; |
| | | height: 74rpx; |
| | | border-radius: 10rpx; |
| | | background: #f5f5f5; |
| | | border: 2rpx solid transparent; |
| | | box-sizing: border-box; |
| | | |
| | | &--active { |
| | | background: #edf5ff; |
| | | border-color: #3d8cff; |
| | | } |
| | | } |
| | | |
| | | &__option-text { |
| | | font-size: 28rpx; |
| | | color: #4b515a; |
| | | |
| | | .filter-popup__option--active & { |
| | | font-weight: 600; |
| | | color: #2678ff; |
| | | } |
| | | } |
| | | |
| | | &__actions { |
| | | display: flex; |
| | | gap: 24rpx; |
| | | padding: 8rpx 18rpx 0; |
| | | } |
| | | |
| | | &__button { |
| | | flex: 1; |
| | | height: 92rpx; |
| | | line-height: 92rpx; |
| | | border-radius: 999rpx; |
| | | font-size: 34rpx; |
| | | font-weight: 700; |
| | | border: 0; |
| | | padding: 0; |
| | | |
| | | &::after { |
| | | border: 0; |
| | | } |
| | | |
| | | &--reset { |
| | | background: #ebebeb; |
| | | color: #777d86; |
| | | } |
| | | |
| | | &--confirm { |
| | | background: linear-gradient(180deg, #2d82ff 0%, #206ef6 100%); |
| | | color: #ffffff; |
| | | } |
| | | |
| | | &--hover { |
| | | opacity: 0.92; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .order-card { |
| | | margin-bottom: 20rpx; |
| | | padding: 20rpx; |
| | | border-radius: 24rpx; |
| | | background: #ffffff; |
| | | box-shadow: 0 10rpx 24rpx rgba(26, 44, 81, 0.04); |
| | | |
| | | &__head { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | } |
| | | |
| | | &__time-main { |
| | | font-size: 42rpx; |
| | | font-weight: 700; |
| | | color: #ff8d27; |
| | | } |
| | | |
| | | &__time-sub { |
| | | margin-left: 8rpx; |
| | | font-size: 28rpx; |
| | | color: #a3a8b2; |
| | | } |
| | | |
| | | &__price-wrap { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 8rpx; |
| | | |
| | | &--serial-only { |
| | | justify-content: flex-start; |
| | | min-width: 54rpx; |
| | | } |
| | | } |
| | | |
| | | &__serial { |
| | | font-size: 38rpx; |
| | | font-weight: 700; |
| | | line-height: 1; |
| | | color: #2c3139; |
| | | } |
| | | |
| | | &__price { |
| | | font-size: 44rpx; |
| | | font-weight: 700; |
| | | color: #ff3b30; |
| | | } |
| | | |
| | | &__meta { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | gap: 16rpx; |
| | | margin-top: 12rpx; |
| | | } |
| | | |
| | | &__extra { |
| | | flex-shrink: 0; |
| | | font-size: 24rpx; |
| | | color: #a0a5af; |
| | | } |
| | | |
| | | &__tags { |
| | | display: flex; |
| | | flex: 1; |
| | | flex-wrap: wrap; |
| | | gap: 10rpx; |
| | | } |
| | | |
| | | &__tag-wrap { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | &__tag-icon { |
| | | width: 108rpx; |
| | | height: 40rpx; |
| | | } |
| | | |
| | | &__tag { |
| | | padding: 4rpx 10rpx; |
| | | border-radius: 8rpx; |
| | | font-size: 22rpx; |
| | | line-height: 1.2; |
| | | border: 1rpx solid #2473f5; |
| | | color: #2473f5; |
| | | |
| | | &--orange { |
| | | border-color: #ff9c45; |
| | | background: #ff9c45; |
| | | color: #ffffff; |
| | | } |
| | | |
| | | &--red { |
| | | border-color: #ff6c57; |
| | | color: #ff6c57; |
| | | } |
| | | |
| | | &--blue-light { |
| | | border-color: #74a9ff; |
| | | color: #74a9ff; |
| | | } |
| | | } |
| | | |
| | | &__route { |
| | | display: flex; |
| | | margin-top: 20rpx; |
| | | } |
| | | |
| | | &__route-side { |
| | | width: 64rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | &__distance-top, |
| | | &__distance-bottom { |
| | | font-size: 22rpx; |
| | | font-weight: 600; |
| | | color: #555b66; |
| | | text-align: center; |
| | | } |
| | | |
| | | &__line { |
| | | position: relative; |
| | | width: 4rpx; |
| | | flex: 1; |
| | | min-height: 86rpx; |
| | | margin: 10rpx 0; |
| | | border-radius: 999rpx; |
| | | background: #d8dbe1; |
| | | |
| | | &::before, |
| | | &::after { |
| | | content: ''; |
| | | position: absolute; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | width: 14rpx; |
| | | height: 14rpx; |
| | | border-radius: 50%; |
| | | background: #6a6f79; |
| | | } |
| | | |
| | | &::before { |
| | | top: -4rpx; |
| | | } |
| | | |
| | | &::after { |
| | | bottom: -4rpx; |
| | | } |
| | | } |
| | | |
| | | &__route-main { |
| | | flex: 1; |
| | | } |
| | | |
| | | &__route-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | gap: 16rpx; |
| | | |
| | | &--destination { |
| | | margin-top: 20rpx; |
| | | } |
| | | } |
| | | |
| | | &__route-texts { |
| | | flex: 1; |
| | | min-width: 0; |
| | | } |
| | | |
| | | &__route-title { |
| | | display: block; |
| | | font-size: 40rpx; |
| | | font-weight: 700; |
| | | color: #2d3139; |
| | | line-height: 1.3; |
| | | } |
| | | |
| | | &__route-desc { |
| | | display: block; |
| | | margin-top: 8rpx; |
| | | font-size: 28rpx; |
| | | color: #9ea4ae; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | &__nav { |
| | | width: 48rpx; |
| | | height: 48rpx; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | &__goods { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | height: 70rpx; |
| | | padding: 0 20rpx; |
| | | margin-top: 20rpx; |
| | | border-radius: 16rpx; |
| | | background: #f4f5f7; |
| | | } |
| | | |
| | | &__goods-text { |
| | | flex: 1; |
| | | font-size: 28rpx; |
| | | color: #7a818d; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | |
| | | &__goods-arrow { |
| | | margin-left: 12rpx; |
| | | font-size: 24rpx; |
| | | color: #a4a9b1; |
| | | } |
| | | |
| | | &__button { |
| | | margin-top: 24rpx; |
| | | width: 100%; |
| | | height: 88rpx; |
| | | line-height: 88rpx; |
| | | border-radius: 999rpx; |
| | | background: linear-gradient(180deg, #2b7fff 0%, #1f6ff3 100%); |
| | | font-size: 34rpx; |
| | | font-weight: 700; |
| | | color: #ffffff; |
| | | border: 0; |
| | | padding: 0; |
| | | |
| | | &::after { |
| | | border: 0; |
| | | } |
| | | |
| | | &--hover { |
| | | opacity: 0.92; |
| | | } |
| | | } |
| | | |
| | | &__actions { |
| | | margin-top: 24rpx; |
| | | |
| | | &--pickup, |
| | | &--delivering { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 22rpx; |
| | | } |
| | | } |
| | | |
| | | &__icon-actions { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 20rpx; |
| | | flex-shrink: 0; |
| | | |
| | | &--single { |
| | | gap: 0; |
| | | } |
| | | } |
| | | |
| | | &__icon-action { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 74rpx; |
| | | } |
| | | |
| | | &__action-icon { |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | border-radius: 8rpx; |
| | | background: #f7f8fa; |
| | | } |
| | | |
| | | &__action-text { |
| | | margin-top: 8rpx; |
| | | font-size: 26rpx; |
| | | line-height: 1; |
| | | color: #5b616b; |
| | | } |
| | | |
| | | &__button--code { |
| | | flex: 1; |
| | | margin-top: 0; |
| | | } |
| | | } |
| | | </style> |