优化:一级菜单移到app

This commit is contained in:
马丁 2026-05-19 14:12:58 +08:00
parent 59edb13f53
commit 67d9c204dd
4 changed files with 377 additions and 359 deletions

View File

@ -5,6 +5,9 @@ import { useI18n } from 'vue-i18n'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import { useUserStore } from './stores/user' import { useUserStore } from './stores/user'
import { useLocaleStore } from './stores/locale' import { useLocaleStore } from './stores/locale'
import { useMenuStore } from './stores/menu'
import { storeToRefs } from 'pinia'
import { useSearchHistory } from './composables/useSearchHistory'
import type { LocaleCode } from './plugins/i18n' import type { LocaleCode } from './plugins/i18n'
import Toast from './components/Toast.vue' import Toast from './components/Toast.vue'
@ -13,8 +16,16 @@ const { t } = useI18n()
const router = useRouter() const router = useRouter()
const userStore = useUserStore() const userStore = useUserStore()
const localeStore = useLocaleStore() const localeStore = useLocaleStore()
const menuStore = useMenuStore()
const localeMenuOpen = ref(false) const localeMenuOpen = ref(false)
const { categoryLayers, layerActiveValues } = storeToRefs(menuStore)
const searchHistory = useSearchHistory()
const searchHistoryList = computed(() => searchHistory.list.value)
const searchExpanded = ref(false)
const searchKeyword = ref('')
const searchInputRef = ref<{ focus: () => void } | null>(null)
function chooseLocale(loc: LocaleCode) { function chooseLocale(loc: LocaleCode) {
localeStore.setLocale(loc) localeStore.setLocale(loc)
localeMenuOpen.value = false localeMenuOpen.value = false
@ -61,6 +72,39 @@ async function refreshUserData() {
await userStore.fetchPositionsValue() await userStore.fetchPositionsValue()
} }
function expandSearch() {
searchExpanded.value = true
nextTick(() => {
searchInputRef.value?.focus()
})
}
function collapseSearch() {
searchExpanded.value = false
searchKeyword.value = ''
}
function onSearchBlur() {
setTimeout(() => {
collapseSearch()
}, 200)
}
function onSearchSubmit() {
const kw = searchKeyword.value.trim()
if (kw) {
searchHistory.add(kw)
// store router
router.push({ path: '/', query: { q: kw } })
}
collapseSearch()
}
function selectHistoryItem(item: string) {
searchKeyword.value = item
onSearchSubmit()
}
onMounted(() => { onMounted(() => {
refreshUserData() refreshUserData()
}) })
@ -75,7 +119,8 @@ watch(
<template> <template>
<v-app> <v-app>
<v-app-bar color="surface" elevation="0"> <v-app-bar color="surface" elevation="0" height="112">
<div class="flex-column header-content">
<div class="app-bar-inner"> <div class="app-bar-inner">
<v-btn v-if="currentRoute !== '/'" icon variant="text" class="back-btn" :aria-label="t('common.back')" <v-btn v-if="currentRoute !== '/'" icon variant="text" class="back-btn" :aria-label="t('common.back')"
@click="onBackClick"> @click="onBackClick">
@ -92,7 +137,9 @@ watch(
</div> </div>
</div> </div>
</v-app-bar-title> </v-app-bar-title>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<template v-if="!userStore.isLoggedIn"> <template v-if="!userStore.isLoggedIn">
<v-menu v-model="localeMenuOpen" :close-on-content-click="true" location="bottom" <v-menu v-model="localeMenuOpen" :close-on-content-click="true" location="bottom"
transition="scale-transition"> transition="scale-transition">
@ -125,6 +172,57 @@ watch(
</v-btn> </v-btn>
</template> </template>
</div> </div>
<!-- 提取的顶部菜单栏与搜索功能 -->
<div v-if="currentRoute === '/' && categoryLayers.length > 0" class="home-category-layer1-wrap">
<div class="home-category-layer1-row">
<v-tabs :model-value="layerActiveValues[0]" class="home-tab-bar home-tab-bar--inline" height="48"
@update:model-value="menuStore.onCategorySelect(0, $event)">
<v-tab v-for="item in categoryLayers[0]" :key="item.id" :value="item.id" :ripple="false" >
{{ item.label }}
</v-tab>
</v-tabs>
<v-btn icon variant="text" class="home-search-btn" :aria-label="t('common.search')" @click="expandSearch">
<v-icon size="24">mdi-magnify</v-icon>
</v-btn>
</div>
<!-- 搜索展开时浮层输入框 + 历史记录 -->
<transition name="home-search-overlay">
<div v-if="searchExpanded" class="home-search-overlay">
<div class="home-search-overlay-inner">
<v-text-field ref="searchInputRef" v-model="searchKeyword" density="compact" hide-details
:placeholder="t('home.searchPlaceholder')" prepend-inner-icon="mdi-magnify" variant="outlined"
class="home-search-overlay-field" @blur="onSearchBlur" @keydown.enter="onSearchSubmit" />
<v-btn icon variant="text" size="small" class="home-search-close-btn" :aria-label="t('common.collapse')"
@click="collapseSearch">
<v-icon size="22">mdi-close</v-icon>
</v-btn>
</div>
<div v-if="searchHistoryList.length > 0" class="home-search-history">
<div class="home-search-history-header">
<span class="home-search-history-title">{{ t('home.searchHistory') }}</span>
<v-btn variant="text" size="x-small" color="primary" class="home-search-history-clear"
@click="searchHistory.clearAll">
{{ t('common.clear') }}
</v-btn>
</div>
<ul class="home-search-history-list">
<li v-for="(item, idx) in searchHistoryList" :key="`${item}-${idx}`" class="home-search-history-item">
<button type="button" class="home-search-history-text" @click="selectHistoryItem(item)">
{{ item }}
</button>
<v-btn icon variant="text" size="x-small" class="home-search-history-delete"
:aria-label="t('common.delete')" @click.stop="searchHistory.remove(idx)">
<v-icon size="20">mdi-close</v-icon>
</v-btn>
</li>
</ul>
</div>
</div>
</transition>
</div>
</div>
</v-app-bar> </v-app-bar>
<v-main class="app-main"> <v-main class="app-main">
<div class="app-main-scroll" data-main-scroll> <div class="app-main-scroll" data-main-scroll>
@ -161,9 +259,15 @@ watch(
</v-app> </v-app>
</template> </template>
<style scoped> <style scoped lang="scss">
.app-bar-inner { .header-content {
width: 100%;
max-width: 1440px; max-width: 1440px;
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
height: 112px;
}
.app-bar-inner {
margin: 0 auto; margin: 0 auto;
width: 100%; width: 100%;
display: flex; display: flex;
@ -346,4 +450,139 @@ watch(
:deep(.v-bottom-navigation__content .v-ripple__container) { :deep(.v-bottom-navigation__content .v-ripple__container) {
display: none !important; display: none !important;
} }
/* 第一层置顶、全宽sticky 参照 app-main-scrolltop:0 贴容器顶(即 app-bar 下方) */
.home-category-layer1-wrap {
width: 100vw;
background-color: white;
}
.home-category-layer1-row {
max-width: 1440px;
margin: 0 auto;
display: flex;
align-items: center;
}
.home-tab-bar--inline {
flex: 1;
min-width: 0;
}
.home-tab-bar--inline :deep(.v-tabs) {
height: 48px;
}
.home-tab-bar--inline :deep(.v-tab) {
height: 48px;
padding: 0 8px 0 8px;
}
.home-category-layer1-actions {
display: flex;
flex-shrink: 0;
gap: 0;
}
.home-search-btn {
flex-shrink: 0;
}
.home-search-overlay-enter-active,
.home-search-overlay-leave-active {
transition: opacity 0.2s ease, transform 0.2s ease;
}
.home-search-overlay-enter-from,
.home-search-overlay-leave-to {
opacity: 0;
transform: translateY(-8px);
}
.v-tabs-height {
height: 48px;
}
.home-search-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
background: #fff;
z-index: 2000;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
padding: 8px;
display: flex;
flex-direction: column;
gap: 8px;
}
.home-search-overlay-inner {
display: flex;
align-items: center;
gap: 8px;
}
.home-search-overlay-field {
flex: 1;
}
.home-search-history {
display: flex;
flex-direction: column;
gap: 8px;
padding: 0 4px;
}
.home-search-history-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.home-search-history-title {
font-size: 0.85rem;
font-weight: 600;
color: rgba(0, 0, 0, 0.6);
}
.home-search-history-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.home-search-history-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 8px;
border-radius: 6px;
transition: background-color 0.2s;
}
.home-search-history-item:hover {
background-color: rgba(0, 0, 0, 0.04);
}
.home-search-history-text {
flex: 1;
text-align: left;
font-size: 0.9rem;
color: rgba(0, 0, 0, 0.87);
background: transparent;
border: none;
cursor: pointer;
padding: 0;
}
@media (max-width: 600px) {
.home-category-layer1-wrap {
margin-left: 12px;
}
}
</style> </style>

View File

@ -30,3 +30,8 @@ html, body {
height: 0; height: 0;
} }
} }
.flex-column {
display: flex;
flex-direction: column;
}

View File

@ -35,10 +35,59 @@ export const useMenuStore = defineStore('menu', () => {
return layers return layers
}) })
/** 分类切换时的防抖状态,防止重复请求 */
let categorySelectTimer: ReturnType<typeof setTimeout> | null = null
/** 触发事件加载的回调函数(由 Home 页面注册) */
const loadEventsCallback = ref<(() => void) | null>(null)
/** 触发清除事件列表缓存的回调函数(由 Home 页面注册) */
const clearCacheCallback = ref<(() => void) | null>(null)
/** 分类选中时:若有 children 则展开下一层并默认选中第一个,并重新加载列表 */
function onCategorySelect(layerIndex: number, selectedId: string) {
if (!selectedId || layerActiveValues.value[layerIndex] === selectedId) {
return
}
if (categorySelectTimer) {
clearTimeout(categorySelectTimer)
}
const nextValues = [...layerActiveValues.value]
nextValues[layerIndex] = selectedId
nextValues.length = layerIndex + 1
const layers = categoryLayers.value
const layer = layers[layerIndex]
const node = layer?.find((n) => n.id === selectedId)
const children = filterVisible(node?.children)
const firstChild = children[0]
if (firstChild && layerIndex < 2) {
nextValues.push(firstChild.id)
}
layerActiveValues.value = nextValues
if (clearCacheCallback.value) {
clearCacheCallback.value()
}
categorySelectTimer = setTimeout(() => {
categorySelectTimer = null
if (loadEventsCallback.value) {
loadEventsCallback.value()
}
}, 100)
}
return { return {
categoryTree, categoryTree,
layerActiveValues, layerActiveValues,
categoryLayers, categoryLayers,
filterVisible filterVisible,
onCategorySelect,
loadEventsCallback,
clearCacheCallback
} }
}) })

View File

@ -1,66 +1,21 @@
<template> <template>
<div class="home-page"> <div class="home-page">
<!-- 第一层单行紧凑布局tabs + 搜索/筛选图标 -->
<div v-if="categoryLayers.length > 0" class="home-category-layer1-wrap">
<div class="home-category-layer1-row">
<v-tabs :model-value="layerActiveValues[0]" class="home-tab-bar home-tab-bar--inline"
@update:model-value="onCategorySelect(0, $event)">
<v-tab v-for="item in categoryLayers[0]" :key="item.id" :value="item.id" :ripple="false">
{{ item.label }}
</v-tab>
</v-tabs>
</div>
<!-- 搜索展开时浮层输入框 + 历史记录 -->
<transition name="home-search-overlay">
<div v-if="searchExpanded" class="home-search-overlay">
<div class="home-search-overlay-inner">
<v-text-field ref="searchInputRef" v-model="searchKeyword" density="compact" hide-details
:placeholder="t('home.searchPlaceholder')" prepend-inner-icon="mdi-magnify" variant="outlined"
class="home-search-overlay-field" @blur="onSearchBlur" @keydown.enter="onSearchSubmit" />
<v-btn icon variant="text" size="small" class="home-search-close-btn" :aria-label="t('common.collapse')"
@click="collapseSearch">
<v-icon size="22">mdi-close</v-icon>
</v-btn>
</div>
<div v-if="searchHistoryList.length > 0" class="home-search-history">
<div class="home-search-history-header">
<span class="home-search-history-title">{{ t('home.searchHistory') }}</span>
<v-btn variant="text" size="x-small" color="primary" class="home-search-history-clear"
@click="searchHistory.clearAll">
{{ t('common.clear') }}
</v-btn>
</div>
<ul class="home-search-history-list">
<li v-for="(item, idx) in searchHistoryList" :key="`${item}-${idx}`" class="home-search-history-item">
<button type="button" class="home-search-history-text" @click="selectHistoryItem(item)">
{{ item }}
</button>
<v-btn icon variant="text" size="x-small" class="home-search-history-delete"
:aria-label="t('common.delete')" @click.stop="searchHistory.remove(idx)">
<v-icon size="20">mdi-close</v-icon>
</v-btn>
</li>
</ul>
</div>
</div>
</transition>
</div>
<v-container fluid class="home-container"> <v-container fluid class="home-container">
<!-- 第二三层随内容滚动回顶部时与列表第一行一起出现 --> <!-- 第二三层随内容滚动回顶部时与列表第一行一起出现 -->
<div v-if="categoryLayers.length >= 2" class="home-category-layers-23-scroll"> <div v-if="categoryLayers.length > 1" class="home-category-layers-23-scroll">
<div class="home-category-layer home-category-layer--icon"> <div class="home-category-layer home-category-layer--icon">
<div class="home-category-icon-row"> <div class="home-category-icon-row">
<v-chip v-for="item in categoryLayers[1]" :key="item.id" class="home-category-icon-item" <v-chip v-for="item in categoryLayers[1]" :key="item.id" class="home-category-icon-item"
:color="layerActiveValues[1] === item.id ? 'primary' : undefined" :color="layerActiveValues[1] === item.id ? 'primary' : undefined"
:variant="layerActiveValues[1] === item.id ? 'tonal' : 'outlined'" size="small" :variant="layerActiveValues[1] === item.id ? 'tonal' : 'outlined'" size="small"
@click="onCategorySelect(1, item.id)"> @click="menuStore.onCategorySelect(1, item.id)">
<span class="home-category-icon-label">{{ item.label }}</span> <span class="home-category-icon-label">{{ item.label }}</span>
</v-chip> </v-chip>
</div> </div>
</div> </div>
<div v-if="categoryLayers.length >= 3" class="home-category-layer home-category-layer--third"> <div v-if="categoryLayers.length >= 3" class="home-category-layer home-category-layer--third">
<v-tabs :model-value="layerActiveValues[2]" class="home-tab-bar home-tab-bar--compact" <v-tabs :model-value="layerActiveValues[2]" class="home-tab-bar home-tab-bar--compact"
@update:model-value="onCategorySelect(2, $event)"> @update:model-value="menuStore.onCategorySelect(2, $event)">
<v-tab v-for="item in categoryLayers[2]" :key="item.id" :value="item.id" :ripple="false"> <v-tab v-for="item in categoryLayers[2]" :key="item.id" :value="item.id" :ripple="false">
{{ item.label }} {{ item.label }}
</v-tab> </v-tab>
@ -119,13 +74,13 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const props = withDefaults( // const props = withDefaults(
defineProps<{ // defineProps<{
/** 进入页面时是否自动展开搜索(供 /search 路由使用) */ // /** /search 使 */
initialSearchExpanded?: boolean // initialSearchExpanded?: boolean
}>(), // }>(),
{ initialSearchExpanded: false }, // { initialSearchExpanded: false },
) // )
defineOptions({ name: 'HomePage' }) defineOptions({ name: 'HomePage' })
import { import {
@ -158,7 +113,6 @@ import {
} from '../api/category' } from '../api/category'
import { USE_MOCK_CATEGORY } from '../config/mock' import { USE_MOCK_CATEGORY } from '../config/mock'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useSearchHistory } from '../composables/useSearchHistory'
import { useToastStore } from '../stores/toast' import { useToastStore } from '../stores/toast'
import { useLocaleStore } from '../stores/locale' import { useLocaleStore } from '../stores/locale'
import { useMenuStore } from '../stores/menu' import { useMenuStore } from '../stores/menu'
@ -166,58 +120,13 @@ import { storeToRefs } from 'pinia'
const { mobile } = useDisplay() const { mobile } = useDisplay()
const { t } = useI18n() const { t } = useI18n()
const searchHistory = useSearchHistory()
const searchHistoryList = computed(() => searchHistory.list.value)
const isMobile = computed(() => mobile.value) const isMobile = computed(() => mobile.value)
const menuStore = useMenuStore() const menuStore = useMenuStore()
const { categoryTree, layerActiveValues, categoryLayers } = storeToRefs(menuStore) const { categoryTree, layerActiveValues, categoryLayers } = storeToRefs(menuStore)
const { filterVisible } = menuStore const { filterVisible } = menuStore
/** 第三层搜索框是否展开 */
const searchExpanded = ref(false)
/** 搜索关键词 */
const searchKeyword = ref('')
const searchInputRef = ref<{ focus: () => void } | null>(null)
function expandSearch() {
searchExpanded.value = true
nextTick(() => {
const el = searchInputRef.value as { focus?: () => void } | null
el?.focus?.()
})
}
function collapseSearch() {
searchExpanded.value = false
searchKeyword.value = ''
if (activeSearchKeyword.value) {
activeSearchKeyword.value = ''
clearEventListCache()
eventPage.value = 1
loadEvents(1, false, '')
}
}
function onSearchBlur() {
setTimeout(() => {
if (!searchKeyword.value.trim()) searchExpanded.value = false
}, 100)
}
function onSearchSubmit() {
const k = searchKeyword.value.trim()
if (k) {
searchHistory.add(k)
doSearch(k)
}
}
function selectHistoryItem(item: string) {
searchKeyword.value = item
searchHistory.add(item)
doSearch(item)
}
function doSearch(keyword: string) { function doSearch(keyword: string) {
activeSearchKeyword.value = keyword activeSearchKeyword.value = keyword
@ -250,39 +159,6 @@ const activeTagIds = computed(() => {
return Array.from(tagIdSet) return Array.from(tagIdSet)
}) })
/** 分类选中时:若有 children 则展开下一层并默认选中第一个,并重新加载列表 */
function onCategorySelect(layerIndex: number, selectedId: string) {
if (!selectedId || layerActiveValues.value[layerIndex] === selectedId) {
return
}
if (categorySelectTimer) {
clearTimeout(categorySelectTimer)
}
const nextValues = [...layerActiveValues.value]
nextValues[layerIndex] = selectedId
nextValues.length = layerIndex + 1
const layers = categoryLayers.value
const layer = layers[layerIndex]
const node = layer?.find((n) => n.id === selectedId)
const children = filterVisible(node?.children)
const firstChild = children[0]
if (firstChild && layerIndex < 2) {
nextValues.push(firstChild.id)
}
layerActiveValues.value = nextValues
clearEventListCache()
eventPage.value = 1
categorySelectTimer = setTimeout(() => {
categorySelectTimer = null
loadEvents(1, false)
}, 100)
}
/** 初始化分类选中:默认选中第一个,若有 children 则递归展开 */ /** 初始化分类选中:默认选中第一个,若有 children 则递归展开 */
function initCategorySelection() { function initCategorySelection() {
@ -305,7 +181,7 @@ function initCategorySelection() {
const PAGE_SIZE = 10 const PAGE_SIZE = 10
/** 分类切换时的防抖状态,防止重复请求 */ /** 分类切换时的防抖状态,防止重复请求 */
let categorySelectTimer: ReturnType<typeof setTimeout> | null = null
/** 接口返回的列表(已映射为卡片所需结构) */ /** 接口返回的列表(已映射为卡片所需结构) */
const eventList = ref<EventCardItem[]>([]) const eventList = ref<EventCardItem[]>([])
@ -457,8 +333,21 @@ function updateGridColumns() {
gridColumns.value = Math.max(1, n) gridColumns.value = Math.max(1, n)
} }
import { useRoute } from 'vue-router'
const route = useRoute()
/** 当前生效的搜索关键词(用于分页加载) */ /** 当前生效的搜索关键词(用于分页加载) */
const activeSearchKeyword = ref('') const activeSearchKeyword = ref((route.query.q as string) || '')
watch(
() => route.query.q,
(newQ) => {
if (newQ !== undefined) {
doSearch(newQ as string)
}
}
)
/** 请求事件列表并追加或覆盖到 eventList公开接口无需鉴权成功后会更新内存缓存 */ /** 请求事件列表并追加或覆盖到 eventList公开接口无需鉴权成功后会更新内存缓存 */
async function loadEvents(page: number, append: boolean, keyword?: string) { async function loadEvents(page: number, append: boolean, keyword?: string) {
@ -538,7 +427,8 @@ onBeforeRouteLeave(() => {
}) })
onMounted(() => { onMounted(() => {
if (props.initialSearchExpanded) expandSearch() // emit
// if (props.initialSearchExpanded) expandSearch()
loadCategory() loadCategory()
nextTick(() => { nextTick(() => {
const scrollEl = getMainScrollEl() const scrollEl = getMainScrollEl()
@ -605,17 +495,13 @@ onActivated(() => {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
/* fluid 后无断点 max-width用自定义 max-width 让列表在 2137px 等宽屏下能算到 6 列 */
.home-container { .home-container {
min-height: 100vh; flex: 1 1 0;
max-width: 2560px; display: flex;
margin-left: auto; flex-direction: column;
margin-right: auto; min-height: 0;
padding: 0 8px !important; padding: 0;
} margin: 112px 0 0 0;
.home-header {
margin-bottom: 20px;
} }
.home-title { .home-title {
@ -732,165 +618,6 @@ onActivated(() => {
margin-top: 40px; margin-top: 40px;
} }
/* 第一层置顶、全宽sticky 参照 app-main-scrolltop:0 贴容器顶(即 app-bar 下方) */
.home-category-layer1-wrap {
position: fixed;
z-index: 10;
top: 64px;
width: 100vw;
background-color: white;
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
}
.home-category-layer1-row {
max-width: 1440px;
margin: 0 auto;
display: flex;
align-items: center;
@include auto-space(padding-left);
}
.home-tab-bar--inline {
flex: 1;
min-width: 0;
}
.home-tab-bar--inline :deep(.v-tabs) {
min-height: 48px;
}
.home-tab-bar--inline :deep(.v-tab) {
min-height: 48px;
padding: 0 8px 0 8px;
}
.home-category-layer1-actions {
display: flex;
flex-shrink: 0;
gap: 0;
}
/* 搜索浮层:绝对定位,遮挡第二三层,不顶开布局 */
.home-search-overlay {
position: absolute;
top: 100%;
left: 0;
right: 0;
z-index: 11;
padding: 8px 12px 12px;
background-color: white;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
border-top: 1px solid rgba(0, 0, 0, 0.06);
}
.home-search-overlay-inner {
display: flex;
align-items: center;
gap: 8px;
}
.home-search-overlay-field {
flex: 1;
min-width: 0;
font-size: 14px;
}
.home-search-close-btn {
flex-shrink: 0;
}
.home-search-overlay-field :deep(.v-field) {
background-color: #f8fafc;
}
.home-search-overlay-field :deep(.v-field__prepend-inner .v-icon) {
font-size: 22px;
}
.home-search-overlay-enter-active,
.home-search-overlay-leave-active {
transition:
opacity 0.15s ease,
transform 0.15s ease;
}
.home-search-overlay-enter-from,
.home-search-overlay-leave-to {
opacity: 0;
}
.home-search-history {
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid rgba(0, 0, 0, 0.06);
}
.home-search-history-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.home-search-history-title {
font-size: 12px;
color: #64748b;
}
.home-search-history-clear {
min-width: auto;
padding: 0 4px;
font-size: 12px;
}
.home-search-history-list {
list-style: none;
margin: 0;
padding: 0;
max-height: 160px;
overflow-y: auto;
}
.home-search-history-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
}
.home-search-history-item:last-child {
border-bottom: none;
}
.home-search-history-text {
flex: 1;
min-width: 0;
padding: 0;
border: none;
background: none;
font-size: 14px;
color: #1e293b;
text-align: left;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.home-search-history-text:hover {
color: rgb(var(--v-theme-primary));
}
.home-search-history-delete {
flex-shrink: 0;
min-width: 28px;
color: #94a3b8;
}
.home-search-history-delete:hover {
color: #ef4444;
}
/* 第二三层:随内容滚动,全宽 */ /* 第二三层:随内容滚动,全宽 */
.home-category-layers-23-scroll { .home-category-layers-23-scroll {
@ -1060,11 +787,9 @@ onActivated(() => {
} }
} }
/* 页面布局flex 列 */
.home-page { .home-page {
margin-top: 112px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 100vh; height: 100%;
} }
</style> </style>