hookehuyr

feat(车辆认证): 重构车辆认证表单并接入真实API

- 将表单字段重命名为与后端一致的命名规范
- 添加品牌型号选择器的动态数据加载
- 实现车辆数据的获取、提交和编辑功能
- 优化图片上传逻辑,同步更新表单数据
- 移除模拟数据,完全使用真实API交互
1 <!-- 1 <!--
2 * @Date: 2022-09-19 14:11:06 2 * @Date: 2022-09-19 14:11:06
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-07-04 12:22:02 4 + * @LastEditTime: 2025-07-10 17:24:11
5 * @FilePath: /jgdl/src/pages/setAuthCar/index.vue 5 * @FilePath: /jgdl/src/pages/setAuthCar/index.vue
6 * @Description: 申请认证 6 * @Description: 申请认证
7 --> 7 -->
...@@ -91,15 +91,16 @@ ...@@ -91,15 +91,16 @@
91 :rules="[{ required: true, message: '请选择品牌型号' }]"> 91 :rules="[{ required: true, message: '请选择品牌型号' }]">
92 <view class="form-item-content" @click="showBrandModelPicker"> 92 <view class="form-item-content" @click="showBrandModelPicker">
93 <text class="form-value"> 93 <text class="form-value">
94 - {{ formData.brand && formData.model ? `${formData.brand} ${formData.model}` : '请选择品牌型号' }} 94 + {{ formData.brand && formData.model ? `${formData.brand} ${formData.model}` : '请选择品牌型号'
95 + }}
95 </text> 96 </text>
96 <Right class="arrow-icon" /> 97 <Right class="arrow-icon" />
97 </view> 98 </view>
98 </nut-form-item> 99 </nut-form-item>
99 100
100 <!-- 续航里程 --> 101 <!-- 续航里程 -->
101 - <nut-form-item label="续航里程" prop="range" required :rules="[{ required: true, message: '请输入续航里程' }]"> 102 + <nut-form-item label="续航里程" prop="range_km">
102 - <nut-input v-model="formData.range" placeholder="60" type="number" input-align="right"> 103 + <nut-input v-model="formData.range_km" placeholder="60" type="number" input-align="right">
103 <template #right> 104 <template #right>
104 <text class="unit">公里</text> 105 <text class="unit">公里</text>
105 </template> 106 </template>
...@@ -107,9 +108,8 @@ ...@@ -107,9 +108,8 @@
107 </nut-form-item> 108 </nut-form-item>
108 109
109 <!-- 最高时速 --> 110 <!-- 最高时速 -->
110 - <nut-form-item label="最高时速" prop="maxSpeed" required 111 + <nut-form-item label="最高时速" prop="max_speed_kmh">
111 - :rules="[{ required: true, message: '请输入最高时速' }]"> 112 + <nut-input v-model="formData.max_speed_kmh" placeholder="25" type="number" input-align="right">
112 - <nut-input v-model="formData.maxSpeed" placeholder="25" type="number" input-align="right">
113 <template #right> 113 <template #right>
114 <text class="unit">km/h</text> 114 <text class="unit">km/h</text>
115 </template> 115 </template>
...@@ -121,8 +121,8 @@ ...@@ -121,8 +121,8 @@
121 <!-- 车辆描述 --> 121 <!-- 车辆描述 -->
122 <view class="form-section"> 122 <view class="form-section">
123 <text class="section-title">车辆描述</text> 123 <text class="section-title">车辆描述</text>
124 - <nut-textarea v-model="formData.description" placeholder="请描述车辆详情,如使用感受、车况特点等" :max-length="200" 124 + <nut-textarea v-model="formData.note" placeholder="请描述车辆详情,如使用感受、车况特点等" :max-length="200" :rows="4"
125 - :rows="4" show-word-limit /> 125 + show-word-limit />
126 </view> 126 </view>
127 </view> 127 </view>
128 128
...@@ -134,11 +134,8 @@ ...@@ -134,11 +134,8 @@
134 </view> 134 </view>
135 135
136 <!-- 品牌型号选择器 --> 136 <!-- 品牌型号选择器 -->
137 - <BrandModelPicker 137 + <BrandModelPicker ref="brandModelPickerRef" :brand-options="brandOptions" @confirm="onBrandModelConfirm"
138 - ref="brandModelPickerRef" 138 + @cancel="onBrandModelCancel" />
139 - @confirm="onBrandModelConfirm"
140 - @cancel="onBrandModelCancel"
141 - />
142 </view> 139 </view>
143 </template> 140 </template>
144 141
...@@ -150,6 +147,10 @@ import './index.less' ...@@ -150,6 +147,10 @@ import './index.less'
150 import BASE_URL from '@/utils/config'; 147 import BASE_URL from '@/utils/config';
151 import BrandModelPicker from '@/components/BrandModelPicker.vue' 148 import BrandModelPicker from '@/components/BrandModelPicker.vue'
152 149
150 +// 导入接口
151 +import { getBrandsModelsAPI } from '@/api/other';
152 +import { addVehicleAPI, editVehicleAPI, getVehicleDetailAPI } from '@/api/car';
153 +
153 // 获取页面参数 154 // 获取页面参数
154 const instance = Taro.getCurrentInstance() 155 const instance = Taro.getCurrentInstance()
155 const { id, mode } = instance.router?.params || {} 156 const { id, mode } = instance.router?.params || {}
...@@ -178,14 +179,23 @@ const formData = reactive({ ...@@ -178,14 +179,23 @@ const formData = reactive({
178 brand: '', 179 brand: '',
179 model: '', 180 model: '',
180 brandModel: '', // 品牌型号组合字段,用于表单验证 181 brandModel: '', // 品牌型号组合字段,用于表单验证
181 - range: '', 182 + range_km: '',
182 - maxSpeed: '', 183 + max_speed_kmh: '',
183 - description: '' 184 + note: '',
185 + // 车辆照片字段
186 + front_photo: '',
187 + left_photo: '',
188 + right_photo: '',
189 + other_photo: '',
190 + verification_status: ''
184 }) 191 })
185 192
186 // 品牌型号选择器组件引用 193 // 品牌型号选择器组件引用
187 const brandModelPickerRef = ref(null) 194 const brandModelPickerRef = ref(null)
188 195
196 +// 品牌型号选项数据
197 +const brandOptions = ref([])
198 +
189 199
190 200
191 /** 201 /**
...@@ -236,6 +246,8 @@ const uploadImage = (filePath, type) => { ...@@ -236,6 +246,8 @@ const uploadImage = (filePath, type) => {
236 success: () => { 246 success: () => {
237 if (res.statusCode === 200) { 247 if (res.statusCode === 200) {
238 uploadedImages[type] = upload_data.data.src; 248 uploadedImages[type] = upload_data.data.src;
249 + // 同时更新formData中对应的照片字段
250 + formData[`${type}_photo`] = upload_data.data.src;
239 Taro.showToast({ 251 Taro.showToast({
240 title: '上传成功', 252 title: '上传成功',
241 icon: 'success' 253 icon: 'success'
...@@ -247,10 +259,10 @@ const uploadImage = (filePath, type) => { ...@@ -247,10 +259,10 @@ const uploadImage = (filePath, type) => {
247 mask: true 259 mask: true
248 }) 260 })
249 } 261 }
250 - }, 262 + }
251 }); 263 });
252 }, 264 },
253 - fail: function (res) { 265 + fail: function () {
254 Taro.hideLoading({ 266 Taro.hideLoading({
255 success: () => { 267 success: () => {
256 Taro.showToast({ 268 Taro.showToast({
...@@ -317,69 +329,100 @@ const onBrandModelCancel = () => { ...@@ -317,69 +329,100 @@ const onBrandModelCancel = () => {
317 } 329 }
318 330
319 /** 331 /**
320 - * 提交申请/保存修改 332 + * 加载品牌型号数据
321 */ 333 */
322 -const onSubmit = () => { 334 +const loadBrandsModels = async () => {
323 - // 表单验证 335 + try {
336 + const { code, data } = await getBrandsModelsAPI()
337 + if (code && data) {
338 + // 转换品牌数据格式
339 + brandOptions.value = data.map(brand => ({
340 + text: brand.name,
341 + value: brand.id,
342 + models: brand.models || []
343 + }))
344 + }
345 + } catch (error) {
346 + console.error('加载品牌型号列表失败:', error)
347 + }
348 +}
349 +
350 +/**
351 + * 表单验证
352 + */
353 +const validateForm = () => {
324 if (!formData.brandModel) { 354 if (!formData.brandModel) {
325 Taro.showToast({ 355 Taro.showToast({
326 title: '请选择品牌型号', 356 title: '请选择品牌型号',
327 icon: 'none' 357 icon: 'none'
328 }) 358 })
329 - return 359 + return false
330 } 360 }
331 361
332 - if (!formData.range) { 362 + // if (!formData.range_km) {
333 - Taro.showToast({ 363 + // Taro.showToast({
334 - title: '请输入续航里程', 364 + // title: '请输入续航里程',
335 - icon: 'none' 365 + // icon: 'none'
336 - }) 366 + // })
337 - return 367 + // return false
338 - } 368 + // }
339 369
340 - if (!formData.maxSpeed) { 370 + // if (!formData.max_speed_kmh) {
341 - Taro.showToast({ 371 + // Taro.showToast({
342 - title: '请输入最高时速', 372 + // title: '请输入最高时速',
343 - icon: 'none' 373 + // icon: 'none'
344 - }) 374 + // })
345 - return 375 + // return false
346 - } 376 + // }
347 377
348 // 检查是否至少上传了一张照片 378 // 检查是否至少上传了一张照片
349 - const hasImage = Object.values(uploadedImages).some(img => img) 379 + const hasImage = uploadedImages.front
350 if (!hasImage) { 380 if (!hasImage) {
351 Taro.showToast({ 381 Taro.showToast({
352 title: '请至少上传一张车辆照片', 382 title: '请至少上传一张车辆照片',
353 icon: 'none' 383 icon: 'none'
354 }) 384 })
355 - return 385 + return false
356 } 386 }
357 387
358 - // 提交数据 388 + return true
359 - const submitData = { 389 +}
360 - ...formData,
361 - images: uploadedImages
362 - }
363 390
364 - if (isEditMode.value) { 391 +/**
365 - submitData.id = carId.value 392 + * 提交申请/保存修改
393 + */
394 +const onSubmit = async () => {
395 + // 表单验证
396 + if (!validateForm()) {
397 + return
366 } 398 }
367 399
400 + // 同步图片数据到表单
401 + formData.front_photo = uploadedImages.front
402 + formData.left_photo = uploadedImages.left
403 + formData.right_photo = uploadedImages.right
404 + formData.other_photo = uploadedImages.other
405 +
368 const loadingTitle = isEditMode.value ? '保存中' : '提交中' 406 const loadingTitle = isEditMode.value ? '保存中' : '提交中'
369 Taro.showLoading({ 407 Taro.showLoading({
370 title: loadingTitle, 408 title: loadingTitle,
371 mask: true 409 mask: true
372 }) 410 })
373 411
374 - // TODO: 调用真实API 412 + try {
375 - // if (isEditMode.value) { 413 + // 调用真实API
376 - // updateAuthApplication(carId.value, submitData) 414 + if (isEditMode.value) {
377 - // } else { 415 + const { code } = await editVehicleAPI({ id: carId.value, ...formData })
378 - // submitAuthApplication(submitData) 416 + if (!code) {
379 - // } 417 + throw new Error('更新失败')
418 + }
419 + } else {
420 + const { code } = await addVehicleAPI(formData)
421 + if (!code) {
422 + throw new Error('提交失败')
423 + }
424 + }
380 425
381 - // 模拟提交成功
382 - setTimeout(() => {
383 Taro.hideLoading() 426 Taro.hideLoading()
384 const successTitle = isEditMode.value ? '保存成功' : '申请提交成功' 427 const successTitle = isEditMode.value ? '保存成功' : '申请提交成功'
385 Taro.showToast({ 428 Taro.showToast({
...@@ -391,7 +434,14 @@ const onSubmit = () => { ...@@ -391,7 +434,14 @@ const onSubmit = () => {
391 setTimeout(() => { 434 setTimeout(() => {
392 Taro.navigateBack() 435 Taro.navigateBack()
393 }, 1500) 436 }, 1500)
394 - }, 2000) 437 + } catch (error) {
438 + console.error('提交失败:', error)
439 + Taro.hideLoading()
440 + Taro.showToast({
441 + title: '提交失败,请重试',
442 + icon: 'none'
443 + })
444 + }
395 } 445 }
396 446
397 /** 447 /**
...@@ -403,56 +453,56 @@ const loadAuthData = async () => { ...@@ -403,56 +453,56 @@ const loadAuthData = async () => {
403 try { 453 try {
404 Taro.showLoading({ title: '加载中...' }) 454 Taro.showLoading({ title: '加载中...' })
405 455
406 - // TODO: 调用真实API获取认证数据 456 + // 调用真实API获取车辆数据
407 - // const authData = await getAuthById(carId.value) 457 + const { code, data } = await getVehicleDetailAPI({ id: carId.value })
408 - 458 + if (code && data) {
409 - // 暂时不模拟数据,按用户要求
410 - // 如果需要模拟数据,可以取消下面的注释
411 -
412 - const mockAuthData = {
413 - brand: '小牛电动',
414 - model: 'NGT',
415 - range: '60',
416 - maxSpeed: '25',
417 - description: '车况良好,电池续航正常,无重大事故。',
418 - images: {
419 - front: 'https://picsum.photos/300/200?random=5',
420 - left: 'https://picsum.photos/300/200?random=6',
421 - right: 'https://picsum.photos/300/200?random=7',
422 - other: 'https://picsum.photos/300/200?random=8'
423 - }
424 - }
425 -
426 // 填充表单数据 459 // 填充表单数据
427 Object.assign(formData, { 460 Object.assign(formData, {
428 - brand: mockAuthData.brand, 461 + ...data,
429 - model: mockAuthData.model, 462 + brand: data.brand,
430 - brandModel: `${mockAuthData.brand} ${mockAuthData.model}`, // 设置组合字段 463 + model: data.model,
431 - range: mockAuthData.range, 464 + brandModel: `${data.brand} ${data.model}`,
432 - maxSpeed: mockAuthData.maxSpeed, 465 + range_km: data.range_km,
433 - description: mockAuthData.description 466 + max_speed_kmh: data.max_speed_kmh,
467 + note: data.note,
468 + front_photo: data.front_photo,
469 + left_photo: data.left_photo,
470 + right_photo: data.right_photo,
471 + other_photo: data.other_photo,
472 + verification_status: data.verification_status
434 }) 473 })
435 474
436 // 填充图片数据 475 // 填充图片数据
437 - Object.assign(uploadedImages, mockAuthData.images) 476 + Object.assign(uploadedImages, {
438 - 477 + front: data.front_photo,
439 - 478 + left: data.left_photo,
440 - Taro.hideLoading() 479 + right: data.right_photo,
480 + other: data.other_photo
481 + })
482 + } else {
483 + throw new Error('获取车辆数据失败')
484 + }
441 } catch (error) { 485 } catch (error) {
442 - console.error('加载认证数据失败:', error) 486 + console.error('加载车辆数据失败:', error)
443 - Taro.hideLoading()
444 Taro.showToast({ 487 Taro.showToast({
445 title: '加载数据失败', 488 title: '加载数据失败',
446 icon: 'none' 489 icon: 'none'
447 }) 490 })
491 +
492 + } finally {
493 + Taro.hideLoading()
448 } 494 }
449 } 495 }
450 496
451 // 页面加载时执行 497 // 页面加载时执行
452 -onMounted(() => { 498 +onMounted(async () => {
499 + // 并行加载品牌型号数据
500 + await loadBrandsModels()
501 +
453 if (isEditMode.value) { 502 if (isEditMode.value) {
454 loadAuthData() 503 loadAuthData()
455 } 504 }
505 +
456 // 动态修改标题 506 // 动态修改标题
457 wx.setNavigationBarTitle({ 507 wx.setNavigationBarTitle({
458 title: isEditMode.value ? '编辑认证' : '发布认证' 508 title: isEditMode.value ? '编辑认证' : '发布认证'
......