hookehuyr

feat(车辆管理): 实现品牌型号和学校数据从API加载

重构品牌型号选择器组件,移除模拟数据
添加学校列表和品牌型号列表的API调用
更新车辆列表页面使用真实API数据
......@@ -15,6 +15,7 @@ declare module 'vue' {
NutConfigProvider: typeof import('@nutui/nutui-taro')['ConfigProvider']
NutDatePicker: typeof import('@nutui/nutui-taro')['DatePicker']
NutDialog: typeof import('@nutui/nutui-taro')['Dialog']
NutEllipsis: typeof import('@nutui/nutui-taro')['Ellipsis']
NutForm: typeof import('@nutui/nutui-taro')['Form']
NutFormItem: typeof import('@nutui/nutui-taro')['FormItem']
NutImagePreview: typeof import('@nutui/nutui-taro')['ImagePreview']
......
/*
* @Date: 2025-07-09 14:58:51
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-09 16:07:38
* @LastEditTime: 2025-07-10 14:45:49
* @FilePath: /jgdl/src/api/car.js
* @Description: 车辆相关API接口
*/
......
......@@ -115,6 +115,14 @@ import Taro from '@tarojs/taro'
import { Right } from '@nutui/icons-vue-taro'
import { $ } from '@tarojs/extend'
// 定义props
const props = defineProps({
brandOptions: {
type: Array,
default: () => []
}
})
// 定义事件
const emit = defineEmits(['confirm', 'cancel'])
......@@ -129,128 +137,26 @@ const searchKeyword = ref('') // 搜索关键词
const customBrand = ref('') // 自定义品牌
const customModel = ref('') // 自定义型号
const allBrands = ref([
{ text: '爱玛', value: 'aima' },
{ text: '雅迪', value: 'yadi' },
{ text: '小牛', value: 'xiaoniu' },
{ text: '台铃', value: 'tailing' },
{ text: '绿源', value: 'lvyuan' },
{ text: '立马', value: 'lima' },
{ text: '新日', value: 'xinri' },
{ text: '宗申', value: 'zongshen' },
{ text: '奇瑞', value: 'qirui' },
{ text: '比德文', value: 'bidewen' },
{ text: '欧派', value: 'oupai' },
{ text: '捷安特', value: 'jiante' },
{ text: '美利达', value: 'meilida' },
{ text: '凤凰', value: 'fenghuang' },
{ text: '永久', value: 'yongjiu' },
{ text: '飞鸽', value: 'feige' },
{ text: '小刀', value: 'xiaodao' },
{ text: '速派奇', value: 'supaiqi' },
{ text: '金箭', value: 'jinjian' },
{ text: '森蓝', value: 'senlan' },
{ text: '其他', value: 'other' }
])
// 品牌型号映射
const brandModelMap = ref({
'aima': [
{ text: 'A1 Pro', value: 'aima_a1_pro' },
{ text: 'A2 Max', value: 'aima_a2_max' },
{ text: 'A3 Plus', value: 'aima_a3_plus' },
{ text: 'A4 Elite', value: 'aima_a4_elite' },
{ text: 'A4i', value: 'aima_a4i' },
{ text: 'A4i+', value: 'aima_a4i_plus' },
{ text: 'A5', value: 'aima_a5' },
{ text: 'A5i', value: 'aima_a5i' },
{ text: 'A6', value: 'aima_a6' },
{ text: 'A6i', value: 'aima_a6i' },
],
'yadi': [
{ text: 'Y1 智享版', value: 'yadi_y1_smart' },
{ text: 'Y2 豪华版', value: 'yadi_y2_luxury' },
{ text: 'Y3 运动版', value: 'yadi_y3_sport' },
{ text: 'Y4 旗舰版', value: 'yadi_y4_flagship' }
],
'xiaoniu': [
{ text: 'N1S', value: 'xiaoniu_n1s' },
{ text: 'N-GT', value: 'xiaoniu_ngt' },
{ text: 'NGT Pro', value: 'xiaoniu_ngt_pro' },
{ text: 'U1 Pro', value: 'xiaoniu_u1_pro' }
],
'tailing': [
{ text: 'T1 经典版', value: 'tailing_t1_classic' },
{ text: 'T2 时尚版', value: 'tailing_t2_fashion' },
{ text: 'T3 豪华版', value: 'tailing_t3_luxury' }
],
'lvyuan': [
{ text: 'L1 标准版', value: 'lvyuan_l1_standard' },
{ text: 'L2 升级版', value: 'lvyuan_l2_upgrade' },
{ text: 'L3 旗舰版', value: 'lvyuan_l3_flagship' }
],
'lima': [
{ text: 'LM-1', value: 'lima_lm1' },
{ text: 'LM-2', value: 'lima_lm2' },
{ text: 'LM-3 Pro', value: 'lima_lm3_pro' }
],
'xinri': [
{ text: 'XR-1', value: 'xinri_xr1' },
{ text: 'XR-2 Plus', value: 'xinri_xr2_plus' },
{ text: 'XR-3 Max', value: 'xinri_xr3_max' }
],
'zongshen': [
{ text: 'ZS-1', value: 'zongshen_zs1' },
{ text: 'ZS-2 Pro', value: 'zongshen_zs2_pro' }
],
'qirui': [
{ text: 'QR-1', value: 'qirui_qr1' },
{ text: 'QR-2 智能版', value: 'qirui_qr2_smart' }
],
'bidewen': [
{ text: 'BD-1', value: 'bidewen_bd1' },
{ text: 'BD-2 Plus', value: 'bidewen_bd2_plus' }
],
'oupai': [
{ text: 'OP-1', value: 'oupai_op1' },
{ text: 'OP-2 Pro', value: 'oupai_op2_pro' }
],
'jiante': [
{ text: 'JT-1', value: 'jiante_jt1' },
{ text: 'JT-2 运动版', value: 'jiante_jt2_sport' }
],
'meilida': [
{ text: 'MD-1', value: 'meilida_md1' },
{ text: 'MD-2 Pro', value: 'meilida_md2_pro' }
],
'fenghuang': [
{ text: 'FH-1 经典', value: 'fenghuang_fh1_classic' },
{ text: 'FH-2 现代', value: 'fenghuang_fh2_modern' }
],
'yongjiu': [
{ text: 'YJ-1', value: 'yongjiu_yj1' },
{ text: 'YJ-2 Plus', value: 'yongjiu_yj2_plus' }
],
'feige': [
{ text: 'FG-1', value: 'feige_fg1' },
{ text: 'FG-2 Pro', value: 'feige_fg2_pro' }
],
'xiaodao': [
{ text: 'XD-1', value: 'xiaodao_xd1' },
{ text: 'XD-2 Max', value: 'xiaodao_xd2_max' }
],
'supaiqi': [
{ text: 'SP-1', value: 'supaiqi_sp1' },
{ text: 'SP-2 Pro', value: 'supaiqi_sp2_pro' }
],
'jinjian': [
{ text: 'JJ-1', value: 'jinjian_jj1' },
{ text: 'JJ-2 Plus', value: 'jinjian_jj2_plus' }
],
'senlan': [
{ text: 'SL-1', value: 'senlan_sl1' },
{ text: 'SL-2 Pro', value: 'senlan_sl2_pro' }
]
// 使用父组件传入的品牌数据,并添加"其他"选项
const allBrands = computed(() => {
const brands = [...props.brandOptions]
// 添加"其他"选项
brands.push({ text: '其他', value: 'other' })
return brands
})
// 根据API数据构建品牌型号映射
const brandModelMap = computed(() => {
const map = {}
props.brandOptions.forEach(brand => {
if (brand.models && brand.models.length > 0) {
map[brand.value] = brand.models.map(model => ({
text: model.name,
value: model.id
}))
}
})
return map
})
// 过滤后的品牌列表
......@@ -323,10 +229,13 @@ const selectBrand = (brand) => {
// 选择型号
const selectModel = (model) => {
// 找到对应的品牌ID
const selectedBrand = props.brandOptions.find(brand => brand.text === selectedBrandName.value)
const result = {
brand: selectedBrandName.value,
model: model.text,
brandValue: selectedBrandName.value,
brandValue: selectedBrand ? selectedBrand.value : selectedBrandName.value,
modelValue: model.value
}
......
<!--
* @Date: 2022-09-19 14:11:06
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-07 16:38:18
* @LastEditTime: 2025-07-10 16:26:10
* @FilePath: /jgdl/src/pages/myCar/index.vue
* @Description: 文件描述
-->
......@@ -20,9 +20,9 @@
>
<!-- 空状态 -->
<view v-if="!loading && carList.length === 0" class="empty-state">
<image src="/static/images/empty-car.png" class="empty-image" mode="aspectFit" />
<!-- <image src="/static/images/empty-car.png" class="empty-image" mode="aspectFit" /> -->
<text class="empty-text">暂无车源信息</text>
<nut-button color="#f97316" size="small" @click="goToSell">发布车源</nut-button>
<!-- <nut-button color="#f97316" @click="goToSell">发布车源</nut-button> -->
</view>
<!-- 车辆卡片列表 -->
......@@ -31,34 +31,34 @@
<!-- 使用 nut-row 和 nut-col 布局 -->
<nut-row :gutter="10">
<!-- 左侧:车辆图片 -->
<nut-col :span="8">
<nut-col :span="6">
<view class="car-image-container">
<image :src="car.image" class="car-image" mode="aspectFill" />
<image :src="car.front_photo" class="car-image" mode="aspectFill" />
</view>
</nut-col>
<!-- 右侧:车辆信息 -->
<nut-col :span="16">
<nut-col :span="18">
<view class="car-info">
<view class="car-title">{{ car.brand }} {{ car.model }}</view>
<view class="car-title">{{ car.manufacture_year }}款 {{ car.brand }} {{ car.model }}</view>
<!-- 状态标识 -->
<view class="status-badges">
<view v-if="car.isAuthenticated" class="status-badge verified">
<text>已认证</text>
<view v-if="car.verification_status" class="status-badge verified">
<text>{{ verifyStatus[car.verification_status] }}</text>
</view>
<view v-if="car.isOffline" class="status-badge offline">
<text>已下架</text>
<view v-if="car.status === 5" class="status-badge offline">
<text>{{ carStatus[car.status] }}</text>
</view>
</view>
<!-- <view class="car-details">
<text class="detail-item">{{ car.year }}</text>
<text class="detail-item">{{ car.condition }}</text>
<text class="detail-item">{{ car.mileage }}公里</text>
</view> -->
<view class="car-description">{{ car.description }}</view>
<view class="car-details">
<text class="detail-item">续航 {{ car.range_km }}km | 最高时速 {{ car.max_speed_kmh }}km/h</text>
</view>
<view class="car-description">
<nut-ellipsis direction="end" :content="car.note" :rows="2"></nut-ellipsis>
</view>
<view class="price-section">
<view class="current-price">¥{{ car.price }}</view>
<view class="market-price">市场价 ¥{{ car.marketPrice }}</view>
<view class="market-price">市场价 ¥{{ car.market_price }}</view>
</view>
</view>
</nut-col>
......@@ -69,13 +69,12 @@
<nut-button size="small" type="default" @click="editCar(car.id)">编辑</nut-button>
<nut-button
size="small"
:type="car.isOffline ? 'success' : 'warning'"
:type="car.status === 5 ? 'success' : 'warning'"
@click="toggleOffline(car)"
>
{{ car.isOffline ? '上架' : '下架' }}
{{ car.status === 5 ? '上架' : '下架' }}
</nut-button>
<nut-button
v-if="!car.isAuthenticated"
size="small"
type="primary"
@click="authCar(car.id)"
......@@ -115,9 +114,25 @@
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import Taro from '@tarojs/taro'
import { ref, computed } from 'vue'
import Taro, { useDidShow } from '@tarojs/taro'
import './index.less'
// 导入接口
import { getMyListingVehicleAPI, changeVehicleStatusAPI } from '@/api/car';
// 认证状态映射
const verifyStatus = ref({
1: '未认证',
3: '认证待审核',
5: '已认证',
7: '认证失败'
})
// 上架状态映射
const carStatus = ref({
3: '已上架',
5: '已下架',
})
/**
* 滚动样式 - 考虑header和TabBar的高度
......@@ -133,7 +148,7 @@ const scrollStyle = computed(() => {
// 页面状态
const loading = ref(false)
const hasMore = ref(true)
const currentPage = ref(1)
const currentPage = ref(0)
const pageSize = ref(10)
// 车辆列表数据
......@@ -149,11 +164,11 @@ const currentOfflineCar = ref(null)
/**
* 跳转到发布车源页面
*/
const goToSell = () => {
Taro.navigateTo({
url: '/pages/sell/index'
})
}
// const goToSell = () => {
// Taro.navigateTo({
// url: '/pages/sell/index'
// })
// }
/**
* 编辑车源
......@@ -178,26 +193,31 @@ const authCar = (carId) => {
*/
const toggleOffline = (car) => {
currentOfflineCar.value = car
offlineDialogContent.value = car.isOffline ? '确认要上架此车源吗?' : '确认要下架此车源吗?'
offlineDialogContent.value = car.status === 5 ? '确认要上架此车源吗?' : '确认要下架此车源吗?'
offlineDialogVisible.value = true
}
/**
* 确认上下架操作
*/
const confirmOffline = () => {
const confirmOffline = async () => {
if (currentOfflineCar.value) {
const car = currentOfflineCar.value
car.isOffline = !car.isOffline
// TODO: 调用API更新车源状态
// updateCarStatus(car.id, { isOffline: car.isOffline })
if (car.status === 3) {
car.status = 5
} else {
car.status = 3
}
// 调用API更新车源状态
const { code } = await changeVehicleStatusAPI({ id: car.id, status: car.status })
if (code) {
Taro.showToast({
title: car.isOffline ? '已下架' : '已上架',
title: car.status === 3 ? '已下架' : '已上架',
icon: 'success'
})
}
}
offlineDialogVisible.value = false
currentOfflineCar.value = null
}
......@@ -213,27 +233,30 @@ const cancelOffline = () => {
/**
* 获取车辆列表数据
*/
const fetchCarList = async (page = 1, append = false) => {
const fetchCarList = async (page = 0, append = false) => {
loading.value = true
try {
// 模拟API调用延迟
await new Promise(resolve => setTimeout(resolve, 800))
// 调用真实API获取车辆列表
const response = await getMyListingVehicleAPI({ page, limit: pageSize.value })
const mockData = generateMockCarData(page, pageSize.value)
if (response.code === 1) {
const { list, total } = response.data
if (append) {
carList.value.push(...mockData)
carList.value.push(...list)
} else {
carList.value = mockData
carList.value = list
}
// 模拟分页逻辑
if (page >= 3) {
hasMore.value = false
}
// 根据总数和当前数据量判断是否还有更多数据
const currentTotal = append ? carList.value.length : list.length
hasMore.value = currentTotal < total
currentPage.value = page
} else {
showToast(response.msg || '获取车辆列表失败', 'error')
}
} catch (error) {
console.error('获取车辆列表失败:', error)
showToast('加载失败,请重试', 'error')
......@@ -267,57 +290,11 @@ const showToast = (message, type = 'success') => {
})
}
/**
* 生成模拟车辆数据
*/
const generateMockCarData = (page = 1, size = 10) => {
const brands = ['奔驰', '宝马', '奥迪', '大众', '丰田', '本田', '日产', '现代']
const models = ['C级', 'E级', 'S级', '3系', '5系', '7系', 'A4', 'A6', 'A8']
const conditions = ['准新车', '车况良好', '车况一般']
const images = [
'https://images.unsplash.com/photo-1549924231-f129b911e442?w=400',
'https://images.unsplash.com/photo-1552519507-da3b142c6e3d?w=400',
'https://images.unsplash.com/photo-1494976388531-d1058494cdd8?w=400',
'https://images.unsplash.com/photo-1503376780353-7e6692767b70?w=400',
'https://images.unsplash.com/photo-1525609004556-c46c7d6cf023?w=400'
]
const list = []
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 condition = conditions[Math.floor(Math.random() * conditions.length)]
const image = images[Math.floor(Math.random() * images.length)]
const price = Math.floor(Math.random() * 20000) + 500
const marketPrice = Math.floor(price * 1.2)
const year = 2018 + Math.floor(Math.random() * 6)
const mileage = Math.floor(Math.random() * 1000) + 100
list.push({
id: `car_${index + 1}`,
brand,
model,
year,
condition,
mileage,
price,
marketPrice,
image,
description: `${year}年${brand}${model},${condition},里程${mileage}公里`,
isAuthenticated: Math.random() > 0.5,
isOffline: Math.random() > 0.7,
publishTime: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000).toISOString()
})
}
return list
}
// 页面加载时获取数据
onMounted(() => {
fetchCarList(1, false)
useDidShow(() => {
fetchCarList(0, false)
})
</script>
......
......@@ -274,7 +274,7 @@
</nut-popup>
<!-- 品牌型号选择器组件 -->
<BrandModelPicker ref="brandModelPickerRef" @confirm="onBrandModelConfirm" @cancel="onBrandModelCancel" />
<BrandModelPicker ref="brandModelPickerRef" :brand-options="brandOptions" @confirm="onBrandModelConfirm" @cancel="onBrandModelCancel" />
<!-- 年份选择 -->
<nut-popup v-model:visible="yearPickerVisible" position="bottom">
......@@ -416,39 +416,10 @@ const conditionValue = ref([])
const brakeWearValue = ref([])
const tireWearValue = ref([])
// TODO: 模拟数据需要等接口
// 选择器选项数据
const schoolOptions = ref([
{ text: '上海理工大学', value: '上海理工大学' },
{ text: '复旦大学', value: '复旦大学' },
{ text: '上海交通大学', value: '上海交通大学' },
{ text: '同济大学', value: '同济大学' },
{ text: '华东师范大学', value: '华东师范大学' },
{ text: '上海大学', value: '上海大学' },
{ text: '东华大学', value: '东华大学' },
{ text: '上海财经大学', value: '上海财经大学' }
])
const brandOptions = ref([
{ text: '小牛电动', value: '小牛电动' },
{ text: '雅迪', value: '雅迪' },
{ text: '爱玛', value: '爱玛' },
{ text: '台铃', value: '台铃' },
{ text: '绿源', value: '绿源' },
{ text: '新日', value: '新日' },
{ text: '立马', value: '立马' },
{ text: '其他', value: '其他' }
])
const modelOptions = ref([
{ text: 'NGT', value: 'NGT' },
{ text: 'UQi+', value: 'UQi+' },
{ text: 'Gova G2', value: 'Gova G2' },
{ text: 'MQi+', value: 'MQi+' },
{ text: 'NQi GTS', value: 'NQi GTS' },
{ text: 'RQi', value: 'RQi' },
{ text: '其他型号', value: '其他型号' }
])
const schoolOptions = ref([])
const brandOptions = ref([])
const modelOptions = ref([])
......@@ -636,9 +607,9 @@ const showTireWearPicker = () => {
/**
* 学校选择确认
*/
const onSchoolConfirm = ({ selectedValue }) => {
formData.school_id = selectedValue[0].id
formData.school_name = selectedValue[0].name
const onSchoolConfirm = ({ selectedValue, selectedOptions }) => {
formData.school_id = selectedValue[0]
formData.school_name = selectedOptions[0].text
schoolPickerVisible.value = false
}
......@@ -939,6 +910,42 @@ const loadCarData = async () => {
}
}
/**
* 加载学校列表
*/
const loadSchools = async () => {
try {
const { code, data } = await getSchoolsAPI()
if (code && data) {
schoolOptions.value = data.map(school => ({
text: school.name,
value: school.id
}))
}
} catch (error) {
console.error('加载学校列表失败:', error)
}
}
/**
* 加载品牌型号列表
*/
const loadBrandsModels = async () => {
try {
const { code, data } = await getBrandsModelsAPI()
if (code && data) {
// 转换品牌数据格式
brandOptions.value = data.map(brand => ({
text: brand.name,
value: brand.id,
models: brand.models || []
}))
}
} catch (error) {
console.error('加载品牌型号列表失败:', error)
}
}
// 页面加载时执行
onMounted(async () => {
// 检查卖车权限
......@@ -947,6 +954,12 @@ onMounted(async () => {
return
}
// 加载基础数据
await Promise.all([
loadSchools(),
loadBrandsModels()
])
if (isEditMode.value) {
loadCarData()
}
......