优化室内导览点位选择布局
This commit is contained in:
@@ -18,7 +18,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="picker-search">
|
<view v-if="floorGroups.length" class="picker-search">
|
||||||
<input
|
<input
|
||||||
class="picker-search-input"
|
class="picker-search-input"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -39,7 +39,30 @@
|
|||||||
<view v-else-if="error" class="picker-state">
|
<view v-else-if="error" class="picker-state">
|
||||||
<text class="picker-state-text error">{{ error }}</text>
|
<text class="picker-state-text error">{{ error }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-else-if="filteredOptions.length === 0" class="picker-state">
|
<view v-else-if="floorGroups.length === 0" class="picker-state">
|
||||||
|
<text class="picker-state-text">{{ emptyText }}</text>
|
||||||
|
</view>
|
||||||
|
<view v-else class="picker-body">
|
||||||
|
<scroll-view class="floor-column" scroll-y>
|
||||||
|
<view
|
||||||
|
v-for="floor in floorGroups"
|
||||||
|
:key="floor.floorId"
|
||||||
|
class="picker-floor-option"
|
||||||
|
:class="{ active: activeFloorId === floor.floorId }"
|
||||||
|
@tap="handleFloorSelect(floor.floorId)"
|
||||||
|
>
|
||||||
|
<text class="floor-option-name">{{ floor.floorLabel }}</text>
|
||||||
|
<text class="floor-option-count">{{ floor.options.length }}</text>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<view class="point-column">
|
||||||
|
<view class="point-column-header">
|
||||||
|
<text class="point-column-title">{{ selectedFloorGroup?.floorLabel || '楼层' }}</text>
|
||||||
|
<text class="point-column-count">{{ filteredOptions.length }} 个点位</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="filteredOptions.length === 0" class="picker-state point-empty">
|
||||||
<text class="picker-state-text">{{ emptyText }}</text>
|
<text class="picker-state-text">{{ emptyText }}</text>
|
||||||
</view>
|
</view>
|
||||||
<scroll-view v-else class="picker-list" scroll-y>
|
<scroll-view v-else class="picker-list" scroll-y>
|
||||||
@@ -61,11 +84,14 @@
|
|||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import {
|
import {
|
||||||
|
compareFloorsTopToBottom,
|
||||||
isIndoorNavigableFloor
|
isIndoorNavigableFloor
|
||||||
} from '@/domain/guideFloor'
|
} from '@/domain/guideFloor'
|
||||||
|
|
||||||
@@ -109,12 +135,42 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const keyword = ref(props.initialKeyword)
|
const keyword = ref(props.initialKeyword)
|
||||||
|
const selectedFloorId = ref('')
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(visible) => {
|
(visible) => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
keyword.value = props.initialKeyword
|
keyword.value = props.initialKeyword
|
||||||
|
selectedFloorId.value = selectedOption.value?.floorId || floorGroups.value[0]?.floorId || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.options,
|
||||||
|
() => {
|
||||||
|
if (!floorGroups.value.length) {
|
||||||
|
selectedFloorId.value = ''
|
||||||
|
keyword.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasSelectedFloor = floorGroups.value.some((floor) => floor.floorId === selectedFloorId.value)
|
||||||
|
if (!hasSelectedFloor) {
|
||||||
|
selectedFloorId.value = selectedOption.value?.floorId || floorGroups.value[0]?.floorId || ''
|
||||||
|
keyword.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.selectedPoiId,
|
||||||
|
() => {
|
||||||
|
if (!props.visible) return
|
||||||
|
const floorId = selectedOption.value?.floorId
|
||||||
|
if (floorId) {
|
||||||
|
selectedFloorId.value = floorId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -130,15 +186,55 @@ watch(
|
|||||||
|
|
||||||
const normalizedKeyword = computed(() => keyword.value.trim().toLowerCase())
|
const normalizedKeyword = computed(() => keyword.value.trim().toLowerCase())
|
||||||
|
|
||||||
const filteredOptions = computed(() => {
|
const indoorOptions = computed(() => (
|
||||||
const indoorOptions = props.options.filter((option) => isIndoorNavigableFloor({
|
props.options.filter((option) => isIndoorNavigableFloor({
|
||||||
floorId: option.floorId,
|
floorId: option.floorId,
|
||||||
label: option.floorLabel
|
label: option.floorLabel
|
||||||
}))
|
}))
|
||||||
|
))
|
||||||
|
|
||||||
if (!normalizedKeyword.value) return indoorOptions
|
const selectedOption = computed(() => (
|
||||||
|
indoorOptions.value.find((option) => option.poiId === props.selectedPoiId) || null
|
||||||
|
))
|
||||||
|
|
||||||
return indoorOptions.filter((option) => {
|
const floorGroups = computed(() => {
|
||||||
|
const groups = new Map<string, { floorId: string; floorLabel: string; options: RoutePointOption[] }>()
|
||||||
|
|
||||||
|
indoorOptions.value.forEach((option) => {
|
||||||
|
const floorId = option.floorId
|
||||||
|
const current = groups.get(floorId)
|
||||||
|
if (current) {
|
||||||
|
current.options.push(option)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
groups.set(floorId, {
|
||||||
|
floorId,
|
||||||
|
floorLabel: option.floorLabel || option.floorId,
|
||||||
|
options: [option]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return Array.from(groups.values()).sort((a, b) => compareFloorsTopToBottom(
|
||||||
|
{ floorId: a.floorId, label: a.floorLabel },
|
||||||
|
{ floorId: b.floorId, label: b.floorLabel }
|
||||||
|
))
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectedFloorGroup = computed(() => (
|
||||||
|
floorGroups.value.find((floor) => floor.floorId === selectedFloorId.value)
|
||||||
|
|| floorGroups.value[0]
|
||||||
|
|| null
|
||||||
|
))
|
||||||
|
|
||||||
|
const activeFloorId = computed(() => selectedFloorGroup.value?.floorId || '')
|
||||||
|
|
||||||
|
const filteredOptions = computed(() => {
|
||||||
|
const floorOptions = selectedFloorGroup.value?.options || []
|
||||||
|
|
||||||
|
if (!normalizedKeyword.value) return floorOptions
|
||||||
|
|
||||||
|
return floorOptions.filter((option) => {
|
||||||
const fields = [
|
const fields = [
|
||||||
option.name,
|
option.name,
|
||||||
option.floorId,
|
option.floorId,
|
||||||
@@ -168,6 +264,10 @@ const handleSearchConfirm = () => {
|
|||||||
emit('search', keyword.value)
|
emit('search', keyword.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleFloorSelect = (floorId: string) => {
|
||||||
|
selectedFloorId.value = floorId
|
||||||
|
}
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
emit('close')
|
emit('close')
|
||||||
}
|
}
|
||||||
@@ -269,12 +369,120 @@ const handleSelect = (option: RoutePointOption) => {
|
|||||||
color: #8b8b84;
|
color: #8b8b84;
|
||||||
}
|
}
|
||||||
|
|
||||||
.picker-list {
|
.picker-body {
|
||||||
|
min-height: 260px;
|
||||||
|
height: min(48vh, calc(100vh - env(safe-area-inset-top) - env(safe-area-inset-bottom) - 170px));
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
max-height: min(48vh, calc(100vh - env(safe-area-inset-top) - env(safe-area-inset-bottom) - 170px));
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-list {
|
||||||
|
height: 100%;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.floor-column {
|
||||||
|
flex: 0 0 82px;
|
||||||
|
width: 82px;
|
||||||
|
height: 100%;
|
||||||
|
background: #f6f7f4;
|
||||||
|
border: 1px solid #ecede7;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-column {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-column-header {
|
||||||
|
min-height: 34px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 0 2px 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 1px solid #ecede7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-column-title {
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2329;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-column-count {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
color: #696962;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-floor-option {
|
||||||
|
min-height: 52px;
|
||||||
|
padding: 8px 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 3px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: transparent;
|
||||||
|
border-bottom: 1px solid #ecede7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-floor-option.active {
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: inset 3px 0 0 #1f2329;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floor-option-name {
|
||||||
|
max-width: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2329;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floor-option-count {
|
||||||
|
min-width: 22px;
|
||||||
|
height: 16px;
|
||||||
|
padding: 0 5px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #e8ebe3;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #696962;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-floor-option.active .floor-option-count {
|
||||||
|
background: #1f2329;
|
||||||
|
color: var(--museum-accent);
|
||||||
|
}
|
||||||
|
|
||||||
.picker-option {
|
.picker-option {
|
||||||
min-height: 62px;
|
min-height: 62px;
|
||||||
padding: 12px 10px;
|
padding: 12px 10px;
|
||||||
@@ -343,6 +551,11 @@ const handleSelect = (option: RoutePointOption) => {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.picker-state.point-empty {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.picker-state-text {
|
.picker-state-text {
|
||||||
max-width: 260px;
|
max-width: 260px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|||||||
Reference in New Issue
Block a user