2025-09-29 10:26:26 +08:00

641 lines
21 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page">
<view class="card">
<view class="title">担架运送代申请</view>
<!-- 代申报科室 -->
<view class="form-item">
<text class="label required">代申报科室</text>
<picker mode="multiSelector" :range="needDeptMultiRange"
:value="needDeptMultiValue" @columnchange="onNeedDeptColumnChange"
@change="onNeedDeptChange">
<view class="picker-value"
:class="{ placeholder: !form.needcontactDept }">
{{ currentNeedDeptName || '请选择代申报科室' }}
</view>
</picker>
</view>
<!-- 代申报人 -->
<view class="form-item">
<text class="label required">代申报人</text>
<input class="input" type="text" v-model="form.needcontact"
placeholder="请输入代申报人" />
</view>
<!-- 代申报电话 -->
<view class="form-item">
<text class="label required">代申报电话</text>
<input class="input" type="number" maxlength="20"
v-model="form.needcontactTel" placeholder="请输入代申报电话" />
</view>
<!-- 申报科室 -->
<view class="form-item">
<text class="label required">申报科室</text>
<picker mode="multiSelector" :range="decDeptMultiRange"
:value="decDeptMultiValue" @columnchange="onDecDeptColumnChange"
@change="onDecDeptChange">
<view class="picker-value" :class="{ placeholder: !form.decDept }">
{{ currentDecDeptName || '请选择所在科室' }}
</view>
</picker>
</view>
<!-- 申报床号 -->
<view class="form-item">
<text class="label required">申报床号</text>
<input class="input" type="text" v-model="form.decBedNo"
placeholder="请输入床号" />
</view>
<!-- 申报手机 -->
<view class="form-item">
<text class="label required">申报手机</text>
<input class="input" type="number" maxlength="11" v-model="form.decTel"
placeholder="请输入手机号码" />
</view>
<!-- 运送工具 -->
<view class="form-item">
<text class="label required">运送工具</text>
<picker mode="selector" :range="toolNames" @change="onPickTool">
<view class="picker-value"
:class="{ placeholder: !form.carryingtools }">
{{ currentToolName || '请选择所需运送工具' }}
</view>
</picker>
</view>
<!-- 工具数量 -->
<view class="form-item">
<text class="label required">工具数量</text>
<input class="input" type="number"
v-model.number="form.carryingtoolsCount" placeholder="请输入工具数量" />
</view>
<!-- 承运人数量 -->
<view class="form-item">
<text class="label required">承运人数量</text>
<input class="input" type="number" v-model.number="form.outnumber"
placeholder="请输入所需运送人员数量" />
</view>
<!-- 是否预约 -->
<view class="form-item">
<text class="label">是否预约</text>
<switch :checked="form.isOrdered" @change="onToggleOrdered" />
</view>
<!-- 预约时间 -->
<view v-if="form.isOrdered" class="form-item">
<text class="label">预约时间</text>
<picker mode="time" :value="form.orderedDatetime || ''"
@change="onPickTime">
<view class="picker-value"
:class="{ placeholder: !form.orderedDatetime }">
{{ form.orderedDatetime || '请选择' }}
</view>
</picker>
</view>
<!-- 支付方式保留但界面可隐藏按需要开启-->
<view class="form-item" v-if="showPayment">
<text class="label required">支付方式</text>
<picker mode="selector" :range="payNames" @change="onPickPay">
<view class="picker-value"
:class="{ placeholder: !form.paymentMethod }">
{{ currentPayName || '请选择支付方式' }}
</view>
</picker>
</view>
<view class="btns">
<button class="btn outline" :disabled="!isCancelSubmit"
@tap="onCancelAgent">取消申请</button>
<button class="btn primary" :disabled="!isSubmit"
@tap="onSubmit">申请</button>
</view>
</view>
<!-- 等待接单浮层 -->
<view v-if="isShowProgress" class="overlay">
<view class="overlay-card">
<text class="overlay-title">等待骑手接单</text>
<text class="overlay-time">{{ countdownText }}</text>
<button class="btn outline" @tap="onCancelAgent">取消申请</button>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'
// @ts-ignore
import util from '@/utils/util.js'
// @ts-ignore
import queryService from '@/service/queryService.js'
// @ts-ignore
import getService from '@/service/getService.js'
// @ts-ignore
import config from '@/config.js'
const showPayment = ref(true) // 如不需要可置为 false
const form = reactive({
needcontactDept: null as any,
needcontactDepts: null as any,
needcontact: null as any,
needcontactTel: null as any,
decChannel: null as any,
decDept: null as any,
decDepts: null as any,
decBedNo: null as any,
decTel: null as any,
carryingtools: null as any,
carryingtoolsCount: 1,
outnumber: 1,
openid: null as any,
isOrdered: false,
orderedDatetime: null as any,
paymentMethod: '01'
})
const formData = reactive({
decDeptDatas: [] as any[],
needcontactDeptDatas: [] as any[],
carryingtoolsDatas: [] as any[],
paymentMethodDatas: [] as any[]
})
// UI & 轮询
const isSubmit = ref(true)
const isCancelSubmit = ref(false)
const isShowProgress = ref(false)
const list = ref<any[] | null>(null)
const COUNTDOWN_MAX = 5 * 60 * 1000
const countdown = ref(COUNTDOWN_MAX)
let pollTimer: any = null
let cdTimer: any = null
// 代申报科室(两列)
const needDeptParents = ref<any[]>([])
const needDeptChildrenMap = ref<Record<string, any[]>>({})
const needDeptMultiRange = ref<string[][]>([[], []])
const needDeptMultiValue = ref<number[]>([0, 0])
// 申报科室(两列)
const decDeptParents = ref<any[]>([])
const decDeptChildrenMap = ref<Record<string, any[]>>({})
const decDeptMultiRange = ref<string[][]>([[], []])
const decDeptMultiValue = ref<number[]>([0, 0])
const toolNames = computed(() => formData.carryingtoolsDatas.map(d => d.name || d.label || ''))
const payNames = computed(() => formData.paymentMethodDatas.map(d => d.name || d.label || ''))
const currentNeedDeptName = computed(() => {
if (!form.needcontactDept) return ''
for (const p of needDeptParents.value) {
const children = needDeptChildrenMap.value[p.id] || []
const c = children.find((it: any) => it.id === form.needcontactDept)
if (c) return c.gridFullname || c.gridName || c.name || c.label || ''
}
const p = needDeptParents.value.find((it: any) => it.id === form.needcontactDept)
return p ? (p.gridFullname || p.gridName || p.name || p.label || '') : ''
})
const currentDecDeptName = computed(() => {
if (!form.decDept) return ''
for (const p of decDeptParents.value) {
const children = decDeptChildrenMap.value[p.id] || []
const c = children.find((it: any) => it.id === form.decDept)
if (c) return c.gridFullname || c.gridName || c.name || c.label || ''
}
const p = decDeptParents.value.find((it: any) => it.id === form.decDept)
return p ? (p.gridFullname || p.gridName || p.name || p.label || '') : ''
})
const currentToolName = computed(() => {
const found = formData.carryingtoolsDatas.find(d => d.id === form.carryingtools)
return found ? (found.name || found.label) : ''
})
const currentPayName = computed(() => {
const found = formData.paymentMethodDatas.find(d => d.id === form.paymentMethod)
return found ? (found.name || found.label) : ''
})
const countdownText = computed(() => {
const total = Math.max(0, countdown.value)
const h = Math.floor(total / 3600000)
const m = Math.floor((total % 3600000) / 60000)
const s = Math.floor((total % 60000) / 1000)
const pad = (n: number) => String(n).padStart(2, '0')
return `${pad(h)}:${pad(m)}:${pad(s)}`
})
function onNeedDeptColumnChange(e: any) {
const { column, value } = e.detail
if (column === 0) {
needDeptMultiValue.value[0] = value
const parent = needDeptParents.value[value]
const children = (parent && needDeptChildrenMap.value[parent.id]) || []
needDeptMultiRange.value[1] = children.length > 0
? children.map((c: any) => c.gridFullname || c.gridName || c.name || c.label || '')
: ['- 无子项 -']
needDeptMultiValue.value[1] = 0
}
}
function onNeedDeptChange(e: any) {
const [pi, ci] = e.detail.value as number[]
const parent = needDeptParents.value[pi]
const children = (parent && needDeptChildrenMap.value[parent.id]) || []
if (children.length > 0) {
const child = children[ci]
if (!child) return
form.needcontactDept = child.id
form.needcontactDepts = ['', child.id]
} else {
if (!parent) return
form.needcontactDept = parent.id
form.needcontactDepts = ['', parent.id]
}
}
function onDecDeptColumnChange(e: any) {
const { column, value } = e.detail
if (column === 0) {
decDeptMultiValue.value[0] = value
const parent = decDeptParents.value[value]
const children = (parent && decDeptChildrenMap.value[parent.id]) || []
decDeptMultiRange.value[1] = children.length > 0
? children.map((c: any) => c.gridFullname || c.gridName || c.name || c.label || '')
: ['- 无子项 -']
decDeptMultiValue.value[1] = 0
}
}
function onDecDeptChange(e: any) {
const [pi, ci] = e.detail.value as number[]
const parent = decDeptParents.value[pi]
const children = (parent && decDeptChildrenMap.value[parent.id]) || []
if (children.length > 0) {
const child = children[ci]
if (!child) return
form.decDept = child.id
form.decDepts = ['', child.id]
} else {
if (!parent) return
form.decDept = parent.id
form.decDepts = ['', parent.id]
}
}
function onPickTool(e: any) {
const idx = Number(e.detail.value)
const item = formData.carryingtoolsDatas[idx]
if (!item) return
form.carryingtools = item.id
}
function onPickPay(e: any) {
const idx = Number(e.detail.value)
const item = formData.paymentMethodDatas[idx]
if (!item) return
form.paymentMethod = item.id
}
function onPickTime(e: any) {
form.orderedDatetime = e.detail.value
}
function onToggleOrdered(e: any) {
form.isOrdered = !!e.detail.value
}
function validate(): boolean {
if (!form.needcontact) { uni.showToast({ title: '代申报人不能为空', icon: 'none' }); return false }
if (!form.needcontactTel) { uni.showToast({ title: '代申报电话不能为空', icon: 'none' }); return false }
if (!form.needcontactDept && (!form.needcontactDepts || (Array.isArray(form.needcontactDepts) && form.needcontactDepts.length <= 1))) {
uni.showToast({ title: '代申报科室不能为空', icon: 'none' }); return false
}
if (!form.decDept && (!form.decDepts || (Array.isArray(form.decDepts) && form.decDepts.length <= 1))) {
uni.showToast({ title: '申报科室不能为空', icon: 'none' }); return false
}
if (!form.decBedNo) { uni.showToast({ title: '床号不能为空', icon: 'none' }); return false }
if (!form.decTel) { uni.showToast({ title: '申报电话不能为空', icon: 'none' }); return false }
if (!form.carryingtools) { uni.showToast({ title: '运送工具不能为空', icon: 'none' }); return false }
if (!form.carryingtoolsCount) { uni.showToast({ title: '工具数量不能为空', icon: 'none' }); return false }
if (!form.outnumber) { uni.showToast({ title: '所需运送人员数量不能为空', icon: 'none' }); return false }
if (showPayment.value && !form.paymentMethod) { uni.showToast({ title: '支付方式不能为空', icon: 'none' }); return false }
if (form.isOrdered && !form.orderedDatetime) { uni.showToast({ title: '请选择预约时间', icon: 'none' }); return false }
return true
}
async function onSubmit() {
if (!validate()) return
if (form.decDepts && Array.isArray(form.decDepts) && form.decDepts.length > 1) form.decDept = form.decDepts[1]
if (form.needcontactDepts && Array.isArray(form.needcontactDepts) && form.needcontactDepts.length > 1) form.needcontactDept = form.needcontactDepts[1]
const openid = uni.getStorageSync('openid')
form.openid = openid ? openid : config.defaultOpenId
const payload: any = { ...form }
if (form.isOrdered) payload.orderedDatetime = toFullDateTime(form.orderedDatetime as any)
else payload.orderedDatetime = null
try {
const res = await util.request({ url: '/API/stretchertransport/create', method: 'POST', data: payload })
if (res.data && res.data.code === 0) {
isSubmit.value = false
isCancelSubmit.value = true
isShowProgress.value = true
startPoll()
startCountdown()
uni.showToast({ title: '担架运送申请成功,等待骑手接单!', icon: 'none' })
} else {
throw new Error('create failed')
}
} catch (err) {
uni.showToast({ title: '担架运送申请失败,请重新申请!', icon: 'none' })
isShowProgress.value = false
isSubmit.value = true
isCancelSubmit.value = false
}
}
async function onCancel() {
try {
const res = await util.request({ url: `/API/stretchertransport/redis/delete/${form.decTel}`, method: 'GET' })
if (res.data && res.data.code === 0) {
isSubmit.value = true
isCancelSubmit.value = false
isShowProgress.value = false
stopPoll()
stopCountdown()
uni.showToast({ title: '担架运送申请取消成功!', icon: 'none' })
} else {
throw new Error('cancel failed')
}
} catch (err) {
uni.showToast({ title: '担架运送申请取消失败!', icon: 'none' })
}
}
async function onCancelAgent() {
const res = await uni.showModal({ title: '确认取消申请提示', content: '您确定要取消本次申请吗?' })
const result: any = Array.isArray(res) ? res[1] : (res as any)
if (result && result.confirm) onCancel()
}
function startPoll() {
if (pollTimer) return
pollTimer = setInterval(async () => {
const data = { decDept: form.decDept, decBedNo: form.decBedNo, decTel: form.decTel }
try {
const res = await queryService.queryStretcherReceiptTask(data)
if (res.data && res.data.code === 0) {
list.value = res.data.data
if (list.value && list.value.length > 0) {
isShowProgress.value = false
isCancelSubmit.value = false
isSubmit.value = true
stopPoll(); stopCountdown(); resetForm()
uni.showToast({ title: '申请已被骑手接单!', icon: 'success', duration: 3000 })
}
}
} catch { }
}, 1000)
}
function stopPoll() { if (pollTimer) clearInterval(pollTimer); pollTimer = null }
function startCountdown() {
countdown.value = COUNTDOWN_MAX
if (cdTimer) clearInterval(cdTimer)
cdTimer = setInterval(() => {
countdown.value -= 1000
if (countdown.value <= 0) onCountdownEnd()
}, 1000)
}
function stopCountdown() { if (cdTimer) clearInterval(cdTimer); cdTimer = null }
function onCountdownEnd() {
stopPoll(); stopCountdown()
uni.showToast({ title: '申请未被接单,请重新申请!', icon: 'success', duration: 4000 })
isShowProgress.value = false
isCancelSubmit.value = false
isSubmit.value = true
}
function resetForm() {
form.needcontactDept = null
form.needcontactDepts = null
form.needcontact = null
form.needcontactTel = null
form.decDept = null
form.decDepts = null
form.decBedNo = null
form.decTel = null
form.carryingtools = null
form.carryingtoolsCount = 1
form.outnumber = 1
form.isOrdered = false
form.orderedDatetime = null
}
async function getCarryToolsType() {
try {
const res = await queryService.getDataDictionary('db_tasksheetcarryingtools_type')
if (res.data && res.data.code === 0) {
formData.carryingtoolsDatas = res.data.data.dictionaryList || []
if (formData.carryingtoolsDatas.length > 0) form.carryingtools = formData.carryingtoolsDatas[0].id
} else uni.showToast({ title: '获取运送工具类别失败!', icon: 'none' })
} catch { uni.showToast({ title: '获取运送工具类别失败!', icon: 'none' }) }
}
async function getPaymentMethodType() {
try {
const res = await queryService.getDataDictionary('db_payment_mode_type')
if (res.data && res.data.code === 0) {
formData.paymentMethodDatas = res.data.data.dictionaryList || []
if (formData.paymentMethodDatas.length > 0) form.paymentMethod = formData.paymentMethodDatas[0].id
} else uni.showToast({ title: '获取支付方式列表失败!', icon: 'none' })
} catch { uni.showToast({ title: '获取支付方式列表失败!', icon: 'none' }) }
}
async function getGrids() {
try {
const res = await queryService.getGrids()
if (res.data && res.data.code === 0) {
const list = res.data.data || []
formData.decDeptDatas = list
formData.needcontactDeptDatas = list
// 代申报科室
needDeptParents.value = list
needDeptChildrenMap.value = {}
needDeptParents.value.forEach((p: any) => { needDeptChildrenMap.value[p.id] = Array.isArray(p.children) ? p.children : [] })
needDeptMultiRange.value[0] = needDeptParents.value.map((p: any) => p.gridFullname || p.gridName || p.name || p.label || '')
const ndFirstParent = needDeptParents.value[0]
const ndFirstChildren = ndFirstParent ? (needDeptChildrenMap.value[ndFirstParent.id] || []) : []
needDeptMultiRange.value[1] = ndFirstChildren.length > 0 ? ndFirstChildren.map((c: any) => c.gridFullname || c.gridName || c.name || c.label || '') : ['无子项']
needDeptMultiValue.value = [0, 0]
if (ndFirstParent) {
if (ndFirstChildren.length > 0) { form.needcontactDept = ndFirstChildren[0].id; form.needcontactDepts = ['', ndFirstChildren[0].id] }
else { form.needcontactDept = ndFirstParent.id; form.needcontactDepts = ['', ndFirstParent.id] }
}
// 申报科室
decDeptParents.value = list
decDeptChildrenMap.value = {}
decDeptParents.value.forEach((p: any) => { decDeptChildrenMap.value[p.id] = Array.isArray(p.children) ? p.children : [] })
decDeptMultiRange.value[0] = decDeptParents.value.map((p: any) => p.gridFullname || p.gridName || p.name || p.label || '')
const dFirstParent = decDeptParents.value[0]
const dFirstChildren = dFirstParent ? (decDeptChildrenMap.value[dFirstParent.id] || []) : []
decDeptMultiRange.value[1] = dFirstChildren.length > 0 ? dFirstChildren.map((c: any) => c.gridFullname || c.gridName || c.name || c.label || '') : ['无子项']
decDeptMultiValue.value = [0, 0]
if (dFirstParent) {
if (dFirstChildren.length > 0) { form.decDept = dFirstChildren[0].id; form.decDepts = ['', dFirstChildren[0].id] }
else { form.decDept = dFirstParent.id; form.decDepts = ['', dFirstParent.id] }
}
} else uni.showToast({ title: '获取网格列表失败!', icon: 'none' })
} catch { uni.showToast({ title: '获取网格列表失败!', icon: 'none' }) }
}
onMounted(async () => {
// #ifdef MP-WEIXIN
const openid = uni.getStorageSync('openid')
if (!openid) { try { getService.getOpenId && getService.getOpenId() } catch { } }
// #endif
await Promise.all([getCarryToolsType(), getPaymentMethodType(), getGrids()])
})
onUnmounted(() => { stopPoll(); stopCountdown() })
function toFullDateTime(timeStr: string): string {
const now = new Date()
const yyyy = now.getFullYear()
const MM = String(now.getMonth() + 1).padStart(2, '0')
const dd = String(now.getDate()).padStart(2, '0')
if (!timeStr) return `${yyyy}-${MM}-${dd} 00:00:00`
const parts = String(timeStr).split(':')
const h = String(parts[0] ?? '00').padStart(2, '0')
const m = String(parts[1] ?? '00').padStart(2, '0')
const s = String(parts[2] ?? '00').padStart(2, '0')
return `${yyyy}-${MM}-${dd} ${h}:${m}:${s}`
}
</script>
<style scoped>
.page {
min-height: 100vh;
background: #f5f7fb;
}
.card {
margin: 24rpx;
padding: 24rpx;
background: #fff;
border-radius: 16rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
}
.title {
font-size: 34rpx;
font-weight: 600;
margin-bottom: 12rpx;
color: #111;
}
.form-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.label {
color: #333;
font-size: 28rpx;
}
.required::before {
content: '*';
color: #ff4d4f;
margin-right: 8rpx;
}
.input {
text-align: right;
flex: 1;
margin-left: 24rpx;
font-size: 28rpx;
color: #333;
}
.picker-value {
flex: 1;
text-align: right;
margin-left: 24rpx;
color: #333;
font-size: 28rpx;
}
.picker-value.placeholder {
color: #999;
}
.btns {
display: flex;
gap: 20rpx;
margin-top: 24rpx;
}
.btn {
flex: 1;
padding: 0 20rpx;
border-radius: 12rpx;
font-size: 30rpx;
}
.btn.primary {
background: #007aff;
color: #fff;
}
.btn.outline {
background: #fff;
color: #007aff;
border: 2rpx solid #007aff;
}
.overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.35);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.overlay-card {
width: 80%;
background: #fff;
border-radius: 16rpx;
padding: 32rpx;
text-align: center;
}
.overlay-title {
display: block;
font-size: 30rpx;
color: #333;
margin-bottom: 12rpx;
}
.overlay-time {
display: block;
font-size: 40rpx;
color: #007aff;
margin-bottom: 16rpx;
font-weight: 600;
}
</style>