hookehuyr

feat: 新增特价好车和最新上架页面功能

添加特价好车和最新上架两个新页面,包含以下主要变更:
1. 新增页面路由配置
2. 实现车辆列表展示、筛选和收藏功能
3. 优化页面样式和交互体验
4. 添加页面间导航逻辑
/*
* @Date: 2025-06-28 10:33:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-02 18:16:09
* @LastEditTime: 2025-07-02 22:18:10
* @FilePath: /jgdl/src/app.config.js
* @Description: 文件描述
*/
......@@ -16,6 +16,8 @@ export default {
'pages/register/index',
'pages/authCar/index',
'pages/setAuthCar/index',
'pages/newCarList/index',
'pages/goodCarList/index',
'pages/auth/index',
],
subpackages: [ // 配置在tabBar中的页面不能分包写到subpackages中去
......
/*
* @Date: 2025-07-02 22:18:16
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-02 23:00:27
* @FilePath: /jgdl/src/pages/goodCarList/index.config.js
* @Description: 文件描述
*/
export default {
navigationBarTitleText: '',
usingComponents: {
},
}
/* 特价好车页面样式 */
.good-car-list {
width: 100%;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
/* 车辆卡片样式 */
.good-car-list .bg-white {
background-color: #ffffff;
border: 1px solid #f0f0f0;
transition: all 0.3s ease;
}
.good-car-list .bg-white:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
/* 图片容器 */
.good-car-list .w-32 {
width: 200rpx;
height: 150rpx;
flex-shrink: 0;
}
.good-car-list image {
border-radius: 8rpx;
object-fit: cover;
}
/* 信息区域 */
.good-car-list .flex-1 {
flex: 1;
min-width: 0;
}
/* 价格样式 */
.good-car-list .text-orange-500 {
color: #f97316;
font-weight: bold;
}
.good-car-list .line-through {
text-decoration: line-through;
}
/* 特价标签 */
.good-car-list .text-red-600 {
color: #dc2626;
font-weight: 500;
}
/* 折扣标签 */
.good-car-list .bg-red-500 {
background-color: #ef4444;
padding: 2rpx 6rpx;
border-radius: 4rpx;
font-size: 20rpx;
line-height: 1;
}
/* 加载状态 */
.loading-container {
display: flex;
justify-content: center;
align-items: center;
padding: 32rpx 0 100rpx 0;
}
.loading-text {
color: #9ca3af;
font-size: 28rpx;
}
/* 无更多数据提示 */
.no-more-container {
display: flex;
justify-content: center;
align-items: center;
padding: 32rpx 0 100rpx 0;
}
.no-more-container text {
color: #9ca3af;
font-size: 24rpx;
}
/* NutUI 组件样式覆盖 */
:deep(.nut-menu) {
background-color: #ffffff;
border-bottom: 1px solid #f0f0f0;
}
:deep(.nut-menu-item) {
color: #333333;
font-size: 28rpx;
}
:deep(.nut-menu-item.active) {
color: #f97316;
}
// .nut-searchbar{
// background: transparent !important;
// }
// :deep(.nut-searchbar .nut-searchbar__input-inner) {
// background-color: #ffffff;
// border-radius: 20rpx;
// font-size: 28rpx;
// }
:deep(.nut-sticky) {
z-index: 999;
}
/* 响应式适配 */
@media screen and (max-width: 750rpx) {
.good-car-list .w-32 {
width: 180rpx;
height: 135rpx;
}
.good-car-list .text-xl {
font-size: 32rpx;
}
.good-car-list .text-sm {
font-size: 24rpx;
}
.good-car-list .text-xs {
font-size: 20rpx;
}
}
/* 深色模式适配 */
@media (prefers-color-scheme: dark) {
.good-car-list .bg-white {
background-color: #1f2937;
border-color: #374151;
}
.good-car-list .text-gray-600 {
color: #9ca3af;
}
.good-car-list .text-gray-500 {
color: #6b7280;
}
.good-car-list .text-gray-400 {
color: #9ca3af;
}
}
/* 动画效果 */
.good-car-list .bg-white {
animation: fadeInUp 0.3s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 收藏按钮动画 */
.good-car-list .absolute {
transition: transform 0.2s ease;
}
.good-car-list .absolute:active {
transform: scale(0.9);
}
/* 卡片点击效果 */
.good-car-list .bg-white:active {
transform: scale(0.98);
transition: transform 0.1s ease;
}
/* 滚动条样式 */
.good-car-list::-webkit-scrollbar {
width: 0;
background: transparent;
}
/* 特价标识样式优化 */
.good-car-list .absolute.bottom-3.right-3 {
background: linear-gradient(45deg, #ef4444, #dc2626);
box-shadow: 0 2rpx 4rpx rgba(239, 68, 68, 0.3);
}
.good-car-list .absolute.top-3.left-3 {
background: linear-gradient(45deg, #ef4444, #dc2626);
box-shadow: 0 2rpx 4rpx rgba(239, 68, 68, 0.3);
}
/* 价格区域样式优化 */
.good-car-list .flex.items-center {
align-items: baseline;
gap: 8rpx;
}
/* 文字省略 */
.good-car-list .font-medium {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100%;
}
/* 间距调整 */
.good-car-list .space-y-4 > * + * {
margin-top: 16rpx;
}
.good-car-list .mb-3 {
margin-bottom: 12rpx;
}
<!--
* @Date: 2022-09-19 14:11:06
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-02 22:49:42
* @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="搜索品牌型号" 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.imageUrl" :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.id)">
<Addfollow v-if="!favoriteIds.includes(car.id)" size="16" color="#9ca3af" />
<HeartFill v-else size="16" color="#ef4444" />
</view>
<text class="font-medium text-sm block">{{ car.name }}</text>
<text class="text-xs text-gray-600 mt-1 block">
{{ car.year }} ·
<text v-if="car.batteryHealth">电池健康度{{ car.batteryHealth }}%</text>
<text v-if="car.mileage"> 行驶{{ car.mileage }}公里</text>
</text>
<view class="mt-2">
<!-- 原价和现价 -->
<view class="flex items-center">
<text v-if="car.originalPrice" class="text-xs text-gray-400 line-through mr-2">
¥{{ car.originalPrice.toLocaleString() }}
</text>
<text class="text-orange-500 font-bold">
¥{{ car.price.toLocaleString() }}
</text>
</view>
<text class="text-xs text-gray-500 mt-1 block">{{ car.school }}</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 { ref, computed, onMounted } from 'vue'
import { Search2, Addfollow, HeartFill } from '@nutui/icons-vue-taro'
import TabBar from '@/components/TabBar.vue'
import './index.less'
// 响应式数据
const searchValue = ref('')
const favoriteIds = ref(['2', '4', '6'])
// Filter states - 使用NutUI Menu组件
const selectedBrand = ref('全部品牌')
const selectedYear = ref('出厂年份')
const selectedSchool = ref('所在学校')
// Menu选项数据
const brandOptions = ref([
{ text: '全部品牌', value: '全部品牌' },
{ text: '雅迪', value: '雅迪' },
{ text: '台铃', value: '台铃' },
{ text: '小鸟', value: '小鸟' },
{ text: '新日', value: '新日' },
{ text: '爱玛', value: '爱玛' },
{ text: '小牛', value: '小牛' }
])
const yearOptions = ref([
{ text: '出厂年份', value: '出厂年份' },
{ text: '2024年', value: '2024年' },
{ text: '2023年', value: '2023年' },
{ text: '2022年', value: '2022年' },
{ text: '2021年', value: '2021年' },
{ text: '2020年', value: '2020年' }
])
const schoolOptions = ref([
{ text: '所在学校', value: '所在学校' },
{ text: '上海理工大学', value: '上海理工大学' },
{ text: '上海复旦大学', value: '上海复旦大学' },
{ text: '上海同济大学', value: '上海同济大学' },
{ text: '上海交通大学', value: '上海交通大学' }
])
// 特价好车数据
const goodCars = ref([
{
id: 1,
name: '雅迪 DE3 电动车',
year: '2023年',
batteryHealth: 85,
mileage: 1200,
originalPrice: 4800,
price: 3600,
discount: 7.5,
school: '上海理工大学',
imageUrl: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400&h=300&fit=crop',
specialTag: '限时特价',
isSpecial: true,
brand: '雅迪'
},
{
id: 2,
name: '爱玛 A600 电动车',
year: '2022年',
batteryHealth: 80,
mileage: 2000,
originalPrice: 3800,
price: 2850,
discount: 7.5,
school: '上海大学',
imageUrl: 'https://images.unsplash.com/photo-1571068316344-75bc76f77890?w=400&h=300&fit=crop',
specialTag: '急售特价',
isSpecial: true,
brand: '爱玛'
},
{
id: 3,
name: '台铃 TDR-2023 电动车',
year: '2023年',
batteryHealth: 88,
mileage: 800,
originalPrice: 4200,
price: 3360,
discount: 8.0,
school: '华东理工大学',
imageUrl: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400&h=300&fit=crop',
specialTag: '毕业甩卖',
isSpecial: true,
brand: '台铃'
},
{
id: 4,
name: '小牛 NGT 电动车',
year: '2022年',
batteryHealth: 75,
mileage: 3000,
originalPrice: 5200,
price: 3640,
discount: 7.0,
school: '上海交通大学',
imageUrl: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400&h=300&fit=crop',
specialTag: '超值特价',
isSpecial: true,
brand: '小牛'
}
])
// 加载状态
const loading = ref(false)
const hasMore = ref(true)
const currentPage = ref(1)
const pageSize = ref(4)
// 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
*/
const toggleFavorite = (carId) => {
const index = favoriteIds.value.indexOf(carId.toString())
if (index > -1) {
favoriteIds.value.splice(index, 1)
showToast('取消收藏', 'success')
} else {
favoriteIds.value.push(carId.toString())
showToast('收藏成功', 'success')
}
}
/**
* 点击车辆卡片
* @param {Object} car - 车辆信息
*/
const onCarClick = (car) => {
// TODO: 跳转到车辆详情页
showToast(`查看${car.name}详情`, 'success')
}
// 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 = () => {
// TODO: 实现过滤逻辑
showToast('筛选条件已更新', 'success')
}
/**
* 生成模拟车辆数据
* @param {number} page - 页码
* @param {number} size - 每页数量
* @returns {Array} 车辆数据数组
*/
const generateMockData = (page, size) => {
const brands = ['雅迪', '台铃', '小鸟', '新日', '爱玛', '小牛', '绿源', '立马']
const schools = ['上海理工大学', '上海复旦大学', '上海同济大学', '上海交通大学', '华东师范大学', '上海大学']
const years = ['2024年', '2023年', '2022年', '2021年', '2020年']
const images = [
'https://images.unsplash.com/photo-1567922045116-2a00fae2ed03?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
'https://images.unsplash.com/photo-1573981368236-719bbb6f70f7?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
'https://images.unsplash.com/photo-1583568671741-c70dafa8e8e7?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
'https://images.unsplash.com/photo-1595941069915-4ebc5197c14a?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
'https://images.unsplash.com/photo-1558981285-6f0c94958bb6?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
'https://images.unsplash.com/photo-1558981403-c5f9899a28bc?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60'
]
const specialTags = ['限时特价', '急售特价', '毕业甩卖', '超值特价', '清仓特价', '年底促销']
const data = []
for (let i = 0; i < size; i++) {
const index = (page - 1) * size + i
const brand = brands[Math.floor(Math.random() * brands.length)]
const school = schools[Math.floor(Math.random() * schools.length)]
const year = years[Math.floor(Math.random() * years.length)]
const image = images[Math.floor(Math.random() * images.length)]
const specialTag = specialTags[Math.floor(Math.random() * specialTags.length)]
const originalPrice = Math.floor(Math.random() * 2000) + 3000
const discount = (Math.floor(Math.random() * 30) + 60) / 10 // 6.0-8.9折
const price = Math.floor(originalPrice * discount / 10)
data.push({
id: `good_${index + 100}`,
name: `${brand} ${['豪华版', '标准版', '运动版', '经典版'][Math.floor(Math.random() * 4)]}`,
year: year,
school: school,
originalPrice: originalPrice,
price: price,
discount: discount,
imageUrl: image,
batteryHealth: Math.floor(Math.random() * 30) + 70, // 特价车电池健康度相对较低
mileage: Math.floor(Math.random() * 3000) + 1000, // 特价车里程相对较高
brand: brand,
specialTag: specialTag,
isSpecial: true
})
}
return data
}
/**
* 加载更多数据
*/
const loadMore = () => {
if (loading.value || !hasMore.value) return
loading.value = true
// 模拟网络请求延迟
setTimeout(() => {
// 模拟最多加载5页数据
if (currentPage.value >= 5) {
hasMore.value = false
loading.value = false
return
}
currentPage.value++
const newData = generateMockData(currentPage.value, pageSize.value)
goodCars.value.push(...newData)
loading.value = false
}, 1000 + Math.random() * 1000)
}
/**
* 滚动事件处理
*/
const scroll = (e) => {
// 可以在这里处理滚动事件
}
/**
* 显示提示信息
*/
const showToast = (message, type = 'success') => {
toastMessage.value = message
toastType.value = type
toastVisible.value = true
}
// 初始化
onMounted(() => {
// 可以在这里加载初始数据
})
</script>
<script>
export default {
name: 'GoodCarListPage'
}
</script>
<!--
* @Date: 2025-06-28 10:33:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-02 20:49:05
* @LastEditTime: 2025-07-02 22:58:32
* @FilePath: /jgdl/src/pages/index/index.vue
* @Description: 捡个电驴首页
-->
......@@ -40,13 +40,13 @@
<!-- Category Icons -->
<view class="px-4 mt-2">
<view class="flex justify-around py-4">
<view class="flex flex-col items-center">
<view class="flex flex-col items-center" @tap="onNewCarClick">
<view class="w-12 h-12 rounded-full bg-orange-100 flex items-center justify-center">
<Clock size="20" color="#f97316" />
</view>
<text class="text-xs mt-1 text-gray-700">最新上架</text>
</view>
<view class="flex flex-col items-center">
<view class="flex flex-col items-center" @tap="onGoodCarClick">
<view class="w-12 h-12 rounded-full bg-orange-100 flex items-center justify-center">
<Star size="20" color="#f97316" />
</view>
......@@ -111,7 +111,7 @@
<view class="px-4 mt-6 mb-20">
<view class="flex justify-between items-center mb-2">
<text class="text-lg font-medium">最新上架</text>
<view class="text-sm text-gray-500 flex items-center">
<view class="text-sm text-gray-500 flex items-center" @tap="onNewCarClick">
<text>更多</text>
<RectRight size="12" />
</view>
......@@ -295,6 +295,21 @@ const onCertifiedClick = () => {
})
}
/**
* 点击特价好车
*/
const onGoodCarClick = () => {
Taro.navigateTo({
url: '/pages/goodCarList/index'
})
}
const onNewCarClick = () => {
Taro.navigateTo({
url: '/pages/newCarList/index'
})
}
// 生命周期钩子
useDidShow(() => {
console.warn('index onShow')
......
/*
* @Date: 2025-07-02 22:16:48
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-02 22:17:11
* @FilePath: /jgdl/src/pages/newCarList/index.config.js
* @Description: 文件描述
*/
export default {
navigationBarTitleText: '',
usingComponents: {
},
}
/* 最新上架页面样式 */
.new-car-list {
width: 100%;
box-sizing: border-box;
/* 滚动条样式 */
&::-webkit-scrollbar {
width: 6rpx;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3rpx;
}
&::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3rpx;
&:hover {
background: #a8a8a8;
}
}
}
/* 车辆卡片样式 */
.new-car-list .bg-white {
background-color: #ffffff;
border: 1px solid #f0f0f0;
border-radius: 16rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
overflow: hidden;
margin-bottom: 24rpx;
transition: all 0.3s ease;
}
.new-car-list .bg-white:hover {
transform: translateY(-2rpx);
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.12);
border-color: #e5e7eb;
}
.new-car-list .bg-white:active {
transform: scale(0.98);
transition: transform 0.1s ease;
}
/* 车辆图片容器 */
.new-car-list .w-32 {
width: 200rpx;
height: 150rpx;
flex-shrink: 0;
}
.new-car-list image {
border-radius: 12rpx;
object-fit: cover;
}
/* 新车标识样式优化 */
.new-car-list .absolute.bottom-3.right-3 {
background: linear-gradient(45deg, #ef4444, #dc2626);
box-shadow: 0 2rpx 4rpx rgba(239, 68, 68, 0.3);
border-radius: 8rpx;
padding: 4rpx 8rpx;
font-size: 20rpx;
font-weight: 600;
}
/* 车辆信息区域 */
.new-car-list .flex-1 {
flex: 1;
min-width: 0;
}
/* 收藏按钮样式 */
.new-car-list .absolute {
transition: transform 0.2s ease;
}
.new-car-list .absolute:active {
transform: scale(0.9);
}
/* 价格样式 */
.new-car-list .text-orange-500 {
color: #f97316;
font-weight: bold;
}
/* 上架时间样式 */
.new-car-list .text-green-600 {
color: #10b981;
font-weight: 500;
}
/* 文字省略 */
.new-car-list .font-medium {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 加载状态样式 */
.loading-container {
padding: 32rpx 0 100rpx 0; /* 增加底部间距避免被TabBar遮挡 */
text-align: center;
.loading-text {
color: #9ca3af;
font-size: 28rpx;
}
}
/* 无更多数据样式 */
.no-more-container {
padding: 32rpx 0 100rpx 0; /* 增加底部间距避免被TabBar遮挡 */
text-align: center;
text {
color: #d1d5db;
font-size: 24rpx;
}
}
/* 动画效果 */
.new-car-list .bg-white {
animation: fadeInUp 0.3s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 响应式适配 */
@media screen and (max-width: 750rpx) {
.new-car-list .w-32 {
width: 180rpx;
height: 135rpx;
}
.new-car-list .text-xl {
font-size: 32rpx;
}
.new-car-list .text-sm {
font-size: 24rpx;
}
.new-car-list .text-xs {
font-size: 20rpx;
}
}
/* 深色模式适配 */
@media (prefers-color-scheme: dark) {
.new-car-list .bg-white {
background-color: #1f2937;
border-color: #374151;
}
.new-car-list .text-gray-600 {
color: #9ca3af;
}
.new-car-list .text-gray-500 {
color: #6b7280;
}
.new-car-list .text-gray-400 {
color: #9ca3af;
}
}
/* NutUI组件样式覆盖 */
:deep(.nut-sticky) {
z-index: 999;
}
:deep(.nut-searchbar) {
.nut-searchbar__content {
border-radius: 50rpx;
background: #fff;
.nut-searchbar__input {
font-size: 28rpx;
color: #374151;
&::placeholder {
color: #9ca3af;
}
}
}
}
:deep(.nut-menu) {
background: #fff;
border-bottom: 1rpx solid #e5e7eb;
.nut-menu__item {
font-size: 28rpx;
color: #374151;
&.active {
color: #f97316;
}
}
.nut-menu__title {
font-size: 28rpx;
&::after {
border-color: #9ca3af;
}
}
}
:deep(.nut-toast) {
.nut-toast__inner {
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 16rpx;
font-size: 28rpx;
}
}
/* 响应式适配 */
@media (max-width: 750rpx) {
.car-image-container {
width: 200rpx;
height: 150rpx;
padding: 12rpx;
}
.car-info {
padding: 20rpx;
.car-name {
font-size: 26rpx;
}
.car-details {
font-size: 22rpx;
}
.car-price {
font-size: 28rpx;
}
.car-school {
font-size: 20rpx;
}
.listing-time {
font-size: 20rpx;
}
}
}
/* 深色模式适配 */
@media (prefers-color-scheme: dark) {
.car-card {
background: #1f2937;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
&:hover {
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.4);
}
}
.car-info {
.car-name {
color: #f9fafb;
}
.car-details {
color: #d1d5db;
}
.car-school {
color: #9ca3af;
}
}
.loading-container .loading-text {
color: #6b7280;
}
.no-more-container text {
color: #4b5563;
}
}
<!--
* @Date: 2022-09-19 14:11:06
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-02 22:59:12
* @FilePath: /jgdl/src/pages/newCarList/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="搜索品牌型号" 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="new-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 newCars" :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.imageUrl" :alt="car.name" mode="aspectFill"
class="w-full h-full object-cover rounded-lg" />
<view v-if="car.isNew"
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>
<view class="flex-1 p-3 relative">
<view class="absolute top-3 right-4" @tap.stop="() => toggleFavorite(car.id)">
<Addfollow v-if="!favoriteIds.includes(car.id)" size="16" color="#9ca3af" />
<HeartFill v-else size="16" color="#ef4444" />
</view>
<text class="font-medium text-sm block">{{ car.name }}</text>
<text class="text-xs text-gray-600 mt-1 block">
{{ car.year }} ·
<text v-if="car.batteryHealth">电池健康度{{ car.batteryHealth }}%</text>
<text v-if="car.mileage"> 行驶{{ car.mileage }}公里</text>
</text>
<view class="mt-2">
<text class="text-orange-500 font-bold">
¥{{ car.price.toLocaleString() }}
</text>
<text class="text-xs text-gray-500 mt-1 block">{{ car.school }}</text>
</view>
<!-- 上架时间 -->
<view class="mt-1">
<text class="text-xs text-green-600">{{ car.listingTime }}</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 && newCars.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 { ref, computed, onMounted } from 'vue'
import { Search2, Addfollow, HeartFill } from '@nutui/icons-vue-taro'
import TabBar from '@/components/TabBar.vue'
import './index.less'
// 响应式数据
const searchValue = ref('')
const favoriteIds = ref(['2', '4', '6'])
// Filter states - 使用NutUI Menu组件
const selectedBrand = ref('全部品牌')
const selectedYear = ref('出厂年份')
const selectedSchool = ref('所在学校')
// Menu选项数据
const brandOptions = ref([
{ text: '全部品牌', value: '全部品牌' },
{ text: '雅迪', value: '雅迪' },
{ text: '台铃', value: '台铃' },
{ text: '小鸟', value: '小鸟' },
{ text: '新日', value: '新日' },
{ text: '爱玛', value: '爱玛' },
{ text: '小牛', value: '小牛' }
])
const yearOptions = ref([
{ text: '出厂年份', value: '出厂年份' },
{ text: '2024年', value: '2024年' },
{ text: '2023年', value: '2023年' },
{ text: '2022年', value: '2022年' },
{ text: '2021年', value: '2021年' },
{ text: '2020年', value: '2020年' }
])
const schoolOptions = ref([
{ text: '所在学校', value: '所在学校' },
{ text: '上海理工大学', value: '上海理工大学' },
{ text: '上海复旦大学', value: '上海复旦大学' },
{ text: '上海同济大学', value: '上海同济大学' },
{ text: '上海交通大学', value: '上海交通大学' }
])
// 最新上架车辆数据
const newCars = ref([
{
id: 1,
name: '小牛NGT 电动车',
year: '2024年',
batteryHealth: 100,
mileage: 0,
price: 5200,
school: '上海理工大学',
imageUrl: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400&h=300&fit=crop',
listingTime: '刚刚上架',
isNew: true,
brand: '小牛'
},
{
id: 2,
name: '雅迪 DE3 电动车',
year: '2024年',
batteryHealth: 98,
mileage: 200,
price: 4800,
school: '上海大学',
imageUrl: 'https://images.unsplash.com/photo-1571068316344-75bc76f77890?w=400&h=300&fit=crop',
listingTime: '5分钟前上架',
isNew: true,
brand: '雅迪'
},
{
id: 3,
name: '爱玛 A600 电动车',
year: '2024年',
batteryHealth: 95,
mileage: 500,
price: 3800,
school: '华东理工大学',
imageUrl: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400&h=300&fit=crop',
listingTime: '10分钟前上架',
isNew: true,
brand: '爱玛'
},
{
id: 4,
name: '台铃 TDR-2024 电动车',
year: '2024年',
batteryHealth: 92,
mileage: 800,
price: 4200,
school: '上海交通大学',
imageUrl: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400&h=300&fit=crop',
listingTime: '15分钟前上架',
isNew: true,
brand: '台铃'
}
])
// 加载状态
const loading = ref(false)
const hasMore = ref(true)
const currentPage = ref(1)
const pageSize = ref(4)
// 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
*/
const toggleFavorite = (carId) => {
const index = favoriteIds.value.indexOf(carId.toString())
if (index > -1) {
favoriteIds.value.splice(index, 1)
showToast('取消收藏', 'success')
} else {
favoriteIds.value.push(carId.toString())
showToast('收藏成功', 'success')
}
}
/**
* 点击车辆卡片
* @param {Object} car - 车辆信息
*/
const onCarClick = (car) => {
// TODO: 跳转到车辆详情页
showToast(`查看${car.name}详情`, 'success')
}
// 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 = () => {
// TODO: 实现过滤逻辑
showToast('筛选条件已更新', 'success')
}
/**
* 生成模拟车辆数据
* @param {number} page - 页码
* @param {number} size - 每页数量
* @returns {Array} 车辆数据数组
*/
const generateMockData = (page, size) => {
const brands = ['雅迪', '台铃', '小鸟', '新日', '爱玛', '小牛', '绿源', '立马']
const schools = ['上海理工大学', '上海复旦大学', '上海同济大学', '上海交通大学', '华东师范大学', '上海大学']
const years = ['2024年', '2023年', '2022年', '2021年', '2020年']
const images = [
'https://images.unsplash.com/photo-1567922045116-2a00fae2ed03?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
'https://images.unsplash.com/photo-1573981368236-719bbb6f70f7?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
'https://images.unsplash.com/photo-1583568671741-c70dafa8e8e7?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
'https://images.unsplash.com/photo-1595941069915-4ebc5197c14a?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
'https://images.unsplash.com/photo-1558981285-6f0c94958bb6?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
'https://images.unsplash.com/photo-1558981403-c5f9899a28bc?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60'
]
const listingTimes = ['刚刚上架', '5分钟前上架', '10分钟前上架', '15分钟前上架', '30分钟前上架', '1小时前上架']
const data = []
for (let i = 0; i < size; i++) {
const index = (page - 1) * size + i
const brand = brands[Math.floor(Math.random() * brands.length)]
const school = schools[Math.floor(Math.random() * schools.length)]
const year = years[Math.floor(Math.random() * years.length)]
const image = images[Math.floor(Math.random() * images.length)]
const listingTime = listingTimes[Math.floor(Math.random() * listingTimes.length)]
data.push({
id: `new_${index + 100}`,
name: `${brand} ${['豪华版', '标准版', '运动版', '经典版'][Math.floor(Math.random() * 4)]}`,
year: year,
school: school,
price: Math.floor(Math.random() * 3000) + 3000, // 新车价格相对较高
imageUrl: image,
batteryHealth: Math.floor(Math.random() * 10) + 90, // 新车电池健康度较高
mileage: Math.floor(Math.random() * 1000), // 新车里程较少
brand: brand,
listingTime: listingTime,
isNew: Math.random() > 0.3 // 70%概率显示新标签
})
}
return data
}
/**
* 加载更多数据
*/
const loadMore = () => {
if (loading.value || !hasMore.value) return
loading.value = true
// 模拟网络请求延迟
setTimeout(() => {
// 模拟最多加载5页数据
if (currentPage.value >= 5) {
hasMore.value = false
loading.value = false
return
}
currentPage.value++
const newData = generateMockData(currentPage.value, pageSize.value)
newCars.value.push(...newData)
loading.value = false
}, 1000 + Math.random() * 1000)
}
/**
* 滚动事件处理
*/
const scroll = (e) => {
// 可以在这里处理滚动事件
}
/**
* 显示提示信息
*/
const showToast = (message, type = 'success') => {
toastMessage.value = message
toastType.value = type
toastVisible.value = true
}
// 初始化
onMounted(() => {
// 可以在这里加载初始数据
})
</script>
<script>
export default {
name: 'NewCarListPage'
}
</script>
// Post页面样式
.post-page {
// 车辆卡片样式
.vehicle-list {
// 图片容器尺寸优化
.w-32 {
width: 200rpx !important;
height: 150rpx !important;
}
// 卡片样式增强
.bg-white {
border: 1px solid #f0f0f0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
&:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
transform: translateY(-2px);
}
&:active {
transform: translateY(0);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
}
// 收藏按钮样式
.absolute.top-2.right-2 {
transition: transform 0.2s ease;
&:hover {
transform: scale(1.1);
}
&:active {
transform: scale(0.95);
}
}
// 价格样式优化
.text-orange-500 {
color: #f97316;
font-weight: 700;
}
// 认证标识样式
.bg-orange-500 {
background: linear-gradient(135deg, #f97316, #ea580c);
box-shadow: 0 2px 4px rgba(249, 115, 22, 0.3);
}
// 文字省略
.font-medium {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
// 精品推荐区域
.grid {
// 图片容器
.w-full.h-36 {
height: 280rpx;
}
// 卡片样式
.bg-white {
border: 1px solid #f0f0f0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
&:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
transform: translateY(-2px);
}
}
}
// 加载状态样式
.load-more-container {
padding: 40rpx 0;
.loading-container {
display: flex;
align-items: center;
justify-content: center;
color: #666;
.loading-spinner {
width: 40rpx;
height: 40rpx;
border: 4rpx solid #f3f3f3;
border-top: 4rpx solid #f97316;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 16rpx;
}
.loading-text {
font-size: 28rpx;
color: #666;
}
}
.no-more-data {
text-align: center;
color: #999;
font-size: 28rpx;
}
}
}
// NutUI组件样式覆盖
.nut-menu {
border-bottom: 1px solid #f0f0f0;
.nut-menu-item {
font-size: 28rpx;
&.active {
color: #f97316;
}
}
}
.nut-searchbar {
.nut-searchbar__input {
font-size: 28rpx;
}
}
.nut-button {
&[type="default"] {
border-color: #f97316;
color: #f97316;
&:hover {
background-color: #f97316;
color: white;
}
}
}
// 响应式适配
@media (max-width: 750rpx) {
.post-page {
.vehicle-list {
.w-32 {
width: 180rpx !important;
height: 135rpx !important;
}
}
.grid {
.w-full.h-36 {
height: 240rpx;
}
}
.font-medium {
font-size: 26rpx;
}
.text-xs {
font-size: 22rpx;
}
}
}
// 深色模式适配
@media (prefers-color-scheme: dark) {
.post-page {
.bg-white {
background-color: #1f2937;
border-color: #374151;
color: #f9fafb;
}
.text-gray-600 {
color: #9ca3af;
}
.text-gray-500 {
color: #6b7280;
}
}
}
// 动画效果
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
// 卡片进入动画
.post-page .vehicle-list > .space-y-4 > view {
animation: fadeInUp 0.6s ease-out;
}
.post-page .grid > view {
animation: fadeInUp 0.6s ease-out;
}
// 滚动条样式
::-webkit-scrollbar {
width: 8rpx;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4rpx;
}
::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4rpx;
&:hover {
background: #a8a8a8;
}
}
\ No newline at end of file
<template>
<view>
<view class="post-page">
<view class="flex flex-col bg-white min-h-screen">
<!-- Header -->
<nut-sticky>
......@@ -44,7 +44,7 @@
</view>
</view>
<view class="flex-1 p-3 relative">
<view class="absolute top-2 right-2" @tap.stop="() => toggleFavorite(scooter.id)">
<view class="absolute top-3 right-4" @tap.stop="() => toggleFavorite(scooter.id)">
<Addfollow v-if="!favoriteIds.includes(scooter.id)" size="16" color="#9ca3af" />
<HeartFill v-else size="16" color="#ef4444" />
</view>
......@@ -132,7 +132,7 @@
<script setup>
import { ref } from 'vue'
import Taro from '@tarojs/taro'
import { Search2, Right, RectRight, Check, Addfollow, HeartFill } from '@nutui/icons-vue-taro'
import { Search2, RectRight, Check, Addfollow, HeartFill } from '@nutui/icons-vue-taro'
import TabBar from '@/components/TabBar.vue'
// 响应式数据
......@@ -393,6 +393,8 @@ const loadMoreData = async () => {
</script>
<style lang="less">
@import './index.less';
// 使用Tailwind CSS类,只保留必要的自定义样式
.space-y-4>view:not(:first-child) {
margin-top: 1rem;
......