first commit

This commit is contained in:
2026-02-13 17:36:42 +08:00
commit f067e1bb78
155 changed files with 46676 additions and 0 deletions

View File

@@ -0,0 +1,189 @@
<template>
<view class="category-bar" :style="{ backgroundColor: bgColor }">
<scroll-view
scroll-x
class="category-scroll"
:scroll-left="scrollLeft"
scroll-with-animation
:show-scrollbar="false"
:enhanced="true"
>
<view class="category-list">
<!-- 最热 -->
<view
class="category-item"
:class="{ active: activeType === 'hot' && !activeId }"
@click="handleTypeClick('hot')"
>
<text class="category-text" :style="getTextStyle('hot', null)">最热</text>
</view>
<!-- 最新 -->
<view
class="category-item"
:class="{ active: activeType === 'new' && !activeId }"
@click="handleTypeClick('new')"
>
<text class="category-text" :style="getTextStyle('new', null)">最新</text>
</view>
<!-- 分类列表 -->
<view
v-for="item in categories.filter(cat => cat.id !== 1 && cat.name !== '全部')"
:key="item.id"
class="category-item"
:class="{
active: activeId === item.id
}"
@click="handleCategoryClick(item)"
>
<text class="category-text" :style="getTextStyle(null, item.id)">{{ item.name }}</text>
</view>
<!-- 右侧占位防止被搜索按钮遮挡 -->
<view class="category-placeholder" />
</view>
</scroll-view>
<!-- 搜索区域 -->
<view class="search-area" @click="handleSearch">
<view class="search-gradient" />
<view class="search-btn">
<image class="search-icon" src="/static/icons/search.png" mode="aspectFit" />
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getCategoryList } from '@/api/category'
const props = defineProps({
bgColor: { type: String, default: 'transparent' },
textColor: { type: String, default: 'rgba(241, 245, 249, 1)' },
activeColor: { type: String, default: '#ffffff' }
})
const emit = defineEmits(['change', 'search'])
const categories = ref([])
const activeId = ref(null)
const activeType = ref('hot')
const scrollLeft = ref(0)
const getTextStyle = (type, id) => {
// 其他分类的激活判断
const isActive = (type && activeType.value === type && !activeId.value) ||
(id && activeId.value === id)
return {
color: isActive ? props.activeColor : props.textColor,
fontWeight: isActive ? '600' : '400'
}
}
const handleTypeClick = (type) => {
activeType.value = type
activeId.value = null
emit('change', { type, categoryId: null })
}
const handleCategoryClick = (item) => {
activeId.value = item.id
activeType.value = null
emit('change', { type: null, categoryId: item.id })
}
const handleSearch = () => {
emit('search')
}
const fetchCategories = async () => {
try {
const res = await getCategoryList()
console.log('分类数据:', res)
// request已处理成功时直接返回data.data
categories.value = res || []
console.log('categories.value:', categories.value)
} catch (e) {
console.error('获取分类失败', e)
}
}
onMounted(() => {
fetchCategories()
})
</script>
<style scoped>
.category-bar {
width: 100%;
padding: 12px 0;
position: relative;
}
.category-scroll {
width: 100%;
white-space: nowrap;
}
/* 隐藏滚动条 - 小程序兼容写法 */
.category-scroll {
scrollbar-width: none;
-ms-overflow-style: none;
}
.category-list {
display: inline-flex;
padding: 0 16px;
gap: 24px;
}
.category-item {
flex-shrink: 0;
padding: 4px 0;
}
.category-text {
font-size: 15px;
}
.category-item.active .category-text {
font-weight: 600;
}
.category-placeholder {
width: 50px;
flex-shrink: 0;
}
.search-area {
position: absolute;
right: 0;
top: 0;
bottom: 0;
display: flex;
align-items: center;
padding-right: 16px;
padding-left: 16px;
background: #09090b;
}
.search-gradient {
position: absolute;
left: -30px;
top: 0;
bottom: 0;
width: 30px;
background: linear-gradient(to right, transparent, #09090b);
}
.search-btn {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
}
.search-icon {
width: 22px;
height: 22px;
}
</style>