|
|
@@ -75,7 +75,7 @@
|
|
|
|
|
|
<!-- 订单列表 -->
|
|
|
<view class="order-list">
|
|
|
- <view class="order-card" v-for="(item, index) in orderList" :key="index" :class="{ 'disabled-card': !item.serviceFlag }">
|
|
|
+ <view class="order-card" v-for="(item, index) in orderList" :key="index">
|
|
|
<view class="card-header">
|
|
|
<view class="type-badge">
|
|
|
<image class="type-icon" :src="item.typeIcon"></image>
|
|
|
@@ -149,17 +149,17 @@
|
|
|
</view>
|
|
|
</view><!-- End of card-body -->
|
|
|
|
|
|
- <!-- 按钮组 (重新排版) -->
|
|
|
- <view class="action-btns" v-if="['接单', '到达', '出发', '开始', '送达', '结束'].includes(item.statusText)">
|
|
|
+ <!-- 按钮组 (常驻显示六个按钮) -->
|
|
|
+ <view class="action-btns" v-if="['到达打卡', '到达', '出发', '开始', '送达', '结束'].includes(item.statusText)">
|
|
|
<view class="action-row">
|
|
|
- <button class="btn normal danger" v-if="item.status === 2"
|
|
|
- @click.stop="handleCancelOrder(item)">取消订单</button>
|
|
|
<button class="btn normal" @click.stop="reportAbnormal(item)">异常上报</button>
|
|
|
- <button class="btn normal" @click.stop="addOrUpdateService(item)">增改服务项</button>
|
|
|
+ <button class="btn normal" @click.stop="addOrUpdateService(item)">服务变更</button>
|
|
|
+ <button class="btn normal danger" @click.stop="handleCancelOrder(item)">取消订单</button>
|
|
|
</view>
|
|
|
<view class="action-row">
|
|
|
- <button class="btn normal" @click.stop="doCall('customer', item)">拨号</button>
|
|
|
- <button class="btn primary" @click.stop="mainAction(item)">到达打卡</button>
|
|
|
+ <button class="btn normal" @click.stop="doCall('customer', item)">拨打电话</button>
|
|
|
+ <button class="btn normal" @click.stop="viewAppealProgress(item)">变更进度</button>
|
|
|
+ <button class="btn primary" @click.stop="mainAction(item)">{{ item.statusText }}</button>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
@@ -236,7 +236,7 @@
|
|
|
<text class="pm-log-date">{{ log.date }}</text>
|
|
|
<text class="pm-log-text">{{ log.content }}</text>
|
|
|
<text class="pm-log-recorder">{{ log.recorder === '系统记录' ? '' : '记录人: ' }}{{ log.recorder
|
|
|
- }}</text>
|
|
|
+ }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
<view style="height: 30rpx;"></view>
|
|
|
@@ -284,11 +284,69 @@
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
+
|
|
|
+ <!-- 申诉进度弹窗 -->
|
|
|
+ <view class="modal-mask" v-if="showAppealModal" @click="closeAppealModal">
|
|
|
+ <view class="custom-modal appeal-modal" @click.stop>
|
|
|
+ <view class="appeal-title-bar">
|
|
|
+ <text class="modal-title">变更进度</text>
|
|
|
+ <view class="appeal-close-btn" @click="closeAppealModal">
|
|
|
+ <text class="close-icon">✕</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <scroll-view scroll-y class="appeal-scroll">
|
|
|
+ <view class="appeal-empty" v-if="appealList.length === 0 && !appealLoading">
|
|
|
+ <text>暂无变更记录</text>
|
|
|
+ </view>
|
|
|
+ <view class="appeal-timeline" v-else>
|
|
|
+ <view class="appeal-item" v-for="(record, idx) in appealList" :key="idx">
|
|
|
+ <view class="timeline-dot" :class="getAppealStatusClass(record)"></view>
|
|
|
+ <view class="timeline-line" v-if="idx < appealList.length - 1"></view>
|
|
|
+ <view class="appeal-card">
|
|
|
+ <view class="appeal-header">
|
|
|
+ <text class="appeal-service">{{ record.service || '服务变更' }}</text>
|
|
|
+ <text class="appeal-status" :class="getAppealStatusClass(record)">{{
|
|
|
+ getAppealStatusText(record) }}</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="appeal-row" v-if="record.fulfillerName">
|
|
|
+ <text class="appeal-label">申请人:</text>
|
|
|
+ <text class="appeal-value">{{ record.fulfillerName }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="appeal-row" v-if="record.serviceSpecification">
|
|
|
+ <text class="appeal-label">变更说明:</text>
|
|
|
+ <text class="appeal-value">{{ record.serviceSpecification }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="appeal-row" v-if="record.reason">
|
|
|
+ <text class="appeal-label">申诉理由:</text>
|
|
|
+ <text class="appeal-value">{{ record.reason }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="appeal-row" v-if="record.rejectReason">
|
|
|
+ <text class="appeal-label">驳回理由:</text>
|
|
|
+ <text class="appeal-value reject-reason">{{ record.rejectReason }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="appeal-row" v-if="record.auditorName">
|
|
|
+ <text class="appeal-label">审核人:</text>
|
|
|
+ <text class="appeal-value">{{ record.auditorName }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="appeal-time-row">
|
|
|
+ <text class="appeal-time">提交:{{ formatTime(record.createTime) }}</text>
|
|
|
+ <text class="appeal-time" v-if="record.auditTime">审核:{{ formatTime(record.auditTime)
|
|
|
+ }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
<custom-tabbar currentPath="pages/orders/index"></custom-tabbar>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
import { getMyOrders, cancelOrderApi } from '@/api/order/subOrder'
|
|
|
+import { getAppealListByOrderId } from '@/api/order/subOrderAppeal'
|
|
|
import { listAllService } from '@/api/service/list'
|
|
|
import { reportGps } from '@/utils/gps'
|
|
|
import customTabbar from '@/components/custom-tabbar/index.vue'
|
|
|
@@ -325,7 +383,10 @@ export default {
|
|
|
remarkText: '',
|
|
|
showCancelModal: false,
|
|
|
cancelReason: '',
|
|
|
- currentOrder: null
|
|
|
+ currentOrder: null,
|
|
|
+ showAppealModal: false,
|
|
|
+ appealList: [],
|
|
|
+ appealLoading: false
|
|
|
}
|
|
|
},
|
|
|
created() {
|
|
|
@@ -409,16 +470,14 @@ export default {
|
|
|
const mode = service?.mode || 0
|
|
|
const isRoundTrip = mode === 1
|
|
|
|
|
|
- // 根据 Tab 索引强制指定状态文字,忽略后端缺失的 status 字段
|
|
|
- let statusText = '接单'
|
|
|
- if (tabIndex === 0) {
|
|
|
- statusText = '接单' // 待接送/服务
|
|
|
- } else if (tabIndex === 1) {
|
|
|
- statusText = isRoundTrip ? '出发' : '开始' // 配送/服务中
|
|
|
+ // 待服务或服务中统一展示为“到达打卡”
|
|
|
+ let statusText = ''
|
|
|
+ if (tabIndex === 0 || tabIndex === 1) {
|
|
|
+ statusText = '到达打卡'
|
|
|
} else if (tabIndex === 2) {
|
|
|
- statusText = '已完成' // 已完成
|
|
|
+ statusText = '已完成'
|
|
|
} else if (tabIndex === 3) {
|
|
|
- statusText = '已拒绝' // 已拒绝
|
|
|
+ statusText = '已取消'
|
|
|
}
|
|
|
return {
|
|
|
id: order.id,
|
|
|
@@ -453,17 +512,16 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
getDisplayStatus(item) {
|
|
|
- if (item.statusText === '已完成') return '已完成';
|
|
|
- if (item.statusText === '已拒绝') return '已拒绝';
|
|
|
- if (item.statusText === '接单') {
|
|
|
- return item.type === 1 ? '待接送' : '待服务';
|
|
|
- }
|
|
|
- return item.type === 1 ? '配送中' : '服务中';
|
|
|
+ if (item.status === 4) return '已完成';
|
|
|
+ if (item.status === 5 || item.status === 6) return '已取消';
|
|
|
+ if (item.status === 2) return item.type === 1 ? '待接送' : '待服务';
|
|
|
+ if (item.status === 3) return item.type === 1 ? '配送中' : '服务中';
|
|
|
+ return item.statusText;
|
|
|
},
|
|
|
getStatusClass(item) {
|
|
|
let display = this.getDisplayStatus(item);
|
|
|
if (display === '已完成') return 'finish';
|
|
|
- if (display === '已拒绝') return 'reject';
|
|
|
+ if (display === '已取消') return 'reject';
|
|
|
if (display === '配送中' || display === '服务中') return 'processing';
|
|
|
return 'highlight';
|
|
|
},
|
|
|
@@ -492,6 +550,10 @@ export default {
|
|
|
this.showPetModal = false;
|
|
|
},
|
|
|
openNavigation(item, pointType) {
|
|
|
+ if (!item.serviceFlag) {
|
|
|
+ uni.showToast({ title: '该订单暂未开通服务,无法操作', icon: 'none' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
this.navTargetItem = item;
|
|
|
this.navTargetPointType = pointType;
|
|
|
this.showNavModal = true;
|
|
|
@@ -555,6 +617,10 @@ export default {
|
|
|
this.activeCallItem = null;
|
|
|
},
|
|
|
doCall(type, item) {
|
|
|
+ if (!item.serviceFlag) {
|
|
|
+ uni.showToast({ title: '该订单暂未开通服务,无法操作', icon: 'none' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
let phoneNum = '';
|
|
|
const targetItem = item || this.activeCallItem;
|
|
|
|
|
|
@@ -642,8 +708,23 @@ export default {
|
|
|
// #endif
|
|
|
},
|
|
|
reportAbnormal(item) {
|
|
|
+ if (!item.serviceFlag) {
|
|
|
+ uni.showToast({ title: '该订单暂未开通服务,无法操作', icon: 'none' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
uni.navigateTo({ url: '/pages/orders/anomaly/index?orderId=' + (item.id || '') });
|
|
|
},
|
|
|
+ mainAction(item) {
|
|
|
+ if (!item.serviceFlag) {
|
|
|
+ uni.showToast({ title: '该订单暂未开通服务,无法操作', icon: 'none' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ uni.navigateTo({ url: `/pages/orders/detail/index?id=${item.id}` });
|
|
|
+ },
|
|
|
+ addOrUpdateService(item) {
|
|
|
+ // 跳转到申诉(增改服务项)页面
|
|
|
+ uni.navigateTo({ url: `/pages/orders/appeal/index?id=${item.id}` });
|
|
|
+ },
|
|
|
toggleDropdown(idx) {
|
|
|
if (this.activeDropdown === idx) {
|
|
|
this.activeDropdown = 0;
|
|
|
@@ -788,6 +869,10 @@ export default {
|
|
|
* @param {Object} item - 订单项
|
|
|
*/
|
|
|
handleCancelOrder(item) {
|
|
|
+ if (!item.serviceFlag) {
|
|
|
+ uni.showToast({ title: '该订单暂未开通服务,无法操作', icon: 'none' });
|
|
|
+ return;
|
|
|
+ }
|
|
|
this.currentOrder = item;
|
|
|
this.cancelReason = '';
|
|
|
this.showCancelModal = true;
|
|
|
@@ -820,6 +905,42 @@ export default {
|
|
|
} finally {
|
|
|
uni.hideLoading();
|
|
|
}
|
|
|
+ },
|
|
|
+ async viewAppealProgress(item) {
|
|
|
+ this.appealList = [];
|
|
|
+ this.appealLoading = true;
|
|
|
+ this.showAppealModal = true;
|
|
|
+ try {
|
|
|
+ const res = await getAppealListByOrderId(item.id);
|
|
|
+ this.appealList = res.data || [];
|
|
|
+ } catch (err) {
|
|
|
+ console.error('获取申诉记录失败:', err);
|
|
|
+ uni.showToast({ title: '获取申诉进度失败', icon: 'none' });
|
|
|
+ } finally {
|
|
|
+ this.appealLoading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ closeAppealModal() {
|
|
|
+ this.showAppealModal = false;
|
|
|
+ this.appealList = [];
|
|
|
+ },
|
|
|
+ getAppealStatusText(record) {
|
|
|
+ // auditStatus: 0=待审核, 1=通过, 2=驳回
|
|
|
+ if (record.auditStatus === 0 || record.auditStatus === null || record.auditStatus === undefined) return '待审核';
|
|
|
+ if (record.auditStatus === 1) return '已通过';
|
|
|
+ if (record.auditStatus === 2) return '已驳回';
|
|
|
+ return '未知';
|
|
|
+ },
|
|
|
+ getAppealStatusClass(record) {
|
|
|
+ if (record.auditStatus === 0 || record.auditStatus === null || record.auditStatus === undefined) return 'pending';
|
|
|
+ if (record.auditStatus === 1) return 'approved';
|
|
|
+ if (record.auditStatus === 2) return 'rejected';
|
|
|
+ return '';
|
|
|
+ },
|
|
|
+ formatTime(timeStr) {
|
|
|
+ if (!timeStr) return '';
|
|
|
+ // 后端返回格式: 2026-04-23T15:40:00 或 2026-04-23 15:40:00
|
|
|
+ return timeStr.replace('T', ' ').substring(0, 16);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -1419,18 +1540,21 @@ page {
|
|
|
|
|
|
.action-row {
|
|
|
display: flex;
|
|
|
- justify-content: space-between;
|
|
|
+ gap: 16rpx;
|
|
|
align-items: center;
|
|
|
width: 100%;
|
|
|
}
|
|
|
|
|
|
.btn {
|
|
|
- height: 60rpx;
|
|
|
- line-height: 60rpx;
|
|
|
- border-radius: 30rpx;
|
|
|
- font-size: 26rpx;
|
|
|
- padding: 0 30rpx;
|
|
|
+ flex: 1;
|
|
|
+ height: 64rpx;
|
|
|
+ line-height: 64rpx;
|
|
|
+ border-radius: 32rpx;
|
|
|
+ font-size: 24rpx;
|
|
|
+ padding: 0;
|
|
|
margin: 0;
|
|
|
+ text-align: center;
|
|
|
+ min-width: 0;
|
|
|
}
|
|
|
|
|
|
.action-right .btn:not(:last-child) {
|
|
|
@@ -1995,15 +2119,201 @@ page {
|
|
|
.mt-30 {
|
|
|
margin-top: 30rpx;
|
|
|
}
|
|
|
-.disabled-card {
|
|
|
- opacity: 0.5; /* 降低透明度以示禁用 */
|
|
|
- pointer-events: none; /* 禁用该卡片内背景的一切交互 */
|
|
|
- filter: grayscale(80%); /* 增加灰度,使视觉效果更明显 */
|
|
|
+
|
|
|
+.disabled-card .card-header,
|
|
|
+.disabled-card .card-body {
|
|
|
+ opacity: 0.6;
|
|
|
+ filter: grayscale(80%);
|
|
|
+}
|
|
|
+
|
|
|
+.service-disabled-tip {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 20rpx 0;
|
|
|
+ background-color: #FFF8E1;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ border: 2rpx dashed #FFCC00;
|
|
|
+}
|
|
|
+
|
|
|
+.service-disabled-tip text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #F57C00;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-progress-btn {
|
|
|
+ width: 100% !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 申诉进度弹窗 */
|
|
|
+.appeal-modal {
|
|
|
+ max-height: 80vh;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ position: relative;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-title-bar {
|
|
|
+ width: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ position: relative;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-title-bar .modal-title {
|
|
|
+ margin-bottom: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-close-btn {
|
|
|
+ position: absolute;
|
|
|
+ right: -10rpx;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ width: 56rpx;
|
|
|
+ height: 56rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ border-radius: 50%;
|
|
|
+ background-color: #f5f5f5;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-close-btn:active {
|
|
|
+ background-color: #e8e8e8;
|
|
|
+}
|
|
|
+
|
|
|
+.close-icon {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #999;
|
|
|
+ line-height: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-scroll {
|
|
|
+ max-height: 60vh;
|
|
|
+ padding: 10rpx 0;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-empty {
|
|
|
+ padding: 80rpx 0;
|
|
|
+ text-align: center;
|
|
|
+ color: #999;
|
|
|
+ font-size: 26rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-timeline {
|
|
|
+ padding: 0 10rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-item {
|
|
|
+ position: relative;
|
|
|
+ padding-left: 40rpx;
|
|
|
+ padding-bottom: 30rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-dot {
|
|
|
+ position: absolute;
|
|
|
+ left: 4rpx;
|
|
|
+ top: 12rpx;
|
|
|
+ width: 20rpx;
|
|
|
+ height: 20rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background-color: #e6a23c;
|
|
|
+ border: 4rpx solid #faecd8;
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-dot.approved {
|
|
|
+ background-color: #67c23a;
|
|
|
+ border-color: #e1f3d8;
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-dot.rejected {
|
|
|
+ background-color: #f56c6c;
|
|
|
+ border-color: #fde2e2;
|
|
|
+}
|
|
|
+
|
|
|
+.timeline-line {
|
|
|
+ position: absolute;
|
|
|
+ left: 13rpx;
|
|
|
+ top: 36rpx;
|
|
|
+ bottom: 0;
|
|
|
+ width: 2rpx;
|
|
|
+ background-color: #e4e7ed;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-card {
|
|
|
+ background: #f8f8f8;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ padding: 20rpx 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 12rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-service {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-status {
|
|
|
+ font-size: 22rpx;
|
|
|
+ padding: 4rpx 16rpx;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-status.pending {
|
|
|
+ color: #e6a23c;
|
|
|
+ background-color: #faecd8;
|
|
|
}
|
|
|
|
|
|
-.disabled-card .action-row {
|
|
|
- pointer-events: auto; /* 允许按钮即使在置灰状态下也能点击操作 */
|
|
|
+.appeal-status.approved {
|
|
|
+ color: #67c23a;
|
|
|
+ background-color: #e1f3d8;
|
|
|
}
|
|
|
|
|
|
-/* 即使使用了 pointer-events: none,外层的 @click 也会失效,为了保险我们在 JS 中也做了判断 */
|
|
|
+.appeal-status.rejected {
|
|
|
+ color: #f56c6c;
|
|
|
+ background-color: #fde2e2;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-row {
|
|
|
+ display: flex;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+ font-size: 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-label {
|
|
|
+ color: #999;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-value {
|
|
|
+ color: #333;
|
|
|
+ flex: 1;
|
|
|
+ word-break: break-all;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-value.reject-reason {
|
|
|
+ color: #f56c6c;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-time-row {
|
|
|
+ display: flex;
|
|
|
+ gap: 24rpx;
|
|
|
+ margin-top: 12rpx;
|
|
|
+ padding-top: 12rpx;
|
|
|
+ border-top: 2rpx solid #eee;
|
|
|
+}
|
|
|
+
|
|
|
+.appeal-time {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #bbb;
|
|
|
+}
|
|
|
</style>
|