Compare commits
No commits in common. "f83f0100e0c9ba44298859c97efe00298424b6f5" and "8d103e2d989569f84e40724d34e8c55ae8c367d1" have entirely different histories.
f83f0100e0
...
8d103e2d98
@ -1,110 +0,0 @@
|
||||
import { get } from './request'
|
||||
|
||||
/** 分类树节点(与后端返回结构一致) */
|
||||
export interface CategoryTreeNode {
|
||||
id: string
|
||||
label: string
|
||||
slug: string
|
||||
/** 第二层专用:MDI 图标名,如 mdi-view-grid-outline */
|
||||
icon?: string
|
||||
/** 第三层展示时的区块标题,如「加密货币」 */
|
||||
sectionTitle?: string
|
||||
forceShow?: boolean
|
||||
forceHide?: boolean
|
||||
publishedAt?: string
|
||||
updatedBy?: number
|
||||
createdAt?: string
|
||||
updatedAt?: string
|
||||
children?: CategoryTreeNode[]
|
||||
}
|
||||
|
||||
/** 模拟分类数据:一层(财经)、二层(体育/加密带图标)、三层(政治) */
|
||||
export const MOCK_CATEGORY_TREE: CategoryTreeNode[] = [
|
||||
{
|
||||
id: '1',
|
||||
label: '政治',
|
||||
slug: 'politics',
|
||||
children: [
|
||||
{
|
||||
id: '11',
|
||||
label: '政治1',
|
||||
slug: 'politics1',
|
||||
children: [
|
||||
{ id: '111', label: '政治1-A', slug: 'politics1a', children: [] },
|
||||
{ id: '112', label: '政治1-B', slug: 'politics1b', children: [] },
|
||||
],
|
||||
},
|
||||
{ id: '12', label: '政治2', slug: 'politics2', children: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
label: '体育',
|
||||
slug: 'sports',
|
||||
children: [
|
||||
{ id: '21', label: '足球', slug: 'football', children: [] },
|
||||
{ id: '22', label: '篮球', slug: 'basketball', children: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
label: '加密',
|
||||
slug: 'crypto',
|
||||
sectionTitle: '加密货币',
|
||||
children: [
|
||||
{
|
||||
id: '31',
|
||||
label: '全部',
|
||||
slug: 'all',
|
||||
icon: 'mdi-view-grid-outline',
|
||||
children: [
|
||||
{ id: '311', label: '全部', slug: 'all', children: [] },
|
||||
{ id: '312', label: 'Above / Below', slug: 'above-below', children: [] },
|
||||
{ id: '313', label: 'Up / Down', slug: 'up-down', children: [] },
|
||||
{ id: '314', label: 'BTC', slug: 'btc', children: [] },
|
||||
{ id: '315', label: 'ETH', slug: 'eth', children: [] },
|
||||
],
|
||||
},
|
||||
{ id: '32', label: '5分钟', slug: '5m', icon: 'mdi-format-list-bulleted', children: [] },
|
||||
{ id: '33', label: '15分钟', slug: '15m', icon: 'mdi-clock-outline', children: [] },
|
||||
{ id: '34', label: '每小时', slug: '1h', icon: 'mdi-refresh', children: [] },
|
||||
{ id: '35', label: '4小时', slug: '4h', icon: 'mdi-clock-outline', children: [] },
|
||||
{ id: '36', label: '每天', slug: '1d', icon: 'mdi-calendar', children: [] },
|
||||
],
|
||||
},
|
||||
{ id: '4', label: '财务', slug: 'finance', children: [] },
|
||||
{ id: '5', label: '地缘政治', slug: 'geopolitics', children: [] },
|
||||
{ id: '0', label: '最新', slug: 'latest', children: [] },
|
||||
]
|
||||
|
||||
/** 分类树接口响应 */
|
||||
export interface CategoryTreeResponse {
|
||||
code: number
|
||||
data: CategoryTreeNode[]
|
||||
msg: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分类树(公开接口,不需要鉴权)
|
||||
* GET /PmTag/getPmTagPublic
|
||||
*
|
||||
* 返回带 children 的树形结构,最多三层
|
||||
* data 可能为数组,或 { list: [] } 等格式,统一转为 CategoryTreeNode[]
|
||||
*/
|
||||
export async function getCategoryTree(): Promise<CategoryTreeResponse> {
|
||||
const res = await get<{ code: number; data: CategoryTreeNode[] | { list?: CategoryTreeNode[] }; msg: string }>(
|
||||
'/PmTag/getPmTagPublic'
|
||||
)
|
||||
let data: CategoryTreeNode[] = []
|
||||
const raw = res.data
|
||||
if (Array.isArray(raw)) {
|
||||
data = raw
|
||||
} else if (raw && typeof raw === 'object') {
|
||||
if (Array.isArray((raw as { list?: CategoryTreeNode[] }).list)) {
|
||||
data = (raw as { list: CategoryTreeNode[] }).list
|
||||
} else if ((raw as CategoryTreeNode).id && (raw as CategoryTreeNode).label) {
|
||||
data = [raw as CategoryTreeNode]
|
||||
}
|
||||
}
|
||||
return { code: res.code, data, msg: res.msg }
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
import { ref, readonly } from 'vue'
|
||||
|
||||
const STORAGE_KEY = 'polyclient_search_history'
|
||||
const MAX_HISTORY = 10
|
||||
|
||||
const history = ref<string[]>([])
|
||||
|
||||
function loadHistory() {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY)
|
||||
history.value = raw ? JSON.parse(raw) : []
|
||||
} catch {
|
||||
history.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function saveHistory() {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(history.value))
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
export function useSearchHistory() {
|
||||
const list = readonly(history)
|
||||
|
||||
function add(keyword: string) {
|
||||
const k = keyword.trim()
|
||||
if (!k) return
|
||||
history.value = [k, ...history.value.filter((h) => h !== k)].slice(0, MAX_HISTORY)
|
||||
saveHistory()
|
||||
}
|
||||
|
||||
function remove(index: number) {
|
||||
history.value = history.value.filter((_, i) => i !== index)
|
||||
saveHistory()
|
||||
}
|
||||
|
||||
function clearAll() {
|
||||
history.value = []
|
||||
saveHistory()
|
||||
}
|
||||
|
||||
loadHistory()
|
||||
|
||||
return { list, add, remove, clearAll }
|
||||
}
|
||||
@ -1,122 +1,13 @@
|
||||
<template>
|
||||
<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">
|
||||
{{ item.label }}
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
<div class="home-category-layer1-actions">
|
||||
<v-btn
|
||||
icon
|
||||
variant="text"
|
||||
size="small"
|
||||
class="home-category-action-btn"
|
||||
aria-label="搜索"
|
||||
@click="expandSearch"
|
||||
>
|
||||
<v-icon size="20">mdi-magnify</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon variant="text" size="small" class="home-category-action-btn" aria-label="筛选">
|
||||
<v-icon size="20">mdi-filter-outline</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</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="Search"
|
||||
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="收起" @click="collapseSearch">
|
||||
<v-icon size="18">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">搜索历史</span>
|
||||
<v-btn
|
||||
variant="text"
|
||||
size="x-small"
|
||||
color="primary"
|
||||
class="home-search-history-clear"
|
||||
@click="searchHistory.clearAll"
|
||||
>
|
||||
清空
|
||||
</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="删除"
|
||||
@click.stop="searchHistory.remove(idx)"
|
||||
>
|
||||
<v-icon size="16">mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
<v-container fluid class="home-container">
|
||||
<!-- 第二三层:随内容滚动,回顶部时与列表第一行一起出现 -->
|
||||
<div v-if="categoryLayers.length >= 2" class="home-category-layers-23-scroll">
|
||||
<div class="home-category-layer home-category-layer--icon">
|
||||
<div class="home-category-icon-row">
|
||||
<button
|
||||
v-for="item in categoryLayers[1]"
|
||||
:key="item.id"
|
||||
type="button"
|
||||
class="home-category-icon-item"
|
||||
:class="{ 'home-category-icon-item--active': layerActiveValues[1] === item.id }"
|
||||
@click="onCategorySelect(1, item.id)"
|
||||
>
|
||||
<v-icon v-if="item.icon" size="24" class="home-category-icon">{{ item.icon }}</v-icon>
|
||||
<span v-else class="home-category-icon-placeholder" aria-hidden="true" />
|
||||
<span class="home-category-icon-label">{{ item.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<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"
|
||||
@update:model-value="onCategorySelect(2, $event)"
|
||||
>
|
||||
<v-tab v-for="item in categoryLayers[2]" :key="item.id" :value="item.id">
|
||||
{{ item.label }}
|
||||
</v-tab>
|
||||
<v-row justify="center" align="center" class="home-tabs">
|
||||
<v-tabs v-model="activeTab" class="home-tab-bar">
|
||||
<v-tab value="overview">Market Overview</v-tab>
|
||||
<v-tab value="trending">Trending</v-tab>
|
||||
<v-tab value="portfolio">Portfolio</v-tab>
|
||||
</v-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</v-row>
|
||||
<!-- 可滚动容器作为 v-pull-to-refresh 的父元素,组件据此判断 scrollTop 仅在顶部时才响应下拉 -->
|
||||
<div ref="scrollRef" class="home-list-scroll">
|
||||
<v-pull-to-refresh class="pull-to-refresh" @load="onRefresh">
|
||||
@ -287,139 +178,11 @@ import {
|
||||
clearEventListCache,
|
||||
type EventCardItem,
|
||||
} from '../api/event'
|
||||
import { getCategoryTree, MOCK_CATEGORY_TREE, type CategoryTreeNode } from '../api/category'
|
||||
import { useSearchHistory } from '../composables/useSearchHistory'
|
||||
|
||||
const { mobile } = useDisplay()
|
||||
const searchHistory = useSearchHistory()
|
||||
const searchHistoryList = computed(() => searchHistory.list.value)
|
||||
const isMobile = computed(() => mobile.value)
|
||||
|
||||
/** 分类树(顶层) */
|
||||
const categoryTree = ref<CategoryTreeNode[]>([])
|
||||
/** 每层选中的 id,[layer0, layer1?, layer2?] */
|
||||
const layerActiveValues = ref<string[]>([])
|
||||
/** 第三层搜索框是否展开 */
|
||||
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) {
|
||||
activeSearchKeyword.value = keyword
|
||||
clearEventListCache()
|
||||
eventPage.value = 1
|
||||
loadEvents(1, false, keyword)
|
||||
}
|
||||
|
||||
/** 过滤 forceHide 的节点 */
|
||||
function filterVisible(nodes: CategoryTreeNode[] | undefined): CategoryTreeNode[] {
|
||||
if (!nodes?.length) return []
|
||||
return nodes.filter((n) => !n.forceHide)
|
||||
}
|
||||
|
||||
/** 第三层区块标题:取当前选中的第一层节点的 sectionTitle 或 label */
|
||||
const selectedLayer0SectionTitle = computed(() => {
|
||||
const root = filterVisible(categoryTree.value)
|
||||
const id = layerActiveValues.value[0]
|
||||
const node = id ? root.find((n) => n.id === id) : root[0]
|
||||
return node?.sectionTitle ?? node?.label ?? ''
|
||||
})
|
||||
|
||||
/** 当前展示的层级数据:[[layer0], [layer1]?, [layer2]?] */
|
||||
const categoryLayers = computed(() => {
|
||||
const root = filterVisible(categoryTree.value)
|
||||
if (root.length === 0) return []
|
||||
|
||||
const layers: CategoryTreeNode[][] = [root]
|
||||
const active = layerActiveValues.value
|
||||
|
||||
let currentNodes = root
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const selectedId = active[i]
|
||||
const node = selectedId ? currentNodes.find((n) => n.id === selectedId) : currentNodes[0]
|
||||
const children = filterVisible(node?.children)
|
||||
if (children.length === 0) break
|
||||
layers.push(children)
|
||||
currentNodes = children
|
||||
}
|
||||
return layers
|
||||
})
|
||||
|
||||
/** 分类选中时:若有 children 则展开下一层并默认选中第一个 */
|
||||
function onCategorySelect(layerIndex: number, selectedId: string) {
|
||||
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
|
||||
}
|
||||
|
||||
/** 初始化分类选中:默认选中第一个,若有 children 则递归展开 */
|
||||
function initCategorySelection() {
|
||||
const root = filterVisible(categoryTree.value)
|
||||
if (root.length === 0) return
|
||||
|
||||
const values: string[] = []
|
||||
let current = root
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const first = current[0]
|
||||
if (!first) break
|
||||
values.push(first.id)
|
||||
const children = filterVisible(first.children)
|
||||
if (children.length === 0) break
|
||||
current = children
|
||||
}
|
||||
layerActiveValues.value = values
|
||||
}
|
||||
const activeTab = ref('overview')
|
||||
|
||||
const PAGE_SIZE = 10
|
||||
|
||||
@ -485,15 +248,11 @@ function updateGridColumns() {
|
||||
gridColumns.value = Math.max(1, n)
|
||||
}
|
||||
|
||||
/** 当前生效的搜索关键词(用于分页加载) */
|
||||
const activeSearchKeyword = ref('')
|
||||
|
||||
/** 请求事件列表并追加或覆盖到 eventList(公开接口,无需鉴权);成功后会更新内存缓存 */
|
||||
async function loadEvents(page: number, append: boolean, keyword?: string) {
|
||||
const kw = keyword !== undefined ? keyword : activeSearchKeyword.value
|
||||
async function loadEvents(page: number, append: boolean) {
|
||||
try {
|
||||
const res = await getPmEventPublic(
|
||||
{ page, pageSize: PAGE_SIZE, keyword: kw || undefined }
|
||||
{ page, pageSize: PAGE_SIZE }
|
||||
)
|
||||
if (res.code !== 0 && res.code !== 200) {
|
||||
throw new Error(res.msg || '请求失败')
|
||||
@ -527,7 +286,7 @@ async function loadEvents(page: number, append: boolean, keyword?: string) {
|
||||
function onRefresh({ done }: { done: () => void }) {
|
||||
clearEventListCache()
|
||||
eventPage.value = 1
|
||||
loadEvents(1, false, activeSearchKeyword.value).finally(() => done())
|
||||
loadEvents(1, false).finally(() => done())
|
||||
}
|
||||
|
||||
function loadMore() {
|
||||
@ -547,28 +306,6 @@ function checkScrollLoad() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
/** 开发时设为 true 可始终使用模拟数据查看一二三层 UI */
|
||||
const USE_MOCK_CATEGORY = true
|
||||
if (USE_MOCK_CATEGORY) {
|
||||
categoryTree.value = MOCK_CATEGORY_TREE
|
||||
initCategorySelection()
|
||||
} else {
|
||||
getCategoryTree()
|
||||
.then((res) => {
|
||||
if (res.code === 0 || res.code === 200) {
|
||||
const data = res.data
|
||||
categoryTree.value = Array.isArray(data) && data.length > 0 ? data : MOCK_CATEGORY_TREE
|
||||
} else {
|
||||
categoryTree.value = MOCK_CATEGORY_TREE
|
||||
}
|
||||
initCategorySelection()
|
||||
})
|
||||
.catch(() => {
|
||||
categoryTree.value = MOCK_CATEGORY_TREE
|
||||
initCategorySelection()
|
||||
})
|
||||
}
|
||||
|
||||
const cached = getEventListCache()
|
||||
if (cached && cached.list.length > 0) {
|
||||
eventList.value = cached.list
|
||||
@ -730,261 +467,16 @@ onUnmounted(() => {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
/* 第一层:置顶、全宽 */
|
||||
.home-category-layer1-wrap {
|
||||
width: 100vw;
|
||||
max-width: 100vw;
|
||||
margin-left: calc(50% - 50vw);
|
||||
position: sticky;
|
||||
top: 64px;
|
||||
z-index: 10;
|
||||
background-color: white;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.home-category-layer1-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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-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 {
|
||||
width: 100vw;
|
||||
max-width: 100vw;
|
||||
margin-left: calc(50% - 50vw);
|
||||
margin-bottom: 0;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.home-category-layer {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.home-category-layer:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.home-category-layer--text :deep(.v-tabs) {
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
.home-category-layer--icon {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.home-category-icon-row {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 8px;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 4px;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.home-category-icon-row::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.home-category-icon-item {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-width: 56px;
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.home-category-icon-item:hover {
|
||||
background-color: rgba(0, 0, 0, 0.04);
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.home-category-icon-item--active {
|
||||
background-color: rgb(var(--v-theme-primary));
|
||||
color: rgb(var(--v-theme-on-primary));
|
||||
}
|
||||
|
||||
.home-category-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.home-category-icon-placeholder {
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.home-category-icon-label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.home-category-layer--third {
|
||||
padding: 12px 16px 0;
|
||||
}
|
||||
|
||||
.home-category-action-btn {
|
||||
min-width: 36px;
|
||||
}
|
||||
|
||||
.home-tab-bar {
|
||||
position: relative;
|
||||
top: auto;
|
||||
left: auto;
|
||||
position: fixed;
|
||||
top: 64px; /* Adjust based on app bar height */
|
||||
left: 0;
|
||||
transform: none;
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
margin-bottom: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.home-tab-bar--compact :deep(.v-tab) {
|
||||
min-height: 40px;
|
||||
font-size: 14px;
|
||||
z-index: 10;
|
||||
background-color: white;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.home-card {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user