|
|
|
@@ -0,0 +1,289 @@ |
|
|
|
<template> |
|
|
|
<u-popup :show="visible" mode="bottom" round="16" :closeOnClickOverlay="true" :safeAreaInsetBottom="true" @close="handleClose"> |
|
|
|
<view class="picker-wrap"> |
|
|
|
<view class="picker-header"> |
|
|
|
<text class="picker-title">选择就诊医院</text> |
|
|
|
<u-icon name="close" size="36rpx" color="#999" @click="handleClose" /> |
|
|
|
</view> |
|
|
|
|
|
|
|
<!-- 全量医院搜索 --> |
|
|
|
<view class="search-bar"> |
|
|
|
<u--input v-model="hospitalKeyword" placeholder="搜索医院名称" prefixIcon="search" prefixIconStyle="color: #999" border="surround" shape="circle" clearable /> |
|
|
|
</view> |
|
|
|
|
|
|
|
<!-- Tab 切换:省 → 市 → 区 → 医院 --> |
|
|
|
<view class="tab-bar"> |
|
|
|
<view class="tab-item" :class="{ active: currentTab === 'province' }" @click="switchTab('province')"> |
|
|
|
{{ selectedProvince ? selectedProvince.provinceName : '请选择省份' }} |
|
|
|
</view> |
|
|
|
<view v-if="selectedProvince" class="tab-item" :class="{ active: currentTab === 'city' }" @click="switchTab('city')"> |
|
|
|
{{ selectedCity ? selectedCity.cityName : '请选择城市' }} |
|
|
|
</view> |
|
|
|
<view v-if="selectedCity" class="tab-item" :class="{ active: currentTab === 'district' }" @click="switchTab('district')"> |
|
|
|
{{ selectedDistrict ? selectedDistrict.districtName : '请选择区县' }} |
|
|
|
</view> |
|
|
|
<view v-if="selectedDistrict" class="tab-item" :class="{ active: currentTab === 'hospital' }" @click="switchTab('hospital')"> |
|
|
|
{{ selectedHospital ? selectedHospital.name : '请选择医院' }} |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
|
|
|
|
<!-- 列表区域 --> |
|
|
|
<scroll-view class="list-wrap" scroll-y> |
|
|
|
<!-- 全量搜索结果 --> |
|
|
|
<template v-if="hasSearchKeyword"> |
|
|
|
<view v-for="item in globalFilteredHospitals" :key="item.id" class="list-item" |
|
|
|
:class="{ selected: selectedHospital && selectedHospital.id === item.id }" @click="onSelectHospital(item)"> |
|
|
|
<view class="hospital-result"> |
|
|
|
<text class="hospital-name">{{ item.name }}</text> |
|
|
|
<text class="hospital-region">{{ item.regionText || '地区未设置' }}</text> |
|
|
|
</view> |
|
|
|
<u-icon v-if="selectedHospital && selectedHospital.id === item.id" name="checkmark" color="#FF7700" size="28rpx" /> |
|
|
|
</view> |
|
|
|
</template> |
|
|
|
<!-- 省份 --> |
|
|
|
<template v-else-if="currentTab === 'province'"> |
|
|
|
<view v-for="item in provinces" :key="item.provinceCode || item.provinceName" class="list-item" |
|
|
|
:class="{ selected: selectedProvince === item }" @click="onSelectProvince(item)"> |
|
|
|
<text>{{ item.provinceName }}</text> |
|
|
|
<u-icon v-if="selectedProvince === item" name="checkmark" color="#FF7700" size="28rpx" /> |
|
|
|
</view> |
|
|
|
</template> |
|
|
|
<!-- 城市 --> |
|
|
|
<template v-if="currentTab === 'city'"> |
|
|
|
<view v-for="item in cities" :key="item.cityCode || item.cityName" class="list-item" |
|
|
|
:class="{ selected: selectedCity === item }" @click="onSelectCity(item)"> |
|
|
|
<text>{{ item.cityName }}</text> |
|
|
|
<u-icon v-if="selectedCity === item" name="checkmark" color="#FF7700" size="28rpx" /> |
|
|
|
</view> |
|
|
|
</template> |
|
|
|
<!-- 区县 --> |
|
|
|
<template v-if="currentTab === 'district'"> |
|
|
|
<view v-for="item in districts" :key="item.districtCode || item.districtName" class="list-item" |
|
|
|
:class="{ selected: selectedDistrict === item }" @click="onSelectDistrict(item)"> |
|
|
|
<text>{{ item.districtName }}</text> |
|
|
|
<u-icon v-if="selectedDistrict === item" name="checkmark" color="#FF7700" size="28rpx" /> |
|
|
|
</view> |
|
|
|
</template> |
|
|
|
<!-- 医院 --> |
|
|
|
<template v-if="currentTab === 'hospital'"> |
|
|
|
<view v-for="item in filteredHospitals" :key="item.id" class="list-item" |
|
|
|
:class="{ selected: selectedHospital && selectedHospital.id === item.id }" @click="onSelectHospital(item)"> |
|
|
|
<text>{{ item.name }}</text> |
|
|
|
<u-icon v-if="selectedHospital && selectedHospital.id === item.id" name="checkmark" color="#FF7700" size="28rpx" /> |
|
|
|
</view> |
|
|
|
</template> |
|
|
|
<u-empty v-if="currentEmpty" mode="data" text="暂无数据" marginTop="60" /> |
|
|
|
</scroll-view> |
|
|
|
</view> |
|
|
|
</u-popup> |
|
|
|
</template> |
|
|
|
|
|
|
|
<script setup> |
|
|
|
import { ref, computed } from 'vue' |
|
|
|
|
|
|
|
const props = defineProps({ |
|
|
|
hospitalData: { type: Array, default: () => [] } |
|
|
|
}) |
|
|
|
const emit = defineEmits(['confirm']) |
|
|
|
|
|
|
|
const visible = ref(false) |
|
|
|
const currentTab = ref('province') |
|
|
|
const selectedProvince = ref(null) |
|
|
|
const selectedCity = ref(null) |
|
|
|
const selectedDistrict = ref(null) |
|
|
|
const selectedHospital = ref(null) |
|
|
|
const hospitalKeyword = ref('') |
|
|
|
|
|
|
|
const provinces = computed(() => props.hospitalData || []) |
|
|
|
const cities = computed(() => (selectedProvince.value && selectedProvince.value.cities) || []) |
|
|
|
const districts = computed(() => (selectedCity.value && selectedCity.value.districts) || []) |
|
|
|
const hospitals = computed(() => (selectedDistrict.value && selectedDistrict.value.hospitals) || []) |
|
|
|
|
|
|
|
const hasSearchKeyword = computed(() => !!hospitalKeyword.value.trim()) |
|
|
|
const globalHospitals = computed(() => { |
|
|
|
const list = [] |
|
|
|
provinces.value.forEach(prov => { |
|
|
|
;(prov.cities || []).forEach(city => { |
|
|
|
;(city.districts || []).forEach(dist => { |
|
|
|
;(dist.hospitals || []).forEach(h => { |
|
|
|
list.push({ |
|
|
|
...h, |
|
|
|
_province: prov, |
|
|
|
_city: city, |
|
|
|
_district: dist, |
|
|
|
regionText: [prov.provinceName, city.cityName, dist.districtName].filter(Boolean).join(' ') |
|
|
|
}) |
|
|
|
}) |
|
|
|
}) |
|
|
|
}) |
|
|
|
}) |
|
|
|
return list |
|
|
|
}) |
|
|
|
const globalFilteredHospitals = computed(() => { |
|
|
|
const kw = hospitalKeyword.value.trim() |
|
|
|
if (!kw) return [] |
|
|
|
return globalHospitals.value.filter(h => h.name.includes(kw) || (h.regionText && h.regionText.includes(kw))) |
|
|
|
}) |
|
|
|
|
|
|
|
const filteredHospitals = computed(() => { |
|
|
|
return hospitals.value |
|
|
|
}) |
|
|
|
|
|
|
|
const currentEmpty = computed(() => { |
|
|
|
if (hasSearchKeyword.value) return globalFilteredHospitals.value.length === 0 |
|
|
|
if (currentTab.value === 'province') return provinces.value.length === 0 |
|
|
|
if (currentTab.value === 'city') return cities.value.length === 0 |
|
|
|
if (currentTab.value === 'district') return districts.value.length === 0 |
|
|
|
return filteredHospitals.value.length === 0 |
|
|
|
}) |
|
|
|
|
|
|
|
const switchTab = (tab) => { currentTab.value = tab } |
|
|
|
|
|
|
|
const onSelectProvince = (item) => { |
|
|
|
selectedProvince.value = item |
|
|
|
selectedCity.value = null |
|
|
|
selectedDistrict.value = null |
|
|
|
selectedHospital.value = null |
|
|
|
if (item.cities && item.cities.length === 1) { |
|
|
|
selectedCity.value = item.cities[0] |
|
|
|
if (selectedCity.value.districts && selectedCity.value.districts.length === 1) { |
|
|
|
selectedDistrict.value = selectedCity.value.districts[0] |
|
|
|
currentTab.value = 'hospital' |
|
|
|
} else { |
|
|
|
currentTab.value = 'district' |
|
|
|
} |
|
|
|
} else { |
|
|
|
currentTab.value = 'city' |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const onSelectCity = (item) => { |
|
|
|
selectedCity.value = item |
|
|
|
selectedDistrict.value = null |
|
|
|
selectedHospital.value = null |
|
|
|
if (item.districts && item.districts.length === 1) { |
|
|
|
selectedDistrict.value = item.districts[0] |
|
|
|
currentTab.value = 'hospital' |
|
|
|
} else { |
|
|
|
currentTab.value = 'district' |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const onSelectDistrict = (item) => { |
|
|
|
selectedDistrict.value = item |
|
|
|
selectedHospital.value = null |
|
|
|
hospitalKeyword.value = '' |
|
|
|
currentTab.value = 'hospital' |
|
|
|
} |
|
|
|
|
|
|
|
const onSelectHospital = (item) => { |
|
|
|
if (item._province) selectedProvince.value = item._province |
|
|
|
if (item._city) selectedCity.value = item._city |
|
|
|
if (item._district) selectedDistrict.value = item._district |
|
|
|
selectedHospital.value = item |
|
|
|
emitData() |
|
|
|
handleClose() |
|
|
|
} |
|
|
|
|
|
|
|
const emitData = () => { |
|
|
|
if (!selectedHospital.value) return |
|
|
|
emit('confirm', { |
|
|
|
hospitalId: selectedHospital.value.id, |
|
|
|
hospitalName: selectedHospital.value.name, |
|
|
|
province_code: selectedHospital.value.province_code || (selectedProvince.value && selectedProvince.value.provinceCode) || '', |
|
|
|
city_code: selectedHospital.value.city_code || (selectedCity.value && selectedCity.value.cityCode) || '', |
|
|
|
district_code: selectedHospital.value.district_code || (selectedDistrict.value && selectedDistrict.value.districtCode) || '' |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
const handleClose = () => { |
|
|
|
visible.value = false |
|
|
|
} |
|
|
|
|
|
|
|
const open = (initial = {}) => { |
|
|
|
selectedProvince.value = null |
|
|
|
selectedCity.value = null |
|
|
|
selectedDistrict.value = null |
|
|
|
selectedHospital.value = null |
|
|
|
hospitalKeyword.value = '' |
|
|
|
currentTab.value = 'province' |
|
|
|
|
|
|
|
const provinceCode = initial.province_code || '' |
|
|
|
const cityCode = initial.city_code || '' |
|
|
|
const districtCode = initial.district_code || '' |
|
|
|
const hospitalName = initial.hospitalName || initial.hospital || '' |
|
|
|
const hospitalId = initial.hospitalId || '' |
|
|
|
|
|
|
|
if (provinceCode) { |
|
|
|
selectedProvince.value = provinces.value.find(p => p.provinceCode === provinceCode) || null |
|
|
|
if (selectedProvince.value && cityCode) { |
|
|
|
selectedCity.value = (selectedProvince.value.cities || []).find(c => c.cityCode === cityCode) || null |
|
|
|
if (selectedCity.value && districtCode) { |
|
|
|
selectedDistrict.value = (selectedCity.value.districts || []).find(d => d.districtCode === districtCode) || null |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (!selectedDistrict.value && (hospitalName || hospitalId)) { |
|
|
|
findHospitalPath(hospitalName, hospitalId) |
|
|
|
} |
|
|
|
|
|
|
|
if (selectedDistrict.value) { |
|
|
|
selectedHospital.value = (selectedDistrict.value.hospitals || []).find(h => { |
|
|
|
return (hospitalId && h.id === hospitalId) || (hospitalName && h.name === hospitalName) |
|
|
|
}) || null |
|
|
|
currentTab.value = 'hospital' |
|
|
|
} else if (selectedCity.value) { |
|
|
|
currentTab.value = 'district' |
|
|
|
} else if (selectedProvince.value) { |
|
|
|
currentTab.value = 'city' |
|
|
|
} |
|
|
|
visible.value = true |
|
|
|
} |
|
|
|
|
|
|
|
const findHospitalPath = (hospitalName, hospitalId) => { |
|
|
|
for (const prov of provinces.value) { |
|
|
|
for (const city of (prov.cities || [])) { |
|
|
|
for (const dist of (city.districts || [])) { |
|
|
|
const hospital = (dist.hospitals || []).find(h => { |
|
|
|
return (hospitalId && h.id === hospitalId) || (hospitalName && h.name === hospitalName) |
|
|
|
}) |
|
|
|
if (hospital) { |
|
|
|
selectedProvince.value = prov |
|
|
|
selectedCity.value = city |
|
|
|
selectedDistrict.value = dist |
|
|
|
selectedHospital.value = hospital |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
defineExpose({ open }) |
|
|
|
</script> |
|
|
|
|
|
|
|
<style lang="scss" scoped> |
|
|
|
.picker-wrap { height: 70vh; display: flex; flex-direction: column; } |
|
|
|
.picker-header { display: flex; align-items: center; justify-content: space-between; padding: 28rpx 32rpx; border-bottom: 1rpx solid #f0f0f0; } |
|
|
|
.picker-title { font-size: 32rpx; font-weight: 600; color: #333; } |
|
|
|
.tab-bar { display: flex; padding: 20rpx 32rpx; gap: 24rpx; border-bottom: 1rpx solid #f0f0f0; flex-wrap: wrap; } |
|
|
|
.tab-item { |
|
|
|
font-size: 26rpx; color: #999; padding-bottom: 12rpx; position: relative; |
|
|
|
max-width: 240rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; |
|
|
|
&.active { |
|
|
|
color: #FF7700; font-weight: 500; |
|
|
|
&::after { content: ''; position: absolute; bottom: 0; left: 0; right: 0; height: 4rpx; background: #FF7700; border-radius: 2rpx; } |
|
|
|
} |
|
|
|
} |
|
|
|
.search-bar { padding: 16rpx 32rpx; border-bottom: 1rpx solid #f0f0f0; } |
|
|
|
.list-wrap { flex: 1; overflow: hidden; } |
|
|
|
.list-item { |
|
|
|
display: flex; align-items: center; justify-content: space-between; |
|
|
|
padding: 28rpx 32rpx; font-size: 28rpx; color: #333; border-bottom: 1rpx solid #f8f8f8; |
|
|
|
&.selected { color: #FF7700; background: #FFF8F0; } |
|
|
|
} |
|
|
|
.hospital-result { display: flex; flex-direction: column; gap: 8rpx; min-width: 0; } |
|
|
|
.hospital-name { font-size: 28rpx; color: #333; } |
|
|
|
.hospital-region { font-size: 22rpx; color: #999; } |
|
|
|
</style> |