index.vue 12.9 KB
<!--
 * @Date: 2022-09-19 14:11:06
 * @LastEditors: hookehuyr hookehuyr@gmail.com
 * @LastEditTime: 2025-07-11 10:45:41
 * @FilePath: /jgdl/src/pages/goodCarList/index.vue
 * @Description: 特价好车页面
-->
<template>
    <view>
        <view class="flex flex-col bg-white min-h-screen">
            <!-- Header -->
            <nut-sticky>
                <view class="bg-orange-400 p-4 pt-4 pb-4">
                    <nut-row type="flex" justify="center" align="center">
                        <nut-col span="6">
                            <view class="text-xl font-bold text-white">特价好车</view>
                        </nut-col>
                        <nut-col span="18">
                            <!-- Search Bar -->
                            <nut-searchbar v-model="searchValue" placeholder="搜索商品名称、品牌、型号" @blur="onBlurSearch" shape="round" background="transparent" input-background="#ffffff">
                                <template #leftin>
                                    <Search2 />
                                </template>
                            </nut-searchbar>
                        </nut-col>
                    </nut-row>
                </view>

                <!-- Filter options -->
                <nut-menu>
                    <nut-menu-item v-model="selectedBrand" :options="brandOptions" @change="onBrandChange" />
                    <nut-menu-item v-model="selectedYear" :options="yearOptions" @change="onYearChange" />
                    <nut-menu-item v-model="selectedSchool" :options="schoolOptions" @change="onSchoolChange" />
                </nut-menu>
            </nut-sticky>

            <!-- 特价好车列表 -->
            <view class="flex-1 p-4">
                <!-- 滚动列表 -->
                <scroll-view
                    class="good-car-list"
                    :style="scrollStyle"
                    :scroll-y="true"
                    @scrolltolower="loadMore"
                    @scroll="scroll"
                    :lower-threshold="50"
                    :enable-flex="false"
                >
                    <view class="space-y-4">
                        <view v-for="car in goodCars" :key="car.id"
                            class="bg-white rounded-lg shadow-sm overflow-hidden mb-3"
                            @tap="() => onCarClick(car)"
                        >
                            <view class="flex">
                                <view class="w-32 h-24 relative p-2">
                                    <image :src="car.front_photo" :alt="car.name" mode="aspectFill"
                                        class="w-full h-full object-cover rounded-lg" />
                                    <view v-if="car.isSpecial"
                                        class="absolute bottom-3 right-3 bg-red-500 text-white text-xs px-1 rounded flex items-center">
                                        <text class="text-white">特</text>
                                    </view>
                                    <!-- 折扣标签 -->
                                    <!-- <view v-if="car.discount"
                                        class="absolute top-3 left-3 bg-red-500 text-white text-xs px-1 rounded">
                                        <text class="text-white">{{ car.discount }}折</text>
                                    </view> -->
                                </view>
                                <view class="flex-1 p-3 relative">
                                    <view class="absolute top-3 right-4" @tap.stop="() => toggleFavorite(car)">
                                        <Heart1 v-if="!car.is_favorite" size="22" color="#9ca3af" />
                                        <HeartFill v-else size="22" color="#ef4444" />
                                    </view>
                                    <text class="font-medium text-sm block">{{ car.brand }} {{ car.model }}</text>
                                    <text class="text-xs text-gray-600 mt-1 block">
                                        {{ car.manufacture_year }} ·
                                        <text v-if="car.range_km">续航{{ car.range_km }}km</text>
                                        <text v-if="car.max_speed_kmh"> 最高时速{{ car.max_speed_kmh }}km/h</text>
                                    </text>
                                    <view class="mt-2">
                                        <!-- 原价和现价 -->
                                        <view class="flex items-center">
                                            <text v-if="car.market_price" class="text-xs text-gray-400 line-through mr-2">
                                                ¥{{ car.market_price.toLocaleString() }}
                                            </text>
                                            <text class="text-orange-500 font-bold" style="font-size: 1.2rem;">
                                                ¥{{ car.price.toLocaleString() }}
                                            </text>
                                        </view>
                                        <text class="text-xs text-gray-500 mt-1 block">{{ car.school_name }}</text>
                                    </view>
                                    <!-- 特价标签 -->
                                    <!-- <view class="mt-1">
                                        <text class="text-xs text-red-600">{{ car.specialTag }}</text>
                                    </view> -->
                                </view>
                            </view>
                        </view>
                    </view>

                    <!-- Loading indicator -->
                    <view v-if="loading" class="loading-container py-4 text-center">
                        <text class="loading-text text-gray-500">加载中...</text>
                    </view>

                    <!-- 没有更多数据 -->
                    <view v-if="!hasMore && goodCars.length > 0" class="no-more-container py-4 text-center">
                        <text class="text-gray-400 text-sm">没有更多数据了</text>
                    </view>
                </scroll-view>
            </view>
        </view>

        <!-- 自定义TabBar -->
        <!-- <TabBar /> -->

        <!-- 成功提示 -->
        <nut-toast
            v-model:visible="toastVisible"
            :msg="toastMessage"
            :type="toastType"
        />
    </view>
</template>

<script setup>
import Taro  from '@tarojs/taro'
import { ref, computed, onMounted } from 'vue'
import { Search2, Heart1, HeartFill } from '@nutui/icons-vue-taro'
import { useFavorite } from '@/composables/useFavorite'
import './index.less'
// 接口导入
import { getVehicleBrandsAPI, getSchoolsAPI } from '@/api/other';
import { getRecommendVehicleAPI } from '@/api/car';
import { DEFAULT_COVER_IMG } from '@/utils/config'

// 响应式数据
const searchValue = ref('')
/**
 * 搜索框失焦事件
 */
const onBlurSearch = async () => {
    // 重置分页并重新加载数据
    currentPage.value = 0
    hasMore.value = true
    await loadVehicleData()
}
// 收藏功能现在使用基于对象属性的模式

// Filter states - 使用NutUI Menu组件
const selectedBrand = ref('')
const selectedYear = ref('')
const selectedSchool = ref('')

// Menu选项数据
const brandOptions = ref([])

const yearOptions = ref([])

const schoolOptions = ref([])

// 特价好车数据
const goodCars = ref([])

// 加载状态
const loading = ref(false)
const hasMore = ref(true)
const currentPage = ref(0)
const pageSize = ref(10)

// Toast提示
const toastVisible = ref(false)
const toastMessage = ref('')
const toastType = ref('success')

// 滚动样式 - 考虑navbar、filter和TabBar的高度
const scrollStyle = computed(() => {
    return {
        height: 'calc(100vh - 380rpx)' // 减去header、filter和TabBar的高度
    }
})

/**
 * 切换收藏状态
 * @param {string} carId - 车辆ID
 */
// 使用收藏功能composables
const { toggleFavorite } = useFavorite()

/**
 * 点击车辆卡片
 * @param {Object} car - 车辆信息
 */
const onCarClick = (car) => {
    // 跳转到车辆详情页
    Taro.navigateTo({
        url: '/pages/productDetail/index?id=' + car.id
    })
}

// Menu组件事件处理方法
/**
 * 品牌选择变化事件
 * @param {string} value - 选中的品牌值
 */
const onBrandChange = (value) => {
    selectedBrand.value = value
    // 这里可以添加过滤逻辑
    filterCars()
}

/**
 * 年份选择变化事件
 * @param {string} value - 选中的年份值
 */
const onYearChange = (value) => {
    selectedYear.value = value
    // 这里可以添加过滤逻辑
    filterCars()
}

/**
 * 学校选择变化事件
 * @param {string} value - 选中的学校值
 */
const onSchoolChange = (value) => {
    selectedSchool.value = value
    // 这里可以添加过滤逻辑
    filterCars()
}

/**
 * 过滤车辆数据
 */
const filterCars = async () => {
    // 重置数据
    goodCars.value = []
    currentPage.value = 0
    hasMore.value = true

    // 重新加载数据
    await loadVehicleData()
    showToast('筛选条件已更新', 'success')
}

/**
 * 加载车辆数据
 * @param {boolean} isLoadMore - 是否为加载更多
 */
const loadVehicleData = async (isLoadMore = false) => {
    if (loading.value) return

    loading.value = true

    try {
        // 构建请求参数
        const params = {
            section: 2,
            page: currentPage.value,
            limit: pageSize.value
        }

        // 添加筛选条件
        if (selectedSchool.value) {
            params.school_id = selectedSchool.value
        }
        if (selectedBrand.value) {
            params.brand = selectedBrand.value
        }
        if (selectedYear.value) {
            params.manufacture_year = selectedYear.value
        }
        if (searchValue.value.trim()) {
            params.keyword = searchValue.value.trim()
        }

        const response = await getRecommendVehicleAPI(params)

        if (response && response.code === 1 && response.data) {
            const vehicleList = response.data.list || []

            // 处理图片数据
            const processedData = vehicleList.map(item => ({
                ...item,
                front_photo: item.front_photo || DEFAULT_COVER_IMG,
                // 添加特价标签(有市场价且当前价格低于市场价的车辆)
                isSpecial: item.market_price && item.price < item.market_price,
                // 确保价格为数字类型
                price: Number(item.price) || 0,
                market_price: Number(item.market_price) || 0
            }))

            if (isLoadMore) {
                goodCars.value.push(...processedData)
            } else {
                goodCars.value = processedData
            }

            // 检查是否还有更多数据 - 基于总数和当前已加载数量
            const totalLoaded = (currentPage.value + 1) * pageSize.value
            hasMore.value = totalLoaded < response.data.total
        } else {
            console.error('API返回错误:', response)
            showToast(response?.msg || '获取数据失败', 'error')
        }
    } catch (error) {
        console.error('加载车辆数据失败:', error)
        showToast('网络错误,请稍后重试', 'error')
    } finally {
        loading.value = false
    }
}

/**
 * 加载更多数据
 */
const loadMore = async () => {
    if (loading.value || !hasMore.value) return

    currentPage.value++
    await loadVehicleData(true)
}

/**
 * 滚动事件处理
 */
const scroll = (e) => {
    // 可以在这里处理滚动事件
}

/**
 * 显示提示信息
 */
const showToast = (message, type = 'success') => {
    toastMessage.value = message
    toastType.value = type
    toastVisible.value = true
}

// 初始化
onMounted(async () => {
    // 获取全部品牌数据
    const vBrands = await getVehicleBrandsAPI()
    if (vBrands.code) {
        brandOptions.value = vBrands.data.map(brand => ({
            text: brand,
            value: brand
        }))
        brandOptions.value = [{
            text: '全部品牌',
            value: ''
        }, ...brandOptions.value]
    }
    // 生成从当前日期开始往前10年的年份
    yearOptions.value = Array.from({ length: 10 }, (_, i) => ({
        text: (new Date().getFullYear() - i).toString() + '年',
        value: (new Date().getFullYear() - i).toString()
    }))
    yearOptions.value = [{
        text: '全部年份',
        value: ''
    }, ...yearOptions.value]
    // 获取全部学校数据
    const schoolData = await getSchoolsAPI()
    if (schoolData.code) {
        schoolOptions.value = schoolData.data.map(school => ({
            text: school.name,
            value: school.id
        }))
    }
    schoolOptions.value = [{
        text: '全部学校',
        value: ''
    }, ...schoolOptions.value]

    // 加载初始车辆数据
    await loadVehicleData()
})
</script>

<script>
export default {
    name: 'GoodCarListPage'
}
</script>