hookehuyr

feat(myAuthCar): 添加我的认证车页面功能

添加我的认证车页面,包括页面配置、路由注册、样式和主要功能实现
- 创建认证车页面组件和样式文件
- 在app.config中注册新页面路由
- 修改个人中心页面跳转逻辑
- 实现认证车列表展示、加载更多和空状态
- 添加模拟数据和API服务占位
/*
* @Date: 2025-06-28 10:33:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-03 12:59:10
* @LastEditTime: 2025-07-03 18:09:07
* @FilePath: /jgdl/src/app.config.js
* @Description: 文件描述
*/
......@@ -23,6 +23,7 @@ export default {
'pages/myFavorites/index',
'pages/myCar/index',
'pages/myOrders/index',
'pages/myAuthCar/index',
],
subpackages: [ // 配置在tabBar中的页面不能分包写到subpackages中去
{
......
/*
* @Date: 2025-07-03 18:08:31
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-03 18:08:57
* @FilePath: /jgdl/src/pages/myAuthCar/index.config.js
* @Description: 文件描述
*/
export default {
navigationBarTitleText: '我的认证车',
usingComponents: {
},
}
/* 我的认证车页面样式 */
.auth-car-list {
background-color: #f8f9fa;
}
/* 车辆卡片样式 */
.car-item {
background: white;
border-radius: 12rpx;
margin: 16rpx;
padding: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
/* 按钮样式优化 */
.sell-button {
background: linear-gradient(135deg, #ff6b6b, #ff8e8e);
border: none;
color: white;
font-weight: 500;
}
/* 价格样式 */
.price-text {
font-size: 32rpx;
font-weight: bold;
color: #ff6b35;
}
.original-price {
font-size: 24rpx;
color: #999;
text-decoration: line-through;
}
/* 空状态样式 */
.empty-state {
padding: 120rpx 0;
text-align: center;
color: #999;
font-size: 28rpx;
}
<!--
* @Date: 2022-09-19 14:11:06
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-03 13:25:46
* @FilePath: /jgdl/src/pages/myAuthCar/index.vue
* @Description: 我的认证车页面
-->
<template>
<view class="flex flex-col bg-white min-h-screen">
<!-- Auth Car List -->
<view class="flex-1">
<!-- 滚动列表 -->
<scroll-view
class="auth-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="item in authCars"
:key="item.id"
@tap="() => onItemClick(item)"
style="border-bottom: 1px solid #e5e7eb"
>
<view class="flex p-4">
<view class="w-24 h-24 relative">
<image
:src="item.imageUrl"
:alt="item.name"
mode="aspectFill"
class="w-full h-full object-cover rounded-lg"
/>
</view>
<view class="flex-1 ml-4">
<text class="font-medium text-base block">{{ item.name }}</text>
<text class="text-sm text-gray-500 mt-1 block">{{ item.details }}</text>
<view class="mt-2 flex justify-between items-center">
<view>
<text class="text-orange-500 font-bold">
¥{{ item.price.toLocaleString() }}
</text>
<text class="text-gray-400 text-xs line-through ml-2">
¥{{ item.originalPrice.toLocaleString() }}
</text>
</view>
<nut-button
@click.stop="handleSellClick(item.id)"
size="small"
type="primary"
class="px-3 py-1 rounded-full text-sm"
>
我要卖车
</nut-button>
</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 && authCars.length > 0" class="no-more-container py-4 text-center">
<text class="text-gray-400 text-sm">没有更多数据了</text>
</view>
<!-- Empty State -->
<view
v-if="authCars.length === 0 && !loading"
class="flex flex-col items-center justify-center h-64"
>
<text class="text-gray-500">暂无认证车辆</text>
</view>
</scroll-view>
</view>
<!-- 成功提示 -->
<nut-toast
v-model:visible="toastVisible"
:msg="toastMessage"
:type="toastType"
/>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import Taro from '@tarojs/taro'
import './index.less'
// ==================== API相关 ====================
/**
* API服务 - 为真实API预留空间
*/
const apiService = {
/**
* 获取我的认证车列表
* @param {number} page - 页码
* @param {number} pageSize - 每页数量
* @returns {Promise} API响应
*/
async getAuthCarsList(page = 1, pageSize = 10) {
// TODO: 替换为真实API调用
// return await request.get('/api/auth-cars', { page, pageSize })
// 模拟API延迟
await new Promise(resolve => setTimeout(resolve, 800 + Math.random() * 400))
// 模拟API响应数据
return {
code: 200,
data: {
list: generateMockData(page, pageSize),
total: 50, // 模拟总数
hasMore: page < 5 // 模拟是否还有更多数据
},
message: 'success'
}
}
}
// ==================== 响应式数据 ====================
/**
* 我的认证车列表数据
*/
const authCars = ref([])
/**
* 加载状态
*/
const loading = ref(false)
const hasMore = ref(true)
const currentPage = ref(1)
const pageSize = ref(10)
/**
* Toast提示
*/
const toastVisible = ref(false)
const toastMessage = ref('')
const toastType = ref('success')
/**
* 滚动样式 - 考虑header和TabBar的高度
*/
const scrollStyle = computed(() => {
return {
height: 'calc(100vh)' // 减去header和TabBar的高度
}
})
// ==================== 数据处理方法 ====================
/**
* 生成模拟数据
* @param {number} page - 页码
* @param {number} size - 每页数量
* @returns {Array} 模拟数据数组
*/
const generateMockData = (page, size) => {
const brands = ['小牛', '雅迪', '绿源', '爱玛', '台铃', '新日', '立马', '小鸟']
const models = ['豪华版', '标准版', '运动版', '经典版', '智能版', '动力版']
const images = [
'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',
'https://images.unsplash.com/photo-1591637333184-19aa84b3e01f?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
'https://images.unsplash.com/photo-1558980664-3a031cf67ea8?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
'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'
]
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 model = models[Math.floor(Math.random() * models.length)]
const image = images[Math.floor(Math.random() * images.length)]
const originalPrice = Math.floor(Math.random() * 3000) + 3000
const price = Math.floor(originalPrice * (0.7 + Math.random() * 0.2)) // 7-9折
const usageTime = Math.floor(Math.random() * 24) + 1 // 1-24个月
const range = Math.floor(Math.random() * 100) + 60 // 60-160km续航
data.push({
id: `auth_${index + 100}`,
name: `${brand} ${model}`,
details: `续航${range}km | 使用${usageTime}个月`,
price: price,
originalPrice: originalPrice,
imageUrl: image,
brand: brand,
authTime: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000).toISOString() // 最近30天内认证
})
}
return data
}
/**
* 初始化加载数据
*/
const initData = async () => {
loading.value = true
try {
const response = await apiService.getAuthCarsList(1, pageSize.value)
if (response.code === 200) {
authCars.value = response.data.list
hasMore.value = response.data.hasMore
currentPage.value = 1
} else {
showToast('加载失败,请重试', 'error')
}
} catch (error) {
console.error('加载我的认证车列表失败:', error)
showToast('网络错误,请重试', 'error')
} finally {
loading.value = false
}
}
/**
* 加载更多数据
*/
const loadMore = async () => {
if (loading.value || !hasMore.value) return
loading.value = true
try {
const nextPage = currentPage.value + 1
const response = await apiService.getAuthCarsList(nextPage, pageSize.value)
if (response.code === 200) {
authCars.value.push(...response.data.list)
hasMore.value = response.data.hasMore
currentPage.value = nextPage
} else {
showToast('加载失败,请重试', 'error')
}
} catch (error) {
console.error('加载更多数据失败:', error)
showToast('网络错误,请重试', 'error')
} finally {
loading.value = false
}
}
// ==================== 事件处理方法 ====================
/**
* 点击车辆项目
* @param {Object} item - 车辆信息
*/
const onItemClick = (item) => {
// TODO: 跳转到车辆详情页
Taro.navigateTo({
url: `/pages/productDetail/index?id=${item.id}`
})
}
/**
* 处理我要卖车点击事件
* @param {string} carId - 车辆ID
*/
const handleSellClick = (carId) => {
Taro.navigateTo({
url: `/pages/sell/index?id=${carId}&mode=edit`
})
}
/**
* 滚动事件处理
*/
const scroll = (e) => {
// 可以在这里处理滚动事件,比如记录滚动位置
}
/**
* 显示提示信息
*/
const showToast = (message, type = 'success') => {
toastMessage.value = message
toastType.value = type
toastVisible.value = true
}
// ==================== 生命周期 ====================
onMounted(() => {
initData()
})
</script>
<script>
export default {
name: "MyAuthCarPage",
};
</script>
<style lang="less" scoped>
// 自定义样式
.object-cover {
object-fit: cover;
}
.line-through {
text-decoration: line-through;
}
.auth-car-list {
.loading-container {
display: flex;
justify-content: center;
align-items: center;
.loading-text {
font-size: 28rpx;
color: #999;
}
}
.no-more-container {
display: flex;
justify-content: center;
align-items: center;
padding: 32rpx 0;
text {
font-size: 24rpx;
color: #ccc;
}
}
}
// 认证标签样式
.absolute {
position: absolute;
}
.top-1 {
top: 4rpx;
}
.right-1 {
right: 4rpx;
}
.bg-green-500 {
background-color: #10b981;
}
.text-white {
color: white;
}
.text-xs {
font-size: 20rpx;
}
.px-1 {
padding-left: 4rpx;
padding-right: 4rpx;
}
.rounded {
border-radius: 8rpx;
}
.flex {
display: flex;
}
.items-center {
align-items: center;
}
</style>
......@@ -52,7 +52,7 @@
<Right size="18" color="#9ca3af" />
</view>
<view class="menu-item" @click="onSettings">
<view class="menu-item" @click="onMyAuthCar">
<StarN size="20" color="#6b7280" />
<text class="menu-text">我的认证</text>
<Right size="18" color="#9ca3af" />
......@@ -163,11 +163,11 @@ const onFeedback = () => {
}
/**
* 设置
* 我的认证车
*/
const onSettings = () => {
const onMyAuthCar = () => {
Taro.navigateTo({
url: '/pages/settings/index'
url: '/pages/myAuthCar/index'
})
}
</script>
......