<template>
|
<view class="page">
|
<view class="custom-nav" :class="isSolidNav ? 'is-solid' : ''"><view class="custom-nav-bg"></view><view class="safe-space" :style="'height:' + navTop + 'px'"></view><view class="nav-row" :style="'height:' + navHeight + 'px;padding-right:' + navRight + 'px'"><view class="back-button" @tap="goBack"><image src="/static/food-assets/arrow-left-white.svg"></image></view><text class="nav-title">{{packageName}}</text><view class="capsule-space" :style="'width:' + navWidth + 'px'"></view></view></view>
|
<scroll-view :show-scrollbar="false" class="content" scroll-y @scroll="handleScroll"><view class="hero"><image class="hero-image" :src="heroImage" mode="aspectFill" @error="replaceHero"></image><view class="hero-shade"></view></view><view class="package-panel">
|
<view v-if="detailLoading" class="detail-state"><view class="loading-spinner"></view><text>正在加载体检套餐</text></view>
|
<view v-else-if="detailError" class="detail-state error-state"><text>体检套餐加载失败</text><text class="retry-text" @tap="loadDetail">重新加载</text></view>
|
<view v-else>
|
<view class="package-info-card"><view class="package-title-row"><text class="package-title">{{packageName}}</text><text class="free-tag">{{freeLabel}}</text></view><view v-if="applicableCards != ''" class="service-tags"><text>{{applicableCards}}</text></view><text class="hospital-name">{{hospitalName}}</text><view class="address-row"><image class="address-icon" src="/static/benefits-assets/map-pin.svg"></image><text class="address-text">{{hospitalAddress}}</text><view class="address-actions"><view :class="canNavigate ? 'round-action' : 'round-action is-disabled'" @tap="openMap"><image src="/static/merchant-assets/navigation.svg"></image></view><view :class="hospitalPhone != '' ? 'round-action' : 'round-action is-disabled'" @tap="callHospital"><image src="/static/merchant-assets/phone.svg"></image></view></view></view><text v-if="!packageAvailable" class="unavailable-text">{{unavailableReason}}</text></view>
|
<view class="tabs"><view class="tab-item active"><text>详情</text><view class="tab-line"></view></view></view>
|
<view v-if="itemsLoading" class="list-state"><view class="loading-spinner"></view><text>正在加载检查项目</text></view><view v-else-if="itemsError" class="list-state error-state"><text>检查项目加载失败</text><text class="retry-text" @tap="loadItems">重新加载</text></view><view v-else-if="itemsLoaded && packageItems.length == 0" class="list-state"><text>暂无体检检查项目</text></view>
|
<view v-if="itemsLoaded && packageItems.length > 0" class="check-table"><view class="check-head"><view class="check-cell cell-item"><text>检查项目</text></view><view class="check-cell cell-meaning"><text>项目意义</text></view><view class="check-cell cell-gender"><text>男</text></view><view class="check-cell cell-gender cell-gender-end"><text>女</text></view></view><view class="check-row" v-for="item in packageItems" :key="item.id"><view class="check-cell cell-item"><text>{{item.name}}</text></view><view class="check-cell cell-meaning"><text>{{item.meaning}}</text></view><view class="check-cell cell-gender"><text>{{item.male ? '√' : ''}}</text></view><view class="check-cell cell-gender cell-gender-end"><text>{{item.female ? '√' : ''}}</text></view></view></view>
|
<view v-if="detailImages.length > 0" class="detail-images"><image v-for="(imageUrl, index) in detailImages" :key="imageUrl + index" class="detail-image" :src="imageUrl" mode="widthFix" @error="dropDetailImage(index)"></image></view>
|
</view><view class="content-bottom"></view>
|
</view></scroll-view>
|
<view class="action-bar"><view :class="canBook ? 'booking-button' : 'booking-button booking-disabled'" hover-class="booking-button-active" @tap="goBooking"><text>{{packageAvailable ? '预约' : '暂不可预约'}}</text></view></view>
|
</view>
|
</template>
|
|
<script setup lang="uts">
|
import { getMedicalPackage, getMedicalPackageItems } from '../../services/portal.uts'
|
import { resolveApiUrl } from '../../services/http.uts'
|
import { safeBack } from '../../utils/navigation.uts'
|
const navTop = ref(28); const navHeight = ref(32); const navWidth = ref(88); const navRight = ref(10); const isSolidNav = ref(false)
|
const packageId = ref(''); const hospitalId = ref(''); const routeApplicableCards = ref(''); const detailLoading = ref(true); const detailError = ref(false); const itemsLoading = ref(false); const itemsLoaded = ref(false); const itemsError = ref(false)
|
const packageName = ref(''); const heroImage = ref(''); const freeLabel = ref('免费'); const applicableCards = ref(''); const packageAvailable = ref(false); const unavailableReason = ref('该套餐暂不可预约')
|
const hospitalName = ref(''); const hospitalAddress = ref(''); const hospitalPhone = ref(''); const latitude = ref(0); const longitude = ref(0); const packageItems = ref<any[]>([])
|
// 商户在「图文详情」上传的图片(可能多张),按顺序铺在检查项目表格下面。
|
const detailImages = ref<string[]>([])
|
const canNavigate = computed(() => latitude.value > 0 && longitude.value > 0)
|
const canBook = computed(() => !detailLoading.value && !detailError.value && packageAvailable.value)
|
const decodeRouteText = (value: any): string => {
|
const text = value == null ? '' : String(value)
|
try { return decodeURIComponent(text) } catch (error) { return text }
|
}
|
onLoad((options: any) => {
|
// #ifdef MP-WEIXIN
|
const capsule = uni.getMenuButtonBoundingClientRect(); const info = uni.getSystemInfoSync(); navTop.value = capsule.top; navHeight.value = capsule.height; navWidth.value = capsule.width; navRight.value = info.windowWidth - capsule.right
|
// #endif
|
packageId.value = options != null && options['packageId'] != null ? options['packageId'] as string : ''; hospitalId.value = options != null && options['hospitalId'] != null ? options['hospitalId'] as string : ''; routeApplicableCards.value = options != null ? decodeRouteText(options['applicableCards']) : ''; applicableCards.value = routeApplicableCards.value; loadDetail()
|
})
|
const loadDetail = () => {
|
if (packageId.value == '') { detailLoading.value = false; detailError.value = true; return }
|
detailLoading.value = true; detailError.value = false
|
getMedicalPackage(packageId.value, (data: any) => { const hospital: any = data.hospital || {}; packageName.value = data.name || ''; heroImage.value = data.heroImage != null && data.heroImage != '' ? resolveApiUrl(data.heroImage as string) : ''; freeLabel.value = data.freeLabel || '免费'; const apiApplicableCards = data.applicableCards != null ? String(data.applicableCards).trim() : ''; applicableCards.value = apiApplicableCards != '' ? apiApplicableCards : routeApplicableCards.value; packageAvailable.value = data.available === true; unavailableReason.value = data.unavailableReason || unavailableReason.value; hospitalId.value = String(hospital.hospitalId || hospitalId.value); hospitalName.value = hospital.name || ''; hospitalAddress.value = hospital.address || '医院暂未配置地址'; hospitalPhone.value = hospital.mobile || ''; latitude.value = Number(hospital.latitude); longitude.value = Number(hospital.longitude); const images: string[] = []; const sourceImages: any[] = data.detailImages instanceof Array ? data.detailImages as any[] : []; for (let index = 0; index < sourceImages.length; index++) { const value = sourceImages[index] == null ? '' : ('' + sourceImages[index]).trim(); if (value.length > 0) images.push(resolveApiUrl(value)) }; detailImages.value = images; detailLoading.value = false; loadItems() }, () => { detailLoading.value = false; detailError.value = true })
|
}
|
const loadItems = () => { if (packageId.value == '') return; itemsLoading.value = true; itemsLoaded.value = false; itemsError.value = false; getMedicalPackageItems(packageId.value, (data: any) => { packageItems.value = data instanceof Array ? data as any[] : []; itemsLoading.value = false; itemsLoaded.value = true }, () => { itemsLoading.value = false; itemsError.value = true }) }
|
const goBack = () => safeBack('/pages/premium-benefits/premium-benefits')
|
const handleScroll = (event: any) => { const show = event.detail.scrollTop > 64; if (isSolidNav.value != show) isSolidNav.value = show }
|
const replaceHero = () => { heroImage.value = '' }
|
const dropDetailImage = (index: number) => { const next = detailImages.value.slice(); next.splice(index, 1); detailImages.value = next }
|
const openMap = () => { if (!canNavigate.value) { uni.showToast({ title: '医院暂未配置位置', icon: 'none' }); return }; uni.openLocation({ latitude: latitude.value, longitude: longitude.value, name: hospitalName.value, address: hospitalAddress.value, scale: 18, fail: () => uni.showToast({ title: '暂时无法打开地图导航', icon: 'none' }) }) }
|
const callHospital = () => { if (hospitalPhone.value == '') { uni.showToast({ title: '医院暂未配置联系电话', icon: 'none' }); return }; uni.makePhoneCall({ phoneNumber: hospitalPhone.value, fail: () => uni.showToast({ title: '暂时无法拨打电话', icon: 'none' }) }) }
|
const goBooking = () => { if (!canBook.value) { if (!detailLoading.value && !detailError.value) uni.showToast({ title: unavailableReason.value, icon: 'none' }); return }; uni.navigateTo({ url: '/pages/medical-booking/medical-booking?packageId=' + encodeURIComponent(packageId.value) + '&hospitalId=' + encodeURIComponent(hospitalId.value) + '&package=' + encodeURIComponent(packageName.value) + '&hospital=' + encodeURIComponent(hospitalName.value) }) }
|
</script>
|
|
<style>
|
.page { width:100%; height:100vh; background-color:#F6F6F6; }.content { height:calc(100% - 150rpx); height:calc(100% - 150rpx - env(safe-area-inset-bottom)); background-color:#F6F6F6; }.hero { height:445rpx; position:relative; overflow:hidden; background-color:#3B3028; }.hero-image { position:absolute; left:0; top:0; width:100%; height:445rpx; }.hero-shade { position:absolute; left:0; right:0; top:0; height:190rpx; background-image:linear-gradient(180deg,rgba(0,0,0,.58),rgba(0,0,0,0)); }.custom-nav { position:fixed; z-index:20; left:0; right:0; top:0; padding-bottom:20rpx; }.custom-nav-bg { position:absolute; z-index:0; left:0; right:0; top:0; bottom:0; opacity:0; background-image:linear-gradient(180deg,#F26A38,#EE542B); transition:opacity 280ms ease-out; }.custom-nav.is-solid .custom-nav-bg { opacity:1; }.safe-space { height:28px; position:relative; z-index:2; }.nav-row { display:flex; box-sizing:border-box; padding-left:24rpx; position:relative; z-index:2; flex-direction:row; align-items:center; justify-content:space-between; }.back-button { display:flex; width:54rpx; height:54rpx; border-radius:50%; background-color:rgba(45,45,45,.52); justify-content:center; align-items:center; }.custom-nav.is-solid .back-button { background-color:rgba(45,45,45,0); }.back-button image { width:34rpx; height:34rpx; }.nav-title { position:absolute; left:105rpx; right:105rpx; opacity:0; color:#FFF; font-size:29rpx; font-weight:600; text-align:center; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }.custom-nav.is-solid .nav-title { opacity:1; }.capsule-space { height:100%; flex-shrink:0; }.package-panel { position:relative; z-index:3; min-height:600rpx; margin-top:-22rpx; border-radius:27rpx 27rpx 0 0; overflow:hidden; background-color:#F6F6F6; }.package-info-card { padding:25rpx 38rpx 20rpx; }.package-title-row { display:flex; min-height:50rpx; align-items:center; justify-content:space-between; }.package-title { min-width:0; flex:1; color:#292929; font-size:35rpx; font-weight:700; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }.free-tag { margin-left:15rpx; color:#FF7C2B; font-size:26rpx; }.service-tags { display:flex; margin-top:16rpx; }.service-tags text { padding:6rpx 11rpx; border:1rpx solid #FF9B70; border-radius:5rpx; color:#EF7046; font-size:24rpx; }.hospital-name { display:block; margin-top:20rpx; color:#292929; font-size:30rpx; font-weight:700; }.address-row { display:flex; min-height:72rpx; margin-top:12rpx; align-items:flex-start; }.address-icon { width:27rpx; height:27rpx; margin-top:4rpx; margin-right:8rpx; flex-shrink:0; }.address-text { width:420rpx; min-width:0; color:#7B8491; font-size:24rpx; line-height:34rpx; }.address-actions { display:flex; margin-left:auto; flex-shrink:0; }.round-action { display:flex; width:52rpx; height:52rpx; border-radius:50%; background-color:#F2F3F5; justify-content:center; align-items:center; }.round-action + .round-action { margin-left:30rpx; }.round-action image { width:27rpx; height:27rpx; }.is-disabled { opacity:.42; }.unavailable-text { display:block; margin-top:10rpx; color:#D96B3B; font-size:23rpx; }.tabs { display:flex; height:85rpx; padding:0 38rpx; align-items:center; }.tab-item { display:flex; height:85rpx; position:relative; color:#303030; font-size:32rpx; font-weight:600; align-items:center; }.tab-line { position:absolute; left:50%; bottom:0; width:59rpx; height:7rpx; margin-left:-30rpx; border-radius:4rpx; background-image:linear-gradient(90deg,#FF4D36,#FFBF3A); }.detail-state,.list-state { display:flex; height:330rpx; margin:16rpx 25rpx 0; color:#969CA5; font-size:24rpx; align-items:center; justify-content:center; }.detail-state { margin-top:0; }.error-state { flex-direction:column; }.retry-text { margin-top:20rpx; color:#F26A38; }.loading-spinner { width:28rpx; height:28rpx; margin-right:13rpx; box-sizing:border-box; border:3rpx solid #FFD6C4; border-top-color:#FF6C2B; border-radius:50%; animation:medical-loading 720ms linear infinite; } @keyframes medical-loading { from { transform:rotate(0deg); } to { transform:rotate(360deg); } }.check-table { margin:16rpx 25rpx 0; border:1rpx solid #E3E6EB; border-radius:8rpx; overflow:hidden; background-color:#FFF; }.check-head,.check-row { display:flex; }.check-head { background-color:#F5F6F8; }.check-row { border-top:1rpx solid #E3E6EB; }.check-cell { display:flex; box-sizing:border-box; padding:18rpx 14rpx; border-left:1rpx solid #E3E6EB; align-items:center; justify-content:center; }.cell-item { width:180rpx; border-left:0; flex-shrink:0; }.cell-meaning { min-width:0; flex:1; justify-content:flex-start; }.cell-gender { width:84rpx; flex-shrink:0; }.check-head .check-cell text { color:#30343A; font-size:24rpx; font-weight:700; }.check-cell text { color:#4A5159; font-size:22rpx; line-height:32rpx; }.cell-gender text { color:#30343A; font-size:26rpx; }.content-bottom { height:35rpx; }.action-bar { display:flex; height:150rpx; height:calc(150rpx + env(safe-area-inset-bottom)); box-sizing:border-box; padding:20rpx 34rpx 2rpx; padding-bottom:calc(2rpx + env(safe-area-inset-bottom)); background:#FFF; box-shadow:0 -5rpx 15rpx rgba(0,0,0,.06); align-items:flex-end; }.booking-button { display:flex; width:100%; height:90rpx; border-radius:46rpx; background-image:linear-gradient(90deg,#FF4D32,#FF8A20); justify-content:center; align-items:center; }.booking-button text { color:#FFF; font-size:31rpx; }.booking-disabled { background-image:none; background-color:#C6CBD2; }.booking-button-active { opacity:.82; transform:scale(.99); }
|
.package-title-row,
|
.service-tags,
|
.address-row,
|
.address-actions,
|
.tabs,
|
.tab-item,
|
.detail-state,
|
.list-state,
|
.check-head,
|
.check-row {
|
display: flex;
|
flex-direction: row;
|
}
|
.service-tags {
|
align-items: flex-start;
|
}
|
.service-tags text {
|
flex-shrink: 0;
|
white-space: nowrap;
|
}
|
.error-state {
|
flex-direction: column;
|
}
|
.check-head,
|
.check-row {
|
width: 100%;
|
align-items: stretch;
|
}
|
.check-cell {
|
display: flex;
|
flex-direction: row;
|
}
|
/* 商户在「图文详情」上传的多张图片:整组铺在检查项目表格下面,按原图比例完整显示。 */
|
.detail-images {
|
margin: 16rpx 25rpx 0;
|
}
|
.detail-image {
|
display: block;
|
width: 100%;
|
margin-bottom: 16rpx;
|
border-radius: 8rpx;
|
background-color: #F2F4F7;
|
}
|
</style>
|