| | |
| | | </template> |
| | | <template v-else> |
| | | <view class="empty-order">暂无订单请耐心等待</view> |
| | | <button class="cancel-button" @tap="cancelReceiving">取消接单</button> |
| | | <button |
| | | class="cancel-button" |
| | | :loading="cancelingReceiving" |
| | | :disabled="cancelingReceiving" |
| | | @tap="cancelReceiving" |
| | | >取消接单</button> |
| | | </template> |
| | | </template> |
| | | <template v-else-if="auditStatus === 'approved'"> |
| | |
| | | import { playNotificationAlert } from '../../services/message' |
| | | import { getWorkbenchData } from '../../services/workbench' |
| | | import { formatDuration, maskPhone } from '../../utils/format' |
| | | import { resolveApiAssetUrl } from '../../utils/request' |
| | | import { formatRatingStars } from '../../utils/rating' |
| | | import { routeTo } from '../../utils/router' |
| | | import { getStatusBarHeight, upxToPx } from '../../utils/system' |
| | |
| | | |
| | | function isSameId(left, right) { |
| | | return left !== undefined && left !== null && right !== undefined && right !== null && String(left) === String(right) |
| | | } |
| | | |
| | | function getDialablePhone(phone) { |
| | | const value = String(phone || '').replace(/\D/g, '') |
| | | return /^1\d{10}$/.test(value) ? value : '' |
| | | } |
| | | |
| | | function buildReceivingConfig(session, scenics = [], fallback = {}) { |
| | |
| | | latitude: meetingPoint.latitude ?? fallback.latitude, |
| | | longitude: meetingPoint.longitude ?? fallback.longitude |
| | | } |
| | | } |
| | | |
| | | function parseApiDateTime(value) { |
| | | if (!value) { |
| | | return NaN |
| | | } |
| | | return new Date(String(value).trim().replace(' ', 'T')).getTime() |
| | | } |
| | | |
| | | export default { |
| | |
| | | countdownTimer: null, |
| | | refreshTimer: null, |
| | | refreshing: false, |
| | | refreshPromise: null, |
| | | receivingActionVersion: 0, |
| | | cancelingReceiving: false, |
| | | forceOffline: false, |
| | | startingLecture: false, |
| | | dialog: { |
| | | visible: false, |
| | |
| | | ? order.customers |
| | | : [{ phone: order.customerPhone, people: order.quantity }] |
| | | customers.forEach((customer) => { |
| | | const phone = customer.phone || '' |
| | | rows.push({ |
| | | no: rows.length + 1, |
| | | phone: maskPhone(customer.phone || ''), |
| | | phone: maskPhone(phone), |
| | | phoneNumber: phone, |
| | | people: Number(customer.people || 0) |
| | | }) |
| | | }) |
| | |
| | | routeTo('/pages/login/index', 'reLaunch') |
| | | return |
| | | } |
| | | this.forceOffline = false |
| | | this.loadData() |
| | | this.startRefresh() |
| | | }, |
| | |
| | | methods: { |
| | | loadData({ silent = false } = {}) { |
| | | if (this.refreshing) { |
| | | return Promise.resolve() |
| | | return this.refreshPromise || Promise.resolve() |
| | | } |
| | | this.refreshing = true |
| | | const actionVersion = this.receivingActionVersion |
| | | const previousOrderId = this.activeReceivingOrder.id |
| | | const previousOnlineStatus = this.guide.onlineStatus |
| | | return getWorkbenchData().then((workbench) => { |
| | | const isCurrentLoad = () => actionVersion === this.receivingActionVersion |
| | | const loadRequest = getWorkbenchData().then((workbench) => { |
| | | if (!isCurrentLoad()) { |
| | | return null |
| | | } |
| | | const remoteGuide = workbench.guide || {} |
| | | const loginGuide = (getLoginState() || {}).guide || {} |
| | | const baseGuide = { |
| | | ...loginGuide, |
| | | ...remoteGuide, |
| | | name: resolveGuideName(remoteGuide, loginGuide), |
| | | phone: remoteGuide.mobile || loginGuide.phone |
| | | phone: remoteGuide.mobile || loginGuide.phone, |
| | | avatar: resolveApiAssetUrl(remoteGuide.avatar || loginGuide.avatar || '') |
| | | } |
| | | this.accountState = workbench.accountState || 'pending' |
| | | this.auditRemark = workbench.auditRemark || '' |
| | |
| | | getCurrentGuideReceivingSession(), |
| | | this.loadReceivingScenics() |
| | | ]).then(([localGuide, orders, receivingSession, receivingScenics]) => { |
| | | if (!isCurrentLoad()) { |
| | | return null |
| | | } |
| | | const activeOrders = orders.filter((item) => ['pending', 'accepted', 'arrived', 'serving'].includes(item.status)) |
| | | const keepOffline = this.forceOffline && activeOrders.length === 0 |
| | | const nextGuide = { |
| | | ...baseGuide, |
| | | ...localGuide, |
| | | name: resolveGuideName(localGuide, baseGuide), |
| | | phone: localGuide.phone || baseGuide.phone, |
| | | onlineStatus: receivingSession |
| | | onlineStatus: keepOffline ? 'offline' : receivingSession |
| | | ? (receivingSession.status === 'IN_SERVICE' ? 'serving' : 'online') |
| | | : 'offline', |
| | | receivingConfig: buildReceivingConfig(receivingSession, receivingScenics, baseGuide.receivingConfig || {}) |
| | | receivingConfig: keepOffline |
| | | ? null |
| | | : buildReceivingConfig(receivingSession, receivingScenics, baseGuide.receivingConfig || {}) |
| | | } |
| | | this.updateGuide(nextGuide) |
| | | this.updateOrders(orders) |
| | |
| | | } |
| | | }).finally(() => { |
| | | this.refreshing = false |
| | | this.refreshPromise = null |
| | | }) |
| | | this.refreshPromise = loadRequest |
| | | return loadRequest |
| | | }, |
| | | loadReceivingScenics() { |
| | | if (this.receivingScenicsLoaded) { |
| | |
| | | if (!this.isReceiving || !this.hasReceivingOrders) { |
| | | return |
| | | } |
| | | this.countdownSeconds = this.isServing ? (this.activeReceivingOrder.durationMinutes || 30) * 60 : 15 * 60 |
| | | this.updateCountdown() |
| | | if (this.countdownSeconds <= 0) { |
| | | return |
| | | } |
| | | this.countdownTimer = setInterval(() => { |
| | | this.updateCountdown() |
| | | if (this.countdownSeconds <= 0) { |
| | | this.clearCountdown() |
| | | return |
| | | } |
| | | this.countdownSeconds -= 1 |
| | | }, 1000) |
| | | }, |
| | | updateCountdown() { |
| | | const order = this.activeReceivingOrder |
| | | const durationMinutes = this.isServing ? Number(order.durationMinutes || 30) : 15 |
| | | const startAt = parseApiDateTime(this.isServing ? order.lectureStartTime : order.receiveStartTime) |
| | | if (!Number.isFinite(startAt)) { |
| | | this.countdownSeconds = durationMinutes * 60 |
| | | return |
| | | } |
| | | const remainingMilliseconds = startAt + durationMinutes * 60 * 1000 - Date.now() |
| | | this.countdownSeconds = Math.max(0, Math.ceil(remainingMilliseconds / 1000)) |
| | | }, |
| | | clearCountdown() { |
| | | if (this.countdownTimer) { |
| | |
| | | }, |
| | | confirmDialog() { |
| | | if (this.dialog.action === 'callCustomer') { |
| | | uni.showToast({ title: '电话能力待接真实号码', icon: 'none' }) |
| | | const phoneNumber = getDialablePhone(this.pendingCustomer && this.pendingCustomer.phoneNumber) |
| | | if (!phoneNumber) { |
| | | uni.showToast({ title: '客户号码无效,无法拨打', icon: 'none' }) |
| | | return |
| | | } |
| | | uni.makePhoneCall({ |
| | | phoneNumber, |
| | | fail: (error) => { |
| | | if (!(error && String(error.errMsg || '').includes('cancel'))) { |
| | | uni.showToast({ title: '无法发起电话,请检查设备电话权限', icon: 'none' }) |
| | | } |
| | | } |
| | | }) |
| | | return |
| | | } |
| | | if (this.dialog.action === 'finishLecture') { |
| | |
| | | return |
| | | } |
| | | if (this.dialog.action === 'cancelReceiving') { |
| | | updateGuideOnlineStatus('offline').then(() => { |
| | | this.loadData() |
| | | uni.showToast({ title: '已取消接单', icon: 'none' }) |
| | | }) |
| | | if (this.cancelingReceiving) { |
| | | return |
| | | } |
| | | this.cancelingReceiving = true |
| | | this.receivingActionVersion += 1 |
| | | const pendingLoad = this.refreshPromise || Promise.resolve() |
| | | updateGuideOnlineStatus('offline') |
| | | .then(() => { |
| | | this.forceOffline = true |
| | | this.updateGuide({ ...this.guide, onlineStatus: 'offline', receivingConfig: null }) |
| | | this.clearCountdown() |
| | | return pendingLoad |
| | | }) |
| | | .then(() => this.loadData({ silent: true })) |
| | | .then(() => { |
| | | uni.showToast({ title: '已取消接单', icon: 'none' }) |
| | | }) |
| | | .catch((error) => { |
| | | uni.showToast({ title: error.message || '取消接单失败', icon: 'none' }) |
| | | }) |
| | | .finally(() => { |
| | | this.cancelingReceiving = false |
| | | }) |
| | | } |
| | | } |
| | | } |