import { getMyOrders, cancelOrderApi } from '@/api/order/subOrder' import { listAllService } from '@/api/service/list' import { reportGps } from '@/utils/gps' import customTabbar from '@/components/custom-tabbar/index.vue' export default { components: { customTabbar }, data() { return { currentTab: 0, tabs: ['待接送/服务', '配送/服务中', '已完成', '已取消'], typeFilterOptions: ['全部类型'], currentTypeFilterIdx: 0, activeDropdown: 0, hasTimeFilter: false, currentMonth: '2026年2月', weekDays: ['日', '一', '二', '三', '四', '五', '六'], calendarDays: [], selectedDateRange: [], allOrderList: [], serviceList: [], searchContent: '', startServiceTime: '', endServiceTime: '', showPetModal: false, currentPetInfo: {}, showNavModal: false, navTargetItem: null, navTargetPointType: '', activeCallItem: null, showRemarkInput: false, remarkText: '' } }, created() { this.initCalendar(); }, async onLoad() { await this.loadServiceList() await this.loadOrders() // 显式请求一次定位授权 reportGps(true).catch(e => console.log('Init GPS check skipped', e)); }, onShow() { uni.hideTabBar() // 此处不需要重复调用,因为逻辑可能在onLoad已处理, // 或者如果需要每次进入都刷新,可以保留,但需确保顺序 this.loadOrders() }, async onPullDownRefresh() { try { await this.loadServiceList() await this.loadOrders() } finally { uni.stopPullDownRefresh() } }, watch: { currentTab() { this.loadOrders() }, currentTypeFilterIdx() { this.loadOrders() }, searchContent() { // 搜索内容变化时,自动重新加载订单 this.loadOrders() } }, computed: { orderList() { return this.allOrderList; } }, methods: { async loadServiceList() { try { const res = await listAllService() this.serviceList = res.data || [] this.typeFilterOptions = ['全部类型', ...this.serviceList.map(s => s.name)] } catch (err) { console.error('获取服务类型失败:', err) } }, async loadOrders() { try { const statusMap = { 0: 2, 1: 3, 2: 4, 3: 5 } const serviceId = this.currentTypeFilterIdx > 0 ? this.serviceList[this.currentTypeFilterIdx - 1]?.id : undefined const params = { status: statusMap[this.currentTab], content: this.searchContent || undefined, service: serviceId, startServiceTime: this.startServiceTime || undefined, endServiceTime: this.endServiceTime || undefined } console.log('订单列表请求参数:', params) const res = await getMyOrders(params) console.log('订单列表响应:', res) const orders = res.rows || [] console.log('订单数量:', orders.length) this.allOrderList = orders.map(order => this.transformOrder(order, this.currentTab)) } catch (err) { console.error('获取订单列表失败:', err) this.allOrderList = [] } }, transformOrder(order, tabIndex) { const service = this.serviceList.find(s => s.id === order.service) const serviceText = service?.name || '未知' const serviceIcon = service?.iconUrl || '' const mode = service?.mode || 0 const isRoundTrip = mode === 1 // 根据 Tab 索引强制指定状态文字,忽略后端缺失的 status 字段 let statusText = '接单' if (tabIndex === 0) { statusText = '接单' // 待接送/服务 } else if (tabIndex === 1) { statusText = isRoundTrip ? '出发' : '开始' // 配送/服务中 } else if (tabIndex === 2) { statusText = '已完成' // 已完成 } else if (tabIndex === 3) { statusText = '已拒绝' // 已拒绝 } return { id: order.id, status: order.status, // 保存原始 status 用于判断权限 type: isRoundTrip ? 1 : 2, typeText: serviceText, typeIcon: serviceIcon, statusText: statusText, price: (order.price / 100).toFixed(2), timeLabel: '服务时间', time: order.serviceTime || '', petAvatar: order.petAvatar || '/static/dog.png', petAvatarUrl: order.petAvatarUrl || '', petName: order.petName || '', petBreed: order.breed || '', startLocation: order.fromAddress || '暂无起点', startAddress: order.fromAddress || '', fromAddress: order.fromAddress || '', fromLat: order.fromLat, fromLng: order.fromLng, startDistance: '0km', endLocation: (order.customerName || '') + ' ' + (order.customerPhone || ''), endAddress: order.toAddress || '', toAddress: order.toAddress || '', toLat: order.toLat, toLng: order.toLng, customerPhone: order.customerPhone || '', endDistance: '0km', serviceContent: order.remark || '', remark: order.remark || '' } }, getDisplayStatus(item) { if (item.statusText === '已完成') return '已完成'; if (item.statusText === '已拒绝') return '已拒绝'; if (item.statusText === '接单') { return item.type === 1 ? '待接送' : '待服务'; } return item.type === 1 ? '配送中' : '服务中'; }, getStatusClass(item) { let display = this.getDisplayStatus(item); if (display === '已完成') return 'finish'; if (display === '已拒绝') return 'reject'; if (display === '配送中' || display === '服务中') return 'processing'; return 'highlight'; }, goToDetail(item) { uni.navigateTo({ url: `/pages/orders/detail?id=${item.id}` }); }, showPetProfile(item) { this.currentPetInfo = { ...item, petGender: 'M', petAge: '2岁', petWeight: '15kg', petPersonality: '活泼亲人,精力旺盛', petHobby: '喜欢追飞盘,爱吃肉干', petRemark: '肠胃较弱,不能乱喂零食;出门易爆冲,请拉紧牵引绳。', petTags: ['拉响警报', '不能吃鸡肉', '精力旺盛'], petLogs: [ { date: '2026-02-09 14:00', content: '今天遛弯拉了两次粑粑,精神状态很好。', recorder: '王阿姨' }, { date: '2026-02-08 10:30', content: '有些挑食,剩了小半碗狗粮。', recorder: '李师傅' }, { date: '2026-02-05 09:00', content: '建档。', recorder: '系统记录' } ] }; this.showPetModal = true; }, closePetProfile() { this.showPetModal = false; }, openNavigation(item, pointType) { this.navTargetItem = item; this.navTargetPointType = pointType; this.showNavModal = true; }, closeNavModal() { this.showNavModal = false; }, chooseMap(mapType) { let item = this.navTargetItem; let pointType = this.navTargetPointType; // 起 -> fromAddress ; 终 -> toAddress let name = pointType === 'start' ? (item.fromAddress || '起点') : (item.toAddress || '终点'); let address = pointType === 'start' ? (item.fromAddress || '起点地址') : (item.toAddress || '终点地址'); let latitude = pointType === 'start' ? Number(item.fromLat) : Number(item.toLat); let longitude = pointType === 'start' ? Number(item.fromLng) : Number(item.toLng); this.showNavModal = false; // 统一定义打开地图的函数 const navigateTo = (lat, lng, addrName, addrDesc) => { uni.openLocation({ latitude: lat, longitude: lng, name: addrName, address: addrDesc || '无法获取详细地址', success: function () { console.log('打开导航成功: ' + mapType); }, fail: function (err) { console.error('打开导航失败:', err); uni.showToast({ title: '打开地图失败', icon: 'none' }); } }); }; // 如果有目标经纬度,直接打开 if (latitude && longitude && !isNaN(latitude) && !isNaN(longitude)) { navigateTo(latitude, longitude, name, address); } else { // 如果没有经纬度,按照需求:使用自己当前的经纬度,然后搜索 fromAddress 或者 toAddress uni.showLoading({ title: '获取当前位置...', mask: true }); reportGps(true).then(res => { uni.hideLoading(); // 使用用户当前经纬度作为锚点打开地图,展示目标地址信息 navigateTo(res.latitude, res.longitude, name, address); }).catch(err => { uni.hideLoading(); console.error('获取地理位置失败:', err); // 具体的授权引导已在 reportGps 内部处理 }); } }, toggleCallMenu(item) { if (this.activeCallItem === item) { this.activeCallItem = null; } else { this.activeCallItem = item; } }, closeCallMenu() { this.activeCallItem = null; }, doCall(type, item) { let phoneNum = ''; const targetItem = item || this.activeCallItem; // 1. 获取电话号码 if (type === 'merchant') { phoneNum = '18900008451'; } else if (type === 'customer') { phoneNum = targetItem?.customerPhone; } // 2. 基础校验 if (!phoneNum) { uni.showToast({ title: '未找到电话号码', icon: 'none' }); this.activeCallItem = null; return; } // 3. 清洗号码 (去除空格、横杠等非数字字符) phoneNum = phoneNum.replace(/[^\d]/g, ''); // 二次校验:确保清洗后仍有数字 if (phoneNum.length < 3) { uni.showToast({ title: '电话号码格式错误', icon: 'none' }); this.activeCallItem = null; return; } console.log('正在发起直接呼叫:', phoneNum); // 4. 核心逻辑:区分环境处理 // #ifdef APP-PLUS // App 端:使用 uni.makePhoneCall 直接发起呼叫 uni.makePhoneCall({ phoneNumber: phoneNum, success: () => { console.log('成功唤起系统拨号盘'); }, fail: (err) => { console.error('拨号失败:', err); // 常见错误:Permission denied (权限被拒) 或 Activity not found let msg = '拨号失败'; if (err.message && err.message.includes('permission')) { msg = '请在手机设置中允许"电话"权限'; } uni.showToast({ title: msg, icon: 'none', duration: 3000 }); // 如果失败,尝试引导用户去设置页 (仅限 Android) // #ifdef APP-ANDROID if (err.message && err.message.includes('permission')) { uni.showModal({ title: '权限提示', content: '拨打电话需要电话权限,是否前往设置开启?', success: (res) => { if (res.confirm) { plus.runtime.openURL("app-settings:"); } } }); } // #endif }, complete: () => { this.activeCallItem = null; // 关闭弹窗 } }); // #endif // #ifdef H5 // H5 端:使用 tel: 协议 window.location.href = `tel:${phoneNum}`; this.activeCallItem = null; // #endif // #ifdef MP-WEIXIN // 小程序端:直接调用 makePhoneCall (微信小程序支持直接弹框确认拨打) uni.makePhoneCall({ phoneNumber: phoneNum, fail: () => { uni.showToast({ title: '拨号失败', icon: 'none' }); }, complete: () => { this.activeCallItem = null; } }); // #endif }, reportAbnormal(item) { uni.navigateTo({ url: '/pages/orders/anomaly?orderId=' + (item.id || '') }); }, toggleDropdown(idx) { if (this.activeDropdown === idx) { this.activeDropdown = 0; } else { this.activeDropdown = idx; } }, closeDropdown() { this.activeDropdown = 0; }, selectType(index) { this.currentTypeFilterIdx = index; this.closeDropdown(); }, initCalendar() { let days = []; for (let i = 1; i <= 28; i++) { days.push(i); } this.calendarDays = days; this.selectedDateRange = [2, 4]; }, prevMonth() { uni.showToast({ title: '上个月', icon: 'none' }); }, nextMonth() { uni.showToast({ title: '下个月', icon: 'none' }); }, selectDateItem(day) { if (this.selectedDateRange.length === 2) { this.selectedDateRange = [day]; } else if (this.selectedDateRange.length === 1) { let start = this.selectedDateRange[0]; if (day > start) { this.selectedDateRange = [start, day]; } else if (day < start) { this.selectedDateRange = [day, start]; } else { this.selectedDateRange = []; } } else { this.selectedDateRange = [day]; } }, getDateClass(day) { if (this.selectedDateRange.length === 0) return ''; if (this.selectedDateRange.length === 1) { return day === this.selectedDateRange[0] ? 'is-start' : ''; } let start = this.selectedDateRange[0]; let end = this.selectedDateRange[1]; if (day === start) return 'is-start'; if (day === end) return 'is-end'; if (day > start && day < end) return 'is-between'; return ''; }, resetTimeFilter() { this.hasTimeFilter = false; this.selectedDateRange = []; this.startServiceTime = ''; this.endServiceTime = ''; this.closeDropdown(); this.loadOrders(); }, confirmTimeFilter() { if (this.selectedDateRange.length === 0) { uni.showToast({ title: '请先选择日期', icon: 'none' }); return; } // 构建时间范围参数 const year = this.currentMonth.replace(/[^0-9]/g, '').substring(0, 4); const month = this.currentMonth.replace(/[^0-9]/g, '').substring(4); const pad = (n) => String(n).padStart(2, '0'); if (this.selectedDateRange.length === 2) { this.startServiceTime = `${year}-${pad(month)}-${pad(this.selectedDateRange[0])} 00:00:00`; this.endServiceTime = `${year}-${pad(month)}-${pad(this.selectedDateRange[1])} 23:59:59`; } else { this.startServiceTime = `${year}-${pad(month)}-${pad(this.selectedDateRange[0])} 00:00:00`; this.endServiceTime = `${year}-${pad(month)}-${pad(this.selectedDateRange[0])} 23:59:59`; } this.hasTimeFilter = true; this.closeDropdown(); this.loadOrders(); }, getMainActionText(item) { return '查看详情'; }, mainAction(item) { uni.navigateTo({ url: `/pages/orders/detail?id=${item.id}` }); }, openRemarkInput() { this.remarkText = ''; this.showRemarkInput = true; }, closeRemarkInput() { this.showRemarkInput = false; this.remarkText = ''; }, submitRemark() { const text = this.remarkText.trim(); if (!text) { uni.showToast({ title: '备注内容不能为空', icon: 'none' }); return; } const now = new Date(); const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`; if (!this.currentPetInfo.petLogs) { this.$set(this.currentPetInfo, 'petLogs', []); } this.currentPetInfo.petLogs.unshift({ date: dateStr, content: text, recorder: '我' }); uni.showToast({ title: '备注已添加', icon: 'success' }); this.closeRemarkInput(); }, /** * 取消订单处理逻辑 * @param {Object} item - 订单项 */ handleCancelOrder(item) { uni.showModal({ title: '提示', content: '确认是否取消这个订单?', success: async (res) => { if (res.confirm) { try { uni.showLoading({ title: '取消中...', mask: true }); await cancelOrderApi({ orderId: item.id }); uni.showToast({ title: '订单已取消', icon: 'success' }); // 延时刷新列表,防止提示框闪现 setTimeout(() => { this.loadOrders(); }, 1500); } catch (err) { console.error('取消订单失败:', err); uni.showToast({ title: '取消失败', icon: 'none' }); } finally { uni.hideLoading(); } } } }); } } }