优化点位搜索快捷入口
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
lyf
2026-07-13 23:12:37 +08:00
parent 93645a2640
commit 350491157a
4 changed files with 239 additions and 245 deletions

View File

@@ -47,16 +47,17 @@
<view v-if="variant === 'home' && !showSearchContent && !isHomeCategoryMode" class="home-category-strip">
<view
v-for="item in homeCategories"
v-for="item in collapsedHomeCategories"
:key="item.id"
class="home-category-chip"
:class="{ active: activeCategoryId === item.id }"
:data-testid="`poi-category-${item.id}`"
@tap="handleFacilityShortcut(item)"
>
<view class="home-category-icon line-icon" :class="`icon-${item.icon}`">
<view class="icon-part a"></view>
<view class="icon-part b"></view>
<view class="icon-part c"></view>
<view class="home-category-icon">
<svg class="poi-category-icon" viewBox="0 0 32 32" aria-hidden="true">
<use :href="getPoiCategoryIconHref(item.id)" />
</svg>
</view>
<text class="home-category-label">{{ item.label }}</text>
</view>
@@ -130,7 +131,7 @@
</view>
<view class="category-scroll">
<view class="category-grid">
<view class="category-grid" :class="{ 'home-shortcut-grid': variant === 'home' }">
<view
v-for="item in searchCategoryColumns"
:key="item.id"
@@ -139,10 +140,10 @@
:data-testid="`poi-category-${item.id}`"
@tap="handleFacilityShortcut(item)"
>
<view class="line-icon" :class="`icon-${item.icon}`">
<view class="icon-part a"></view>
<view class="icon-part b"></view>
<view class="icon-part c"></view>
<view class="category-icon-shell">
<svg class="poi-category-icon" viewBox="0 0 32 32" aria-hidden="true">
<use :href="getPoiCategoryIconHref(item.id)" />
</svg>
</view>
<text class="category-label">{{ item.label }}</text>
</view>
@@ -230,6 +231,7 @@ import {
HOME_POI_CATEGORIES,
POI_CATEGORIES,
findPoiCategoryByKeyword,
getPoiCategoryIconHref,
getPoiCategoryById,
getPoiDataIssues,
inspectPoiCollection,
@@ -273,8 +275,11 @@ type PreparedPoiResults = {
}
const homeCategories = HOME_POI_CATEGORIES
const collapsedHomeCategories = computed(() => homeCategories.slice(0, 5))
const searchCategories = POI_CATEGORIES
const searchCategoryColumns = searchCategories
const searchCategoryColumns = computed(() => (
props.variant === 'home' ? homeCategories : searchCategories
))
const searchKeyword = ref('')
const searchDraftKeyword = ref('')
@@ -1083,89 +1088,76 @@ defineExpose({
.home-category-strip {
margin-top: 10px;
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 6px;
}
.variant-home.is-collapsed .home-category-strip {
margin-top: 7px;
gap: 5px;
gap: 6px;
}
.home-category-chip {
min-width: 0;
height: 60px;
min-height: 68px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 5px;
padding: 6px 2px;
gap: 4px;
padding: 6px 2px 5px;
box-sizing: border-box;
background: #fffef2;
border: 1px solid #ecebd2;
background: #f5f5ed;
border: 1px solid #dfe2d7;
border-radius: 8px;
box-shadow: none;
box-shadow: 0 2px 5px rgba(21, 23, 19, 0.1);
transition: background-color 0.18s ease, border-color 0.18s ease, transform 0.14s ease;
}
.variant-home.is-collapsed .home-category-chip {
min-width: 0;
height: 56px;
gap: 4px;
padding: 5px 1px;
border-radius: 7px;
min-height: 68px;
}
.home-category-chip:active {
transform: translateY(1px);
border-color: #d8d900;
background: #eef06d;
border-color: #c8ca00;
}
.home-category-chip.active {
background: #f2f48d;
border-color: #bfc100;
}
.home-category-icon {
width: 24px;
height: 23px;
flex: 0 0 24px;
width: 28px;
height: 28px;
flex: 0 0 28px;
}
.poi-category-icon {
display: block;
width: 100%;
height: 100%;
color: #151713;
pointer-events: none;
}
.home-category-icon.line-icon {
width: 24px;
height: 23px;
}
.home-category-icon .icon-part {
transform: scale(0.8);
transform-origin: center;
}
.variant-home.is-collapsed .home-category-icon,
.variant-home.is-collapsed .home-category-icon.line-icon {
width: 22px;
height: 21px;
flex-basis: 22px;
}
.variant-home.is-collapsed .home-category-icon .icon-part {
transform: scale(0.74);
.home-category-chip.active .poi-category-icon,
.category-item.active .poi-category-icon {
color: #151713;
}
.home-category-label {
min-width: 0;
max-width: 100%;
font-size: 12px;
line-height: 16px;
font-size: 11px;
line-height: 15px;
font-weight: 700;
color: #151713;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.variant-home.is-collapsed .home-category-label {
max-width: 100%;
font-size: 11px;
line-height: 14px;
}
.home-category-results {
@@ -1436,6 +1428,14 @@ defineExpose({
touch-action: pan-x;
}
.variant-home .category-scroll {
width: calc(100% - 8px);
margin: 0 4px;
overflow: visible;
padding-bottom: 0;
touch-action: pan-y;
}
.category-scroll::-webkit-scrollbar {
display: none;
}
@@ -1455,22 +1455,24 @@ defineExpose({
min-width: 0;
min-height: 70px;
position: relative;
padding: 9px 6px 10px;
padding: 9px 4px 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 7px;
box-sizing: border-box;
overflow: hidden;
background: #fffef2;
border: 1px solid #ecebd2;
overflow: visible;
background: #f5f5ed;
border: 1px solid #dfe2d7;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(21, 23, 19, 0.08);
transition: background-color 0.18s ease, border-color 0.18s ease, transform 0.14s ease;
}
.category-item.active {
background: #fffddc;
border-color: #d8d900;
background: #f2f48d;
border-color: #bfc100;
}
.home-category-chip.disabled,
@@ -1482,170 +1484,52 @@ defineExpose({
.category-item.active::after {
content: '';
position: absolute;
left: 14px;
right: 14px;
bottom: 0;
height: 4px;
background: var(--museum-accent);
}
.line-icon {
width: 26px;
height: 24px;
position: relative;
color: #151713;
}
.category-item.active .line-icon {
color: #151713;
}
.icon-part {
position: absolute;
box-sizing: border-box;
}
.icon-service .icon-part.a,
.icon-exhibition .icon-part.a,
.icon-cinema .icon-part.a,
.icon-ticket .icon-part.a,
.icon-drink .icon-part.a,
.icon-bag .icon-part.a {
left: 4px;
right: 4px;
top: 5px;
bottom: 5px;
border: 2px solid currentColor;
border-radius: 3px;
}
.icon-service .icon-part.b,
.icon-exhibition .icon-part.b,
.icon-cinema .icon-part.b,
.icon-ticket .icon-part.b,
.icon-drink .icon-part.b,
.icon-bag .icon-part.b {
left: 9px;
right: 9px;
top: 1px;
height: 5px;
border: 2px solid currentColor;
border-bottom: 0;
border-radius: 6px 6px 0 0;
}
.icon-elevator .icon-part.a,
.icon-escalator .icon-part.a,
.icon-restroom .icon-part.a,
.icon-nursing .icon-part.a,
.icon-restaurant .icon-part.a,
.icon-stairs .icon-part.a,
.icon-entrance .icon-part.a {
left: 4px;
right: 4px;
top: 3px;
bottom: 3px;
border: 2px solid currentColor;
border-radius: 4px;
}
.icon-elevator .icon-part.b {
left: 9px;
top: 7px;
width: 8px;
height: 10px;
border-left: 2px solid currentColor;
border-right: 2px solid currentColor;
}
.icon-escalator .icon-part.b,
.icon-entrance .icon-part.b {
left: 7px;
top: 12px;
width: 13px;
height: 2px;
background: currentColor;
transform: rotate(-22deg);
}
.icon-restroom .icon-part.b,
.icon-nursing .icon-part.b {
left: 8px;
top: 8px;
width: 4px;
height: 4px;
background: currentColor;
border-radius: 50%;
box-shadow: 8px 0 0 currentColor;
}
.icon-exhibition .icon-part.c {
left: 6px;
right: 6px;
bottom: 2px;
height: 2px;
background: currentColor;
border-radius: 2px;
}
.icon-cinema .icon-part.b {
left: 2px;
right: 2px;
top: 4px;
height: 4px;
border: 0;
border-top: 2px solid currentColor;
border-bottom: 2px solid currentColor;
border-radius: 0;
}
.icon-restaurant .icon-part.b {
left: 7px;
top: 6px;
width: 2px;
height: 12px;
background: currentColor;
box-shadow: 5px 0 0 currentColor, 10px 0 0 currentColor;
}
.icon-restaurant .icon-part.c {
right: 3px;
top: 5px;
width: 7px;
height: 15px;
border-left: 2px solid currentColor;
border-bottom: 2px solid currentColor;
border-radius: 0 0 0 5px;
}
.icon-stairs .icon-part.b {
left: 6px;
top: 7px;
width: 14px;
height: 12px;
border-left: 2px solid currentColor;
border-bottom: 2px solid currentColor;
}
.icon-stairs .icon-part.c {
left: 10px;
top: 11px;
width: 10px;
height: 8px;
border-left: 2px solid currentColor;
border-bottom: 2px solid currentColor;
right: 10px;
bottom: -1px;
height: 3px;
background: var(--museum-accent);
border-radius: 3px 3px 0 0;
}
.category-icon-shell {
width: 28px;
height: 28px;
flex: 0 0 28px;
}
.category-item:active {
transform: translateY(1px);
background: #eef06d;
border-color: #c8ca00;
}
.variant-home .home-shortcut-grid {
display: grid;
grid-auto-flow: row;
grid-template-columns: repeat(5, minmax(0, 1fr));
grid-template-rows: repeat(2, 72px);
grid-auto-columns: auto;
width: 100%;
min-width: 0;
gap: 6px;
}
.variant-home .category-item {
min-height: 72px;
height: 72px;
padding: 6px 2px 5px;
gap: 4px;
}
.category-label {
max-width: 100%;
font-size: 12px;
line-height: 16px;
font-size: 11px;
line-height: 15px;
font-weight: 700;
color: #151713;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.category-item.active .category-label {

View File

@@ -4,6 +4,7 @@ import type {
} from '@/domain/museum'
export type PoiCategoryId =
| 'itinerary'
| 'exhibition-hall'
| 'cinema'
| 'ticket-office'
@@ -25,25 +26,37 @@ export interface PoiCategoryDefinition {
keywords: readonly string[]
categoryIds: readonly string[]
iconTypes: readonly string[]
excludedCategoryIds?: readonly string[]
}
const definitions: PoiCategoryDefinition[] = [
{
id: 'exhibition-hall',
label: '展厅',
icon: 'exhibition',
id: 'itinerary',
label: '行程',
icon: 'itinerary',
order: 1,
homeVisible: true,
keywords: ['展厅', '览', '展馆', 'exhibition_hall', 'touring_poi', 'hall'],
categoryIds: ['exhibition_hall', 'exhibition_hall_entrance', 'touring_poi'],
iconTypes: ['exhibition_hall', 'hall_entrance', 'exhibition']
keywords: ['行程', '览', '参观', 'touring_poi'],
categoryIds: ['touring_poi'],
iconTypes: ['touring', 'guide']
},
{
id: 'exhibition-hall',
label: '展览',
icon: 'exhibition',
order: 2,
homeVisible: true,
keywords: ['展厅', '展览', '展馆', 'exhibition_hall', 'hall'],
categoryIds: ['exhibition_hall', 'exhibition_hall_entrance'],
iconTypes: ['exhibition_hall', 'hall_entrance', 'exhibition'],
excludedCategoryIds: ['touring_poi']
},
{
id: 'cinema',
label: '影院',
icon: 'cinema',
order: 2,
homeVisible: false,
order: 3,
homeVisible: true,
keywords: ['影院', '影厅', '剧场', '报告厅', 'cinema', 'theater'],
categoryIds: ['space_theater'],
iconTypes: ['cinema', 'theater']
@@ -52,8 +65,8 @@ const definitions: PoiCategoryDefinition[] = [
id: 'ticket-office',
label: '售票处',
icon: 'ticket',
order: 3,
homeVisible: false,
order: 6,
homeVisible: true,
keywords: ['售票处', '售票', '票务', 'ticket_office'],
categoryIds: [],
iconTypes: ['ticket_office']
@@ -62,18 +75,18 @@ const definitions: PoiCategoryDefinition[] = [
id: 'service-center',
label: '服务中心',
icon: 'service',
order: 4,
homeVisible: false,
order: 8,
homeVisible: true,
keywords: ['服务中心', '服务台', '咨询台', 'service_desk'],
categoryIds: [],
iconTypes: ['service_desk']
},
{
id: 'dining',
label: '餐',
label: '餐',
icon: 'restaurant',
order: 5,
homeVisible: false,
homeVisible: true,
keywords: ['餐饮', '餐厅', '快餐', '咖啡', 'restaurant', 'dining', 'food', 'cafe'],
categoryIds: [],
iconTypes: ['restaurant', 'dining', 'food', 'cafe']
@@ -82,7 +95,7 @@ const definitions: PoiCategoryDefinition[] = [
id: 'souvenir',
label: '文创',
icon: 'bag',
order: 6,
order: 11,
homeVisible: false,
keywords: ['文创', '纪念品', '商店', 'souvenir', 'gift', 'shop'],
categoryIds: [],
@@ -100,9 +113,9 @@ const definitions: PoiCategoryDefinition[] = [
},
{
id: 'restroom',
label: '卫生间',
label: '洗手间',
icon: 'restroom',
order: 8,
order: 4,
homeVisible: true,
keywords: ['卫生间', '洗手间', '厕所', 'toilet', 'accessible_toilet', 'restroom'],
categoryIds: [],
@@ -122,8 +135,8 @@ const definitions: PoiCategoryDefinition[] = [
id: 'stairs',
label: '楼梯',
icon: 'stairs',
order: 10,
homeVisible: true,
order: 12,
homeVisible: false,
keywords: ['楼梯', 'stairs', 'stair'],
categoryIds: [],
iconTypes: ['stairs', 'stair']
@@ -132,7 +145,7 @@ const definitions: PoiCategoryDefinition[] = [
id: 'escalator',
label: '扶梯',
icon: 'escalator',
order: 11,
order: 10,
homeVisible: true,
keywords: ['扶梯', 'escalator'],
categoryIds: [],
@@ -145,11 +158,15 @@ export const POI_CATEGORIES = Object.freeze(
)
const homeCategoryOrder: readonly PoiCategoryId[] = [
'itinerary',
'exhibition-hall',
'cinema',
'restroom',
'dining',
'ticket-office',
'nursing-room',
'service-center',
'elevator',
'stairs',
'escalator'
]
@@ -159,6 +176,25 @@ export const HOME_POI_CATEGORIES = Object.freeze(
))
)
const POI_CATEGORY_ICON_SYMBOLS: Readonly<Record<PoiCategoryId, string>> = {
itinerary: 'poi-itinerary',
'exhibition-hall': 'poi-exhibition-hall',
cinema: 'poi-cinema',
'ticket-office': 'poi-ticket-office',
'service-center': 'poi-service-center',
dining: 'poi-dining',
souvenir: 'poi-souvenir',
'nursing-room': 'poi-nursing-room',
restroom: 'poi-restroom',
elevator: 'poi-elevator',
stairs: 'poi-stairs',
escalator: 'poi-escalator'
}
export const getPoiCategoryIconHref = (categoryId: PoiCategoryId) => (
`/static/icons/poi/shortcut-icons.svg#${POI_CATEGORY_ICON_SYMBOLS[categoryId]}`
)
type PoiCategorySource = Pick<MuseumPoi, 'name' | 'primaryCategory' | 'categories'>
const normalizeValue = (value?: string | null) => (value || '').trim().toLowerCase()
@@ -177,6 +213,12 @@ export const matchesPoiCategory = (
const normalizedCategoryIds = new Set(categories.map((category) => normalizeValue(category.id)))
const normalizedIconTypes = new Set(categories.map((category) => normalizeValue(category.iconType)))
if (definition.excludedCategoryIds?.some((categoryId) => (
normalizedCategoryIds.has(normalizeValue(categoryId))
))) {
return false
}
if (definition.categoryIds.some((categoryId) => normalizedCategoryIds.has(normalizeValue(categoryId)))) {
return true
}