929 lines
21 KiB
Vue
929 lines
21 KiB
Vue
<template>
|
||
<view class="explain-list">
|
||
<!-- 顶部筛选器 -->
|
||
<view class="explain-header">
|
||
<!-- 搜索栏触发按钮 -->
|
||
<view class="search-bar-trigger" @tap="handleOpenSearch">
|
||
<view class="search-input-wrapper">
|
||
<svg class="search-icon" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||
<circle cx="8.5" cy="8.5" r="5.5" stroke="currentColor" stroke-width="1.5"/>
|
||
<path d="M12.5 12.5L17 17" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||
</svg>
|
||
<text class="search-placeholder">{{ searchKeyword || '搜索展品、展厅、设施...' }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 筛选按钮 -->
|
||
<view class="filter-buttons">
|
||
<view
|
||
class="filter-btn"
|
||
:class="{ active: activeFilter === 'hall' }"
|
||
@tap="handleFilterChange('hall')"
|
||
>
|
||
<text class="filter-text">按展厅</text>
|
||
</view>
|
||
<view
|
||
class="filter-btn"
|
||
:class="{ active: activeFilter === 'theme' }"
|
||
@tap="handleFilterChange('theme')"
|
||
>
|
||
<text class="filter-text">按主题</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 抽屉遮罩层 -->
|
||
<view
|
||
v-if="drawerVisible && !searchPanelOpen"
|
||
class="drawer-overlay"
|
||
@tap="handleCloseDrawer"
|
||
></view>
|
||
|
||
<!-- 底部可拖拽抽屉 -->
|
||
<view
|
||
class="drawer-container"
|
||
:class="{
|
||
'search-mode': searchPanelOpen,
|
||
'drawer-visible': drawerVisible,
|
||
'drawer-collapsed': !drawerVisible && !searchPanelOpen
|
||
}"
|
||
:style="drawerStyle"
|
||
@touchstart="handleTouchStart"
|
||
@touchmove="handleTouchMove"
|
||
@touchend="handleTouchEnd"
|
||
>
|
||
<!-- 抽屉把手 -->
|
||
<view class="drawer-handle" @tap="handleToggleDrawer">
|
||
<view class="handle-bar"></view>
|
||
</view>
|
||
|
||
<!-- 抽屉内容 -->
|
||
<scroll-view
|
||
class="drawer-content"
|
||
:scroll-y="true"
|
||
:scroll-top="searchScrollTop"
|
||
:show-scrollbar="false"
|
||
>
|
||
<!-- 搜索模式内容 -->
|
||
<view v-if="searchPanelOpen" class="search-content">
|
||
<!-- 搜索框 -->
|
||
<view class="search-box">
|
||
<svg class="search-box-icon" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||
<circle cx="8.5" cy="8.5" r="5.5" stroke="currentColor" stroke-width="1.5"/>
|
||
<path d="M12.5 12.5L17 17" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||
</svg>
|
||
<input
|
||
class="search-box-input"
|
||
type="text"
|
||
placeholder="搜索展品、展厅、设施..."
|
||
v-model="searchInputValue"
|
||
@input="handleSearchInput"
|
||
@confirm="handleSearchConfirm"
|
||
/>
|
||
<view v-if="searchInputValue" class="clear-btn" @tap="handleClearSearch">
|
||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
|
||
<circle cx="7" cy="7" r="6" fill="#999"/>
|
||
<path d="M4.5 4.5L9.5 9.5M9.5 4.5L4.5 9.5" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
|
||
</svg>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 搜索结果 -->
|
||
<view v-if="searchInputValue" class="results-section">
|
||
<view v-if="searchResults.length" class="results-list">
|
||
<view
|
||
v-for="item in searchResults"
|
||
:key="item.id"
|
||
class="result-item"
|
||
@tap="handleResultClick(item)"
|
||
>
|
||
<view class="result-icon" :class="item.type">
|
||
<text>{{ getResultIcon(item.type) }}</text>
|
||
</view>
|
||
<view class="result-info">
|
||
<text class="result-name">{{ item.name }}</text>
|
||
<text class="result-desc">{{ item.desc }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view v-else class="no-results">
|
||
<text>未找到 "{{ searchInputValue }}" 相关结果</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 热门搜索 (无关键词时显示) -->
|
||
<view v-if="!searchInputValue" class="hot-section">
|
||
<view class="section-header">
|
||
<text class="section-title">热门搜索</text>
|
||
</view>
|
||
<view class="hot-tags">
|
||
<view
|
||
v-for="tag in hotSearches"
|
||
:key="tag"
|
||
class="hot-tag"
|
||
@tap="handleHotTagClick(tag)"
|
||
>
|
||
<text class="hot-tag-text">{{ tag }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 搜索历史 (无关键词时显示) -->
|
||
<view v-if="!searchInputValue && searchHistory.length" class="history-section">
|
||
<view class="section-header">
|
||
<text class="section-title">搜索历史</text>
|
||
<text class="clear-history" @tap="handleClearHistory">清除</text>
|
||
</view>
|
||
<view class="history-list">
|
||
<view
|
||
v-for="(item, index) in searchHistory"
|
||
:key="index"
|
||
class="history-item"
|
||
@tap="handleHistoryClick(item)"
|
||
>
|
||
<text class="history-text">{{ item }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 关闭搜索按钮 -->
|
||
<view class="close-search" @tap="handleCloseSearch">
|
||
<text>关闭</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 正常模式内容 - 展品列表 -->
|
||
<view v-else class="exhibits-list">
|
||
<view
|
||
v-for="exhibit in filteredExhibits"
|
||
:key="exhibit.id"
|
||
class="exhibit-card"
|
||
@tap="handleExhibitClick(exhibit)"
|
||
>
|
||
<!-- 左侧展品图片 -->
|
||
<view class="exhibit-image">
|
||
<image
|
||
v-if="exhibit.image"
|
||
class="image"
|
||
:src="exhibit.image"
|
||
mode="aspectFill"
|
||
/>
|
||
<view v-else class="image-placeholder">
|
||
<svg class="placeholder-icon" width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||
<rect x="3" y="3" width="18" height="18" rx="3" stroke="currentColor" stroke-width="1.5"/>
|
||
<circle cx="8.5" cy="8.5" r="2" fill="currentColor"/>
|
||
<path d="M3 16L7 12L10 15L14 11L21 18" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 中间展品信息 -->
|
||
<view class="exhibit-info">
|
||
<text class="exhibit-name">{{ exhibit.name }}</text>
|
||
<text class="exhibit-floor">{{ exhibit.hall || exhibit.floor }}</text>
|
||
</view>
|
||
|
||
<!-- 右侧音频指示器 -->
|
||
<view v-if="exhibit.hasAudio" class="audio-indicator" @tap.stop="handleAudioClick(exhibit)">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
|
||
<path d="M12 3C12 3 14 5 14 9C14 13 12 15 12 15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||
<path d="M12 15C12 15 14 17 14 21C14 21 10 21 10 21C10 21 10 17 12 15Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||
<path d="M8 9C6.5 9 5 10.5 5 12C5 13.5 6.5 15 8 15C9 15 9.5 14.5 9.5 14.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||
<path d="M16 7C13.5 7 11.5 9 11.5 11.5C11.5 14 13.5 16 16 16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||
</svg>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部加载完毕提示 -->
|
||
<view class="loading-end">
|
||
<text class="end-text">— 已加载全部 —</text>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed } from 'vue'
|
||
import type {
|
||
ExplainExhibitViewModel
|
||
} from '@/view-models/explainViewModels'
|
||
|
||
export interface Exhibit extends ExplainExhibitViewModel {
|
||
audioUrl?: string
|
||
}
|
||
|
||
interface SearchResult {
|
||
id: string
|
||
name: string
|
||
desc: string
|
||
type: 'exhibit' | 'hall' | 'facility'
|
||
}
|
||
|
||
const props = withDefaults(defineProps<{
|
||
exhibits?: Exhibit[]
|
||
hotSearches?: string[]
|
||
initialSearchHistory?: string[]
|
||
}>(), {
|
||
exhibits: () => [],
|
||
hotSearches: () => ['恐龙化石', '银杏', '海洋生物', '霸王龙', '古植物', '矿石标本', '恐龙', '化石'],
|
||
initialSearchHistory: () => ['霸王龙', '化石', '恐龙']
|
||
})
|
||
|
||
const emit = defineEmits<{
|
||
exhibitClick: [exhibit: Exhibit]
|
||
audioClick: [exhibit: Exhibit]
|
||
filterChange: [filter: 'hall' | 'theme']
|
||
search: [keyword: string]
|
||
}>()
|
||
|
||
// 状态
|
||
const searchKeyword = ref('')
|
||
const searchPanelOpen = ref(false)
|
||
const searchInputValue = ref('')
|
||
const searchScrollTop = ref(0)
|
||
const activeFilter = ref<'hall' | 'theme'>('hall') // 默认选中"按展厅"
|
||
const drawerVisible = ref(false) // 抽屉默认收起
|
||
const drawerOffset = ref(0)
|
||
|
||
// 抽屉拖拽样式 - 将 drawerOffset 绑定到 transform
|
||
const drawerStyle = computed(() => {
|
||
if (!drawerVisible.value && !searchPanelOpen.value) {
|
||
return {} // 收起状态不需要 transform
|
||
}
|
||
return {
|
||
transform: `translateY(${drawerOffset.value}px)`
|
||
}
|
||
})
|
||
|
||
// 抽屉拖拽相关
|
||
const DRAWER_MIN_OFFSET = 0
|
||
const DRAWER_MAX_OFFSET = 200
|
||
const DRAWER_SNAP_THRESHOLD = 100
|
||
let touchStartY = 0
|
||
let touchCurrentY = 0
|
||
|
||
// 热门搜索
|
||
const hotSearches = computed(() => props.hotSearches)
|
||
|
||
// 搜索历史
|
||
const searchHistory = ref<string[]>([...props.initialSearchHistory])
|
||
|
||
// 搜索数据
|
||
const allData = computed<SearchResult[]>(() => props.exhibits.map((exhibit) => ({
|
||
id: exhibit.id,
|
||
name: exhibit.name,
|
||
desc: exhibit.hall || exhibit.floor || '展项内容',
|
||
type: 'exhibit'
|
||
})))
|
||
|
||
// 搜索结果计算
|
||
const searchResults = computed(() => {
|
||
if (!searchInputValue.value.trim()) return []
|
||
const kw = searchInputValue.value.toLowerCase()
|
||
return allData.value.filter(item =>
|
||
item.name.toLowerCase().includes(kw) ||
|
||
item.desc.toLowerCase().includes(kw)
|
||
)
|
||
})
|
||
|
||
// 展品列表
|
||
const exhibits = computed(() => props.exhibits)
|
||
|
||
// 过滤展品
|
||
const filteredExhibits = computed(() => {
|
||
if (!searchKeyword.value.trim()) {
|
||
return exhibits.value
|
||
}
|
||
const keyword = searchKeyword.value.toLowerCase()
|
||
return exhibits.value.filter(exhibit =>
|
||
exhibit.name.toLowerCase().includes(keyword) ||
|
||
(exhibit.hall && exhibit.hall.toLowerCase().includes(keyword))
|
||
)
|
||
})
|
||
|
||
// 事件处理
|
||
const handleExhibitClick = (exhibit: Exhibit) => {
|
||
emit('exhibitClick', exhibit)
|
||
}
|
||
|
||
const handleAudioClick = (exhibit: Exhibit) => {
|
||
emit('audioClick', exhibit)
|
||
}
|
||
|
||
// 搜索相关方法
|
||
const handleOpenSearch = () => {
|
||
searchPanelOpen.value = true
|
||
searchScrollTop.value = 0
|
||
}
|
||
|
||
const handleCloseSearch = () => {
|
||
searchPanelOpen.value = false
|
||
searchInputValue.value = ''
|
||
}
|
||
|
||
const handleSearchInput = () => {
|
||
// 实时搜索
|
||
}
|
||
|
||
const handleSearchConfirm = () => {
|
||
if (searchInputValue.value.trim()) {
|
||
addToHistory(searchInputValue.value)
|
||
searchKeyword.value = searchInputValue.value
|
||
emit('search', searchInputValue.value)
|
||
}
|
||
}
|
||
|
||
const handleClearSearch = () => {
|
||
searchInputValue.value = ''
|
||
}
|
||
|
||
const handleHotTagClick = (tag: string) => {
|
||
searchInputValue.value = tag
|
||
handleSearchConfirm()
|
||
}
|
||
|
||
const handleHistoryClick = (history: string) => {
|
||
searchInputValue.value = history
|
||
handleSearchConfirm()
|
||
}
|
||
|
||
const handleClearHistory = () => {
|
||
searchHistory.value = []
|
||
}
|
||
|
||
const handleResultClick = (item: SearchResult) => {
|
||
if (item.type === 'exhibit') {
|
||
const exhibit = exhibits.value.find(e => e.id === item.id)
|
||
if (exhibit) {
|
||
emit('exhibitClick', exhibit)
|
||
}
|
||
}
|
||
searchPanelOpen.value = false
|
||
searchInputValue.value = ''
|
||
}
|
||
|
||
const addToHistory = (keyword: string) => {
|
||
if (!searchHistory.value.includes(keyword)) {
|
||
searchHistory.value.unshift(keyword)
|
||
if (searchHistory.value.length > 10) {
|
||
searchHistory.value.pop()
|
||
}
|
||
}
|
||
}
|
||
|
||
const getResultIcon = (type: string): string => {
|
||
const icons: Record<string, string> = {
|
||
exhibit: '🖼️',
|
||
hall: '🏛️',
|
||
facility: '📍'
|
||
}
|
||
return icons[type] || '📍'
|
||
}
|
||
|
||
const handleFilterChange = (filter: 'hall' | 'theme') => {
|
||
if (activeFilter.value === filter && drawerVisible.value) {
|
||
// 再次点击同一筛选项,收起抽屉
|
||
handleCloseDrawer()
|
||
} else {
|
||
// 切换筛选项或展开抽屉
|
||
activeFilter.value = filter
|
||
drawerVisible.value = true
|
||
drawerOffset.value = 0
|
||
emit('filterChange', filter)
|
||
}
|
||
}
|
||
|
||
// 收起抽屉
|
||
const handleCloseDrawer = () => {
|
||
drawerVisible.value = false
|
||
drawerOffset.value = 0
|
||
}
|
||
|
||
// 切换抽屉展开/收起
|
||
const handleToggleDrawer = () => {
|
||
if (drawerVisible.value) {
|
||
handleCloseDrawer()
|
||
} else {
|
||
drawerVisible.value = true
|
||
drawerOffset.value = 0
|
||
emit('filterChange', activeFilter.value)
|
||
}
|
||
}
|
||
|
||
// 抽屉拖拽处理
|
||
const handleTouchStart = (e: TouchEvent) => {
|
||
// 搜索模式下或收起状态禁用抽屉拖拽
|
||
if (searchPanelOpen.value || !drawerVisible.value) return
|
||
touchStartY = e.touches[0].clientY
|
||
touchCurrentY = touchStartY
|
||
}
|
||
|
||
const handleTouchMove = (e: TouchEvent) => {
|
||
// 搜索模式下或收起状态禁用抽屉拖拽
|
||
if (searchPanelOpen.value || !drawerVisible.value) return
|
||
touchCurrentY = e.touches[0].clientY
|
||
const deltaY = touchCurrentY - touchStartY
|
||
|
||
let newOffset = drawerOffset.value + deltaY
|
||
newOffset = Math.max(DRAWER_MIN_OFFSET, Math.min(DRAWER_MAX_OFFSET, newOffset))
|
||
|
||
drawerOffset.value = newOffset
|
||
touchStartY = touchCurrentY
|
||
}
|
||
|
||
const handleTouchEnd = () => {
|
||
// 搜索模式下或收起状态禁用抽屉拖拽
|
||
if (searchPanelOpen.value || !drawerVisible.value) return
|
||
|
||
if (drawerOffset.value < DRAWER_SNAP_THRESHOLD) {
|
||
drawerOffset.value = DRAWER_MIN_OFFSET
|
||
} else {
|
||
// 向下拖拽超过阈值,收起抽屉
|
||
handleCloseDrawer()
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.explain-list {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
position: relative;
|
||
background: transparent;
|
||
// 非交互区域不透光穿透,让触摸事件落到地图上
|
||
pointer-events: none;
|
||
}
|
||
|
||
.explain-header {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
padding: 56px 16px 12px;
|
||
background-color: transparent;
|
||
position: relative;
|
||
z-index: 10;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.search-bar-trigger {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.search-input-wrapper {
|
||
box-sizing: border-box;
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
width: 100%;
|
||
background: var(--museum-bg-cream);
|
||
border: 1px solid white;
|
||
border-radius: var(--radius-mini);
|
||
padding: 9px 12px;
|
||
}
|
||
|
||
.search-icon {
|
||
flex-shrink: 0;
|
||
margin-right: 8px;
|
||
color: #333333;
|
||
}
|
||
|
||
.search-placeholder {
|
||
font-size: 14px;
|
||
color: var(--museum-text-disabled);
|
||
}
|
||
|
||
.filter-buttons {
|
||
display: flex;
|
||
gap: 8px;
|
||
position: relative;
|
||
z-index: 11;
|
||
width: 100%;
|
||
}
|
||
|
||
.filter-btn {
|
||
flex: 1;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 34px;
|
||
padding: 0;
|
||
border-radius: 6px;
|
||
background-color: rgba(0, 0, 0, 0.06);
|
||
border: 1px solid transparent;
|
||
transition: all 0.2s;
|
||
|
||
&:active {
|
||
opacity: 0.7;
|
||
}
|
||
}
|
||
|
||
.filter-btn.active {
|
||
background-color: #1a1a1a;
|
||
border-color: #1a1a1a;
|
||
}
|
||
|
||
.filter-text {
|
||
font-size: 13px;
|
||
font-weight: 400;
|
||
color: #333333;
|
||
line-height: 1;
|
||
}
|
||
|
||
.filter-btn.active .filter-text {
|
||
color: #E0E100;
|
||
font-weight: 500;
|
||
}
|
||
|
||
// 抽屉遮罩层
|
||
.drawer-overlay {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.3);
|
||
z-index: 99;
|
||
animation: fadeIn 0.3s ease;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; }
|
||
to { opacity: 1; }
|
||
}
|
||
|
||
// 抽屉容器
|
||
.drawer-container {
|
||
position: absolute;
|
||
// 默认展开高度:屏幕一半
|
||
top: 50vh;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background-color: #FFFFFF;
|
||
border-radius: 24px 24px 0 0;
|
||
box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.08);
|
||
transition: top 0.35s cubic-bezier(0.4, 0, 0.2, 1),
|
||
transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||
opacity 0.3s ease;
|
||
z-index: 100;
|
||
}
|
||
|
||
// 收起状态:隐藏到屏幕底部
|
||
.drawer-container.drawer-collapsed {
|
||
top: 100vh;
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
}
|
||
|
||
// 展开状态
|
||
.drawer-container.drawer-visible {
|
||
top: 50vh;
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.drawer-container.search-mode {
|
||
top: 44px;
|
||
border-radius: 0;
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.drawer-handle {
|
||
display: flex;
|
||
justify-content: center;
|
||
padding: 8px 0;
|
||
}
|
||
|
||
.handle-bar {
|
||
width: 66px;
|
||
height: 5px;
|
||
background-color: #E5E5E5;
|
||
border-radius: 2.5px;
|
||
}
|
||
|
||
.drawer-content {
|
||
flex: 1;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
// 移除 overflow-y: auto,避免与 scroll-view 自身滚动机制冲突
|
||
padding: 12px 16px 0;
|
||
height: 0;
|
||
|
||
::-webkit-scrollbar {
|
||
display: none;
|
||
width: 0;
|
||
height: 0;
|
||
color: transparent;
|
||
}
|
||
}
|
||
|
||
// 搜索内容样式
|
||
.search-content {
|
||
padding-bottom: calc(20px + env(safe-area-inset-bottom));
|
||
}
|
||
|
||
.search-box {
|
||
box-sizing: border-box;
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
width: 100%;
|
||
background: var(--museum-bg-cream);
|
||
border: 1px solid white;
|
||
border-radius: var(--radius-mini);
|
||
padding: 9px 12px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.search-box-icon {
|
||
flex-shrink: 0;
|
||
margin-right: 8px;
|
||
color: #333333;
|
||
}
|
||
|
||
.search-box-input {
|
||
flex: 1;
|
||
font-size: 14px;
|
||
color: var(--museum-text-primary);
|
||
background: transparent;
|
||
border: none;
|
||
outline: none;
|
||
|
||
&::placeholder {
|
||
color: var(--museum-text-disabled);
|
||
}
|
||
}
|
||
|
||
.clear-btn {
|
||
flex-shrink: 0;
|
||
width: 18px;
|
||
height: 18px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.results-section {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.results-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.result-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 12px;
|
||
border-radius: 12px;
|
||
|
||
&:active {
|
||
background-color: rgba(0, 0, 0, 0.04);
|
||
}
|
||
}
|
||
|
||
.result-icon {
|
||
width: 32px;
|
||
height: 32px;
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: rgba(0, 0, 0, 0.04);
|
||
border-radius: 8px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.result-icon.exhibit {
|
||
background-color: rgba(224, 225, 0, 0.15);
|
||
}
|
||
|
||
.result-info {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.result-name {
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
color: #1A1A1A;
|
||
display: block;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.result-desc {
|
||
font-size: 12px;
|
||
color: #999999;
|
||
display: block;
|
||
}
|
||
|
||
.no-results {
|
||
padding: 40px 20px;
|
||
text-align: center;
|
||
color: #999999;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.hot-section {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.section-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #1A1A1A;
|
||
}
|
||
|
||
.clear-history {
|
||
font-size: 12px;
|
||
color: #999;
|
||
}
|
||
|
||
.hot-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.hot-tag {
|
||
padding: 8px 14px;
|
||
background-color: rgba(0, 0, 0, 0.04);
|
||
border-radius: 18px;
|
||
|
||
&:active {
|
||
background-color: rgba(224, 225, 0, 0.3);
|
||
}
|
||
}
|
||
|
||
.hot-tag-text {
|
||
font-size: 13px;
|
||
color: #333333;
|
||
}
|
||
|
||
.history-section {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.history-list {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.history-item {
|
||
padding: 8px 14px;
|
||
background-color: rgba(0, 0, 0, 0.04);
|
||
border-radius: 18px;
|
||
|
||
&:active {
|
||
background-color: rgba(224, 225, 0, 0.3);
|
||
}
|
||
}
|
||
|
||
.history-text {
|
||
font-size: 13px;
|
||
color: #333333;
|
||
}
|
||
|
||
.close-search {
|
||
text-align: center;
|
||
padding: 12px;
|
||
margin-top: 8px;
|
||
|
||
text {
|
||
font-size: 14px;
|
||
color: #666666;
|
||
}
|
||
|
||
&:active {
|
||
text {
|
||
color: #333333;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 展品列表样式
|
||
.exhibits-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.exhibit-card {
|
||
display: flex;
|
||
align-items: center;
|
||
width: 100%;
|
||
height: 64px;
|
||
background-color: #F3F3F3;
|
||
border-radius: 12px;
|
||
padding: 0 12px;
|
||
box-sizing: border-box;
|
||
transition: all 0.2s ease;
|
||
|
||
&:active {
|
||
transform: scale(0.98);
|
||
opacity: 0.9;
|
||
}
|
||
}
|
||
|
||
.exhibit-image {
|
||
flex-shrink: 0;
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
background-color: #F0F0F0;
|
||
}
|
||
|
||
.image {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.image-placeholder {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #F0F0F0;
|
||
}
|
||
|
||
.placeholder-icon {
|
||
color: #CCCCCC;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.exhibit-info {
|
||
flex: 1;
|
||
margin-left: 12px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.exhibit-name {
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
color: #1A1A1A;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.exhibit-floor {
|
||
font-size: 12px;
|
||
color: #999999;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
.audio-indicator {
|
||
flex-shrink: 0;
|
||
width: 36px;
|
||
height: 36px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #E0E100;
|
||
border-radius: 50%;
|
||
margin-left: 12px;
|
||
|
||
&:active {
|
||
transform: scale(0.9);
|
||
}
|
||
|
||
svg {
|
||
width: 18px;
|
||
height: 18px;
|
||
color: #1A1A1A;
|
||
}
|
||
}
|
||
|
||
.loading-end {
|
||
padding: 24px 16px calc(24px + env(safe-area-inset-bottom));
|
||
text-align: center;
|
||
}
|
||
|
||
.end-text {
|
||
font-size: 12px;
|
||
color: #CCCCCC;
|
||
}
|
||
</style>
|