|
- <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>
|