Files
medical-mall/pages/mall/delivery/orders/execute.uvue

258 lines
7.1 KiB
Plaintext

<template>
<ServicePageScaffold title="服务执行" fallback-url="/pages/mall/delivery/orders/detail">
<ServicePanel title="执行计时" subtitle="服务开始后持续计时,提交前可暂存。">
<text class="timer-text">已服务时长:{{ elapsedText }}</text>
<text class="info-text">备注:{{ progressNote }}</text>
</ServicePanel>
<ServicePanel title="服务项目打卡" subtitle="未完成项必须填写原因。">
<view v-for="item in serviceItems" :key="item.id" class="item-card">
<view class="item-header">
<text class="item-title">{{ item.name }}</text>
<switch :checked="item.completed" color="#0f766e" @change="toggleItem(item.id, $event)" />
</view>
<input class="note-input" v-model="item.remark" placeholder="服务备注,例如监测结果、陪护说明" />
<input v-if="item.completed == false" class="note-input" v-model="item.incompleteReason" placeholder="未完成原因,必填" />
</view>
</ServicePanel>
<ServicePanel title="过程留痕" subtitle="服务中可补充备注、证据和异常上报。">
<input class="note-input" v-model="progressNote" placeholder="请输入服务过程备注" />
<input class="note-input" v-model="serviceSummary" placeholder="请输入服务总结,完成提交时会带入" />
<view class="action-row">
<view class="secondary-btn" @click="goEvidence"><text class="btn-text secondary-text">证据上传</text></view>
<view class="secondary-btn" @click="goException"><text class="btn-text secondary-text">异常上报</text></view>
</view>
</ServicePanel>
<view class="bottom-space"></view>
<view class="fixed-bar">
<view class="bar-btn secondary-btn" @click="saveDraft"><text class="btn-text secondary-text">暂存</text></view>
<view class="bar-btn primary-btn" @click="goFinish"><text class="btn-text">完成服务</text></view>
</view>
</ServicePageScaffold>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import { onLoad, onUnload } from '@dcloudio/uni-app'
import ServicePageScaffold from '@/components/homeService/ServicePageScaffold.uvue'
import ServicePanel from '@/components/homeService/ServicePanel.uvue'
import { getDeliveryOrderDetail, saveServiceProgress, startService } from '@/services/deliveryService.uts'
import type { DeliveryOrderType, DeliveryServiceItemType } from '@/types/delivery.uts'
import { requireDeliveryAuth } from '@/utils/deliveryAuth.uts'
import { getDeliveryRouteParam } from '@/utils/deliveryRoute.uts'
const orderId = ref('')
const order = ref<DeliveryOrderType | null>(null)
const serviceItems = ref([] as Array<DeliveryServiceItemType>)
const progressNote = ref('')
const serviceSummary = ref('')
const elapsedText = ref('00:00')
let timerId: number | null = null
function formatElapsed() {
if (order.value == null || order.value.actualStartTime == '') {
elapsedText.value = '00:00'
return
}
const start = new Date(order.value.actualStartTime.replace(' ', 'T')).getTime()
const diffMinutes = Math.floor((Date.now() - start) / 60000)
const hours = Math.floor(diffMinutes / 60)
const minutes = diffMinutes % 60
const hourText = hours < 10 ? '0' + String(hours) : String(hours)
const minuteText = minutes < 10 ? '0' + String(minutes) : String(minutes)
elapsedText.value = hourText + ':' + minuteText
}
function startTimer() {
formatElapsed()
if (timerId != null) {
clearInterval(timerId)
}
timerId = setInterval(() => {
formatElapsed()
}, 60000)
}
function toggleItem(itemId: string, event: any) {
for (let i = 0; i < serviceItems.value.length; i++) {
if (serviceItems.value[i].id == itemId) {
const checked = event.detail.value === true
serviceItems.value[i].completed = checked
serviceItems.value[i].updatedAt = new Date().toISOString().replace('T', ' ').substring(0, 19)
if (checked) {
serviceItems.value[i].incompleteReason = ''
}
}
}
}
async function ensureStarted(orderData: DeliveryOrderType) {
if (orderData.status == 'checked_in' || orderData.status == 'arrived') {
order.value = await startService(orderId.value)
} else {
order.value = orderData
}
if (order.value != null) {
serviceItems.value = order.value.serviceItems
progressNote.value = order.value.progressNote
serviceSummary.value = order.value.serviceSummary
startTimer()
}
}
async function loadData() {
const authResult = await requireDeliveryAuth({ redirectOnFail: true, toastOnFail: true })
if (!authResult.ok || orderId.value == '') {
return
}
const detail = await getDeliveryOrderDetail(orderId.value)
if (detail != null) {
await ensureStarted(detail)
}
}
function validateItems(): boolean {
for (let i = 0; i < serviceItems.value.length; i++) {
const item = serviceItems.value[i]
if (item.completed == false && item.incompleteReason == '') {
uni.showToast({ title: '未完成项目必须填写原因', icon: 'none' })
return false
}
}
return true
}
async function saveDraft() {
if (!validateItems()) {
return
}
await saveServiceProgress(orderId.value, { items: serviceItems.value, progressNote: progressNote.value, serviceSummary: serviceSummary.value })
uni.showToast({ title: '已暂存服务记录', icon: 'success' })
loadData()
}
async function goFinish() {
if (!validateItems()) {
return
}
await saveServiceProgress(orderId.value, { items: serviceItems.value, progressNote: progressNote.value, serviceSummary: serviceSummary.value })
uni.navigateTo({ url: '/pages/mall/delivery/orders/finish?id=' + orderId.value })
}
function goEvidence() {
uni.navigateTo({ url: '/pages/mall/delivery/orders/evidence?id=' + orderId.value })
}
function goException() {
uni.navigateTo({ url: '/pages/mall/delivery/orders/exception?id=' + orderId.value })
}
onLoad((options) => {
if (options != null) {
orderId.value = getDeliveryRouteParam(options as UTSJSONObject, 'id')
}
loadData()
})
onUnload(() => {
if (timerId != null) {
clearInterval(timerId)
}
})
</script>
<style scoped>
.timer-text {
font-size: 38rpx;
font-weight: 700;
color: #0f766e;
}
.info-text {
margin-top: 12rpx;
font-size: 24rpx;
line-height: 36rpx;
color: #5e758c;
}
.item-card {
padding: 20rpx;
margin-bottom: 16rpx;
border-radius: 18rpx;
background: #f8fbfc;
}
.item-header,
.action-row {
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.item-title {
font-size: 28rpx;
font-weight: 700;
color: #16324f;
}
.note-input {
height: 84rpx;
padding: 0 24rpx;
margin-top: 14rpx;
border-radius: 18rpx;
background: #f2f7f6;
font-size: 26rpx;
}
.action-row {
margin-top: 18rpx;
}
.secondary-btn,
.primary-btn,
.bar-btn {
flex: 1;
padding: 20rpx 0;
border-radius: 18rpx;
align-items: center;
justify-content: center;
}
.secondary-btn {
background: #eaf2f0;
margin-right: 12rpx;
}
.primary-btn {
background: #0f766e;
margin-left: 12rpx;
}
.btn-text {
font-size: 28rpx;
font-weight: 700;
color: #ffffff;
}
.secondary-text {
color: #0f766e;
}
.bottom-space {
height: 140rpx;
}
.fixed-bar {
position: fixed;
left: 24rpx;
right: 24rpx;
bottom: 24rpx;
padding: 16rpx;
flex-direction: row;
background: #ffffff;
border-radius: 24rpx;
box-shadow: 0 12rpx 24rpx rgba(15, 118, 110, 0.08);
}
</style>