app
MrShi
2026-04-25 80c0aa2a864071ad77d6c7de7c508348b2eb2fee
app/pages/index/index.vue
@@ -1,111 +1,1547 @@
<template>
   <view class="index">
      <view class="index_list">
         <view class="index_list_item">
            <view class="index_list_item_info">
               <text>SHE事件上报</text>
               <text>FAC/NM</text>
   <view class="hall-page">
      <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="centerUserInfo.fullImgUrl || '/static/image/ic_pic@2x.png'" mode="aspectFill"></image>
               <text class="hall-page__name">{{ centerUserInfo.name }}</text>
            </view>
            <image src="/static/bg_a.png" mode="widthFix"></image>
            <view class="hall-page__status" v-if="userInfo.auditStatus === 3" @click="openStatusPicker">
               <view class="hall-page__status-dot" :class="{ 'hall-page__status-dot--offline': acceptingStatus === 0 }"></view>
               <text class="hall-page__status-text">{{ acceptingStatus === 1 ? '接单中' : '已下线' }}</text>
               <text class="hall-page__status-arrow">▼</text>
            </view>
            <view class="hall-page__user" style="opacity: 0;">
               <image class="hall-page__avatar" :src="centerUserInfo.fullImgUrl || '/static/image/ic_pic@2x.png'" mode="aspectFill"></image>
               <text class="hall-page__name">{{ centerUserInfo.name }}</text>
            </view>
         </view>
         <view class="index_list_item">
            <view class="index_list_item_info">
               <text>跌绊滑风险上报</text>
               <text>TAG</text>
         <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">{{ userInfo.auditStatus === 3 ? item.value : '-' }}</text>
               <text class="hall-page__stat-label">{{ item.label }}</text>
            </view>
            <image src="/static/bg_b.png" mode="widthFix"></image>
         </view>
         <view class="hall-page__tabs">
            <view v-for="tab in displayTabs" :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">{{ userInfo.auditStatus === 3 ? 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>
            <scroll-view class="filter-popup__content" scroll-y>
               <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>
            </scroll-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 class="hall-page__verified" v-if="userInfo.auditStatus !== 3">
            <image src="/static/image/default_unverified@2x.png" mode="widthFix"></image>
            <button @click="toDriverCertification">去认证</button>
         </view>
         <view v-else-if="userInfo.auditStatus === 3 && currentOrderList.length" class="hall-page__list">
            <view v-for="(item, index) in currentOrderList" :key="item.id" class="order-card" @click="goToOrderDetail(item, index)">
               <view class="order-card__head">
                  <view class="order-card__time">
                     <text class="order-card__time-main">{{ item.remainMinutes }}</text>
                     <text class="order-card__time-sub">分钟</text>
                  </view>
                  <view v-if="activeTab === 'hall'" class="order-card__price-wrap">
                     <text class="order-card__price">¥{{ (item.driverFee / 100).toFixed(1) }}</text>
                  </view>
                  <view v-else class="order-card__price-wrap order-card__price-wrap--serial-only">
                     <text v-if="item.code" class="order-card__serial">#{{ index + 1 }}</text>
                  </view>
               </view>
               <view class="order-card__meta">
                  <view class="order-card__tags">
                     <image v-if="item.isUrgent === 1" class="order-card__tag-img" src="/static/image/ic_jisuda@2x.png" mode="widthFix"></image>
                     <image v-else class="order-card__tag-img" src="/static/image/ic_biaosuda@2x.png" mode="widthFix"></image>
                     <text v-if="item.isValuable === true" class="order-card__tag order-card__tag--orange">贵重物品</text>
                     <text v-else class="order-card__tag order-card__tag--blue">{{ item.goodLevelName }}</text>
                  </view>
                  <text v-if="activeTab === 'hall' && item.urgentAmount" class="order-card__extra">含加急¥{{ item.urgentAmount / 100 }}</text>
               </view>
               <view class="order-card__route">
                  <view class="order-card__route-side">
                     <text class="order-card__distance-top">{{ item.depositDistance }}</text>
                     <view class="order-card__line"></view>
                     <text class="order-card__distance-bottom">{{ item.takeDistance }}</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.depositShopName }}</text>
                           <text class="order-card__route-desc">{{ item.depositShopAddress }}</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">
                           <template v-if="item.takeShopId">
                              <text class="order-card__route-title">{{ item.takeName }}</text>
                              <text class="order-card__route-desc">{{ item.takeAddress }}</text>
                           </template>
                           <text v-else class="order-card__route-title">{{ item.takeAddress }}</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" v-if="item.items && item.items.length > 0">
                  <text class="order-card__goods-text">{{ item.items ? item.items.map(i => `${i.name}*${i.quantity}`).join('、') : '无' }}</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="handleCancelOrder(item)">
                        <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="handleCall(item)">
                           <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="handleShowPickupCode(item)">取货码</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="handleCall(item)">
                           <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="handleShowPickupCode(item)">存件码</button>
                  </template>
                  <button v-else class="order-card__button" hover-class="order-card__button--hover" @click="handleGrabOrder(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="取消订单确认"
         @confirm="confirmCancelOrder">
         <view style="text-align: center;color: #333333;font-size: 28rpx;font-weight: 400;">
            您今日还可取消 {{ cancelRemain }} 次订单,次数用尽后今日将无法接单,是否确认取消?
         </view>
      </u-modal>
      <!-- 立即抢单 -->
      <u-modal
         :show="showGrabModal"
         showCancelButton
         @cancel="showGrabModal = false"
         cancelColor="#666666"
         confirmColor="#0055FF"
         title="温馨提示"
         @confirm="confirmGrabOrder">
         <view style="text-align: center;color: #333333;font-size: 28rpx;font-weight: 400;">
            {{ selectedGrabOrder && selectedGrabOrder.hasOversized === 1 ? '本订单有特大件尺寸行李,请确认是否继续抢单?' : '是否确认接单?' }}
         </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>{{ activeTab === 'delivering' ? '存件码' : '取货码' }}</text>
               <image src="/static/image/ic_close@2x.png" mode="widthFix" @click="show1 = false"></image>
            </view>
            <view class="qrcode-image">
               <image v-if="selectedPickupOrder && selectedPickupOrder.driverVerifyCode" :src="'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=' + selectedPickupOrder.driverVerifyCode" mode="widthFix"></image>
            </view>
            <view class="qrcode-btn" hover-class="qrcode-btn--hover" @click="show1 = false">关闭</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>
      <u-picker
         :show="showStatusPicker"
         confirmColor="#10B2FA"
         keyName="text"
         :columns="[statusOptions]"
         @confirm="confirmStatus"
         @cancel="showStatusPicker = false" />
   </view>
</template>
<script>
   import { mapState } from 'vuex'
   export default {
      computed: {
         ...mapState(['userInfo'])
      },
      data() {
         return {
            title: 'Hello'
            tts: null,
            show: false,
            show1: false,
            showGrabModal: false,
            selectedGrabOrder: null,
            selectedCancelOrder: null,
            cancelRemain: 0,
            selectedPickupOrder: null,
            showPhotoDeliverPopup: false,
            routeInfo: null,
            statusBarHeight: 0,
            headerHeight: 0,
            tabbarHeight: 0,
            scrollHeight: 0,
            showFilterPopup: false,
            acceptingStatus: 0,
            showStatusPicker: false,
            statusOptions: [
               { text: '上线', value: 1 },
               { text: '下线', value: 0 }
            ],
            centerUserInfo: {},
            activeTab: 'hall',
            categoryList: [],
            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: '不限'
            },
            stats: [
               { value: '-', label: '服务分' },
               { value: '-', label: '今日预计佣金' },
               { value: '-', label: '今日接单' }
            ],
            tabs: [
               { label: '抢单大厅', value: 'hall' },
               { label: '待取货', value: 'pickup' },
               { label: '配送中', value: 'delivering' }
            ],
            orderList: [],
            pickupOrderList: [],
            deliveringOrderList: [],
            hallPage: 1,
            hallPageSize: 10,
            hallLoading: false,
            hallHasMore: true,
            pickupLoading: false,
            deliveringLoading: false,
            activeOrderCount: null
         }
      },
      onLoad() {
      watch: {
         activeTab(newVal) {
            this.loadOrdersByTab(newVal)
         }
      },
      methods: {
         jump(type) {
            switch (type) {
               case 1:
                  uni.navigateTo({
                     url: '/pages/reporting_she/reporting_she'
                  })
                  break;
               case 2:
                  uni.navigateTo({
                     url: '/pages/riskReporting/riskReporting'
                  })
                  break;
               case 3:
                  uni.navigateTo({
                     url: '/pages/report_dca/report_dca'
                  })
                  break;
      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)
         this.loadOrdersByTab(this.activeTab)
      },
      onShow() {
         this.getCenterInfo()
         this.getCategoryListData()
         this.getActiveOrderCount()
      },
      onReachBottom() {
         if (this.activeTab === 'hall') {
            this.hallPage++
            this.getHallOrders()
         }
      },
      computed: {
         ...mapState(['userInfo', 'token']),
         displayTabs() {
            return this.tabs.map(tab => {
               if (tab.value === 'pickup') {
                  return { ...tab, count: this.activeOrderCount?.grabbedCount }
               } else if (tab.value === 'delivering') {
                  return { ...tab, count: this.activeOrderCount?.deliveringCount }
               }
               return tab
            })
         },
         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'
            }
         }
      },
      onReady() {
         this.initTTS() // 页面渲染完成初始化语音
      },
      methods: {
         getActiveOrderCount() {
            this.$u.api.activeOrderCount().then(res => {
               if (res.code === 200) {
                  this.activeOrderCount = res.data
               }
            }).catch((err) => {
               this.activeOrderCount = null
            })
         },
         handleGrabOrder(item) {
            this.selectedGrabOrder = item
            this.showGrabModal = true
         },
         confirmGrabOrder() {
            if (!this.selectedGrabOrder) return
            this.$u.api.grabOrder({ orderId: this.selectedGrabOrder.id }).then(res => {
               this.showGrabModal = false
               if (res.code === 200) {
                  uni.showToast({ title: '抢单成功', icon: 'success' })
                  this.hallPage = 1
                  this.hallHasMore = true
                  this.orderList = []
                  this.getHallOrders()
                  this.getActiveOrderCount()
                  this.getCenterInfo()
               } else {
                  uni.showToast({ title: res.message || '抢单失败', icon: 'none' })
               }
            }).catch(() => {
               this.showGrabModal = false
            })
         },
         handleCancelOrder(item) {
            this.selectedCancelOrder = item
            this.$u.api.cancelLimit().then(res => {
               if (res.code === 200) {
                  this.cancelRemain = res.data.remain
               }
            }).finally(() => {
               this.show = true
            })
         },
         confirmCancelOrder() {
            if (!this.selectedCancelOrder) return
            this.$u.api.cancelOrder({ orderId: this.selectedCancelOrder.id }).then(res => {
               this.show = false
               if (res.code === 200) {
                  uni.showToast({ title: '取消成功', icon: 'success' })
                  this.getPickupOrders()
                  this.getCenterInfo()
                  this.getActiveOrderCount()
               } else {
                  uni.showToast({ title: res.message || '取消失败', icon: 'none' })
               }
            }).catch(() => {
               this.show = false
            })
         },
         handleCall(item) {
            if (item.contactPhone) {
               uni.makePhoneCall({
                  phoneNumber: item.contactPhone
               })
            }
         },
         handleShowPickupCode(item) {
            this.selectedPickupOrder = item
            this.show1 = true
         },
         goToOrderDetail(item, index) {
            uni.navigateTo({
               url: `/pages/order-detail/order-detail?id=${item.id}&index=${index + 1}`
            })
         },
         getCenterInfo() {
            this.$u.api.centerInfo().then(res => {
               if (res.code === 200) {
                  this.acceptingStatus = res.data.acceptingStatus
                  this.centerUserInfo = {
                     fullImgUrl: res.data.fullImgUrl,
                     name: res.data.name
                  }
                  this.stats = [
                     { value: res.data.score, label: '服务分' },
                     { value: res.data.todayCommission, label: '今日预计佣金' },
                     { value: res.data.todayOrderCount, label: '今日接单' }
                  ]
               }
            })
         },
         getCategoryListData() {
            this.$u.api.getCategoryList({ type: 3 }).then(res => {
               if (res.code === 200) {
                  this.categoryList = res.data || []
                  this.filterSections.forEach(section => {
                     if (section.key === 'level') {
                        section.options = ['不限', ...this.categoryList.map(item => item.name)]
                     }
                  })
               }
            })
         },
         loadOrdersByTab(tab) {
            if (tab === 'hall') {
               this.hallPage = 1
               this.hallHasMore = true
               this.orderList = []
               this.getHallOrders()
            } else if (tab === 'pickup') {
               this.pickupOrderList = []
               this.getPickupOrders()
            } else if (tab === 'delivering') {
               this.deliveringOrderList = []
               this.getDeliveringOrders()
            }
         },
         getHallOrders() {
            if (this.hallLoading || !this.hallHasMore) {
               return
            }
            this.hallLoading = true
            let distance = null
            if (this.selectedFilters.distance !== '不限') {
               const distanceText = this.selectedFilters.distance
               if (distanceText.includes('km')) {
                  distance = parseInt(distanceText) * 1000
               } else {
                  distance = parseInt(distanceText)
               }
            }
            const sortTypeMap = {
               '综合排序': 1,
               '距离最近': 2
            }
            const sortType = this.selectedFilters.sort !== '不限' ? (sortTypeMap[this.selectedFilters.sort] || null) : null
            let gradeId = null
            if (this.selectedFilters.level !== '不限') {
               const selectedCategory = this.categoryList.find(item => item.name === this.selectedFilters.level)
               if (selectedCategory) {
                  gradeId = selectedCategory.id
               }
            }
            console.log('接单大厅:', { distance, gradeId, sortType })
            this.$u.api.grabOrderHall({
               capacity: this.hallPageSize,
               page: this.hallPage,
               model: {
                  distance: distance,
                  gradeId: gradeId,
                  sortType: sortType
               }
            }).then(res => {
               console.log('接单大厅', res)
               this.hallLoading = false
               if (res.code === 200) {
                  const list = res.data.records || []
                  this.orderList = this.hallPage === 1 ? list : this.orderList.concat(list)
                  this.hallHasMore = list.length >= this.hallPageSize
               }
            }).catch((err) => {
               this.hallLoading = false
            })
         },
         getPickupOrders() {
            if (this.pickupLoading) return
            this.pickupLoading = true
            this.$u.api.activeOrders({ status: 3 }).then(res => {
               console.log('待取货:', res)
               this.pickupLoading = false
               if (res.code === 200) {
                  this.pickupOrderList = res.data.records || res.data || []
               }
            }).catch((err) => {
               this.pickupLoading = false
            })
         },
         getDeliveringOrders() {
            if (this.deliveringLoading) return
            this.deliveringLoading = true
            this.$u.api.activeOrders({ status: 4 }).then(res => {
               console.log('配送中:', res)
               this.deliveringLoading = false
               if (res.code === 200) {
                  this.deliveringOrderList = res.data || []
               }
            }).catch((err) => {
               this.deliveringLoading = false
            })
         },
         openStatusPicker() {
            this.showStatusPicker = true
         },
         confirmStatus(e) {
            this.showStatusPicker = false
            const selectedValue = e.value[0]
            this.$u.api.updateAcceptingStatus({ status: selectedValue.value }).then(res => {
               if (res.code === 200) {
                  this.getCenterInfo()
               }
            })
         },
         toDriverCertification() {
            uni.navigateTo({
               url: '/pages/driver-certification/driver-certification'
            })
         },
         toggleFilterPopup(show) {
            this.showFilterPopup = show
         },
         selectFilter(key, option) {
            this.selectedFilters = {
               ...this.selectedFilters,
               [key]: option
            }
         },
         resetFilters() {
            this.selectedFilters = {
               sort: '综合排序',
               level: '不限',
               distance: '不限'
            }
            this.showFilterPopup = false
            if (this.activeTab === 'hall') {
               this.hallPage = 1
               this.hallHasMore = true
               this.orderList = []
               this.getHallOrders()
            }
         },
         confirmFilters() {
            this.showFilterPopup = false
            if (this.activeTab === 'hall') {
               this.hallPage = 1
               this.hallHasMore = true
               this.orderList = []
               this.getHallOrders()
            }
         },
         initTTS() {
            if (uni.getSystemInfoSync().platform !== 'android') {
               console.log('仅支持安卓')
               return
            }
            try {
               // 导入安卓原生类
               const TextToSpeech = plus.android.importClass('android.speech.tts.TextToSpeech')
               const Locale = plus.android.importClass('java.util.Locale')
               // 创建TTS
               this.tts = new TextToSpeech(plus.android.runtimeMainActivity(), {
                  onInit: (status) => {
                     if (status == 0) {
                        // 设置中文
                        this.tts.setLanguage(Locale.CHINA)
                        console.log('语音初始化成功')
                     }
                  }
               })
            } catch (e) {
               console.log('初始化失败', e)
            }
         },
         speak(text) {
            if (!this.tts) {
               uni.showToast({
                  title: '语音未准备好',
                  icon: 'none'
               })
               return
            }
            try {
               // 安卓原生播报(QUEUE_FLUSH = 立即播报,打断上一条)
               this.tts.speak(text, 0, null)
            } catch (err) {
               console.log('播报失败', err)
            }
         },
         stopSpeak() {
            if (this.tts) this.tts.stop()
         }
      },
      onUnload() {
         if (this.tts) {
            this.tts.stop()
            this.tts.shutdown()
         }
      }
   }
</script>
<style lang="scss" scoped>
   .index {
      width: 100vw;
      padding: 30rpx;
      box-sizing: border-box;
      height: calc(100vh - 44px - 50px);
      background: linear-gradient( 180deg, #B5D2FF 0%, #FFFFFF 100%);
      .index_list {
         width: 100%;
         display: flex;
         flex-direction: column;
         margin-top: 12rpx;
         .index_list_item {
   .hall-page {
      position: relative;
      height: 100vh;
      background: #f5f6f8;
      overflow: hidden;
      .qrcode {
         padding: 36rpx 30rpx;
         box-sizing: border-box;
         .qrcode-title {
            width: 100%;
            height: 200rpx;
            margin-bottom: 30rpx;
            position: relative;
            .index_list_item_info {
               width: 100%;
               height: 100%;
               padding: 0 48rpx;
               box-sizing: border-box;
               display: flex;
               justify-content: center;
               flex-direction: column;
               position: relative;
               z-index: 99;
               text {
                  &:nth-child(1) {
                     font-weight: bold;
                     font-size: 34rpx;
                     color: #FFFFFF;
                  }
                  &:nth-child(2) {
                     font-weight: 400;
                     font-size: 26rpx;
                     color: rgba(255,255,255,0.6);
                     margin-top: 10rpx;
                  }
               }
            }
            display: flex;
            align-items: center;
            justify-content: space-between;
            image {
               width: 100%;
               height: 100%;
               position: absolute;
               top: 0;
               left: 0;
               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;
            }
         }
      }
      &__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;
         &--offline {
            background: #ff3b30;
         }
      }
      &__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: 30rpx;
      }
      &__verified {
         width: 100%;
         height: 100%;
         display: flex;
         flex-direction: column;
         align-items: center;
         justify-content: center;
         image {
            width: 320rpx;
            height: 320rpx;
         }
         button {
            width: 160rpx;
            height: 64rpx;
            line-height: 64rpx;
            text-align: center;
            padding: 0 !important;
            border: 0 !important;
            background: #106EFA;
            border-radius: 50rpx;
            margin-top: 40rpx;
            font-weight: 400;
            font-size: 28rpx;
            color: #FFFFFF;
         }
      }
      &__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 {
         display: flex;
         flex-direction: column;
         height: 100%;
         background: #ffffff;
         border-bottom-left-radius: 28rpx;
         border-bottom-right-radius: 28rpx;
      }
      &__content {
         flex: 1;
         overflow-y: auto;
         padding: 22rpx 18rpx 0;
      }
      &__section {
         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 {
         flex-shrink: 0;
         display: flex;
         gap: 24rpx;
         padding: 8rpx 18rpx 26rpx;
      }
      &__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-img {
         width: 108rpx;
         height: 40rpx;
      }
      &__tag {
         padding: 4rpx 10rpx;
         border-radius: 8rpx;
         font-size: 22rpx;
         line-height: 1.2;
         display: flex;
         align-items: center;
         justify-content: center;
         border: 1rpx solid #2473f5;
         color: #2473f5;
         &--orange {
            border: none;
            background: linear-gradient(319deg, #EE9D0E 0%, #FF4E4E 100%);
            color: #ffffff;
         }
         &--red {
            border-color: #ff6c57;
            color: #ff6c57;
         }
         &--blue-light {
            border-color: #74a9ff;
            color: #74a9ff;
         }
         &--blue {
            background: rgba(16,178,250,0.08);
            border-radius: 15rpx;
            border: 2rpx solid #106EFA;
            font-weight: 400;
            font-size: 22rpx;
            color: #106EFA;
         }
      }
      &__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>