hookehuyr

feat(品牌型号选择): 添加自定义品牌型号输入功能

refactor(车辆发布): 移除电池损耗度选择并优化表单验证
- 将磨损程度文字与数字分数相互转换
- 直接使用表单数据存储照片URL
- 简化品牌型号选择验证逻辑
......@@ -65,6 +65,47 @@
</view>
</view>
</nut-popup>
<!-- 自定义输入弹框 -->
<nut-popup v-model:visible="customInputVisible" position="bottom" :style="{ height: '60%' }">
<view class="custom-input-picker">
<view class="picker-header">
<nut-button size="small" @click="goBackToBrandPicker">返回</nut-button>
<text class="picker-title">自定义品牌型号</text>
<nut-button size="small" type="primary" @click="closePicker">关闭</nut-button>
</view>
<view class="custom-input-content">
<view class="input-group">
<text class="input-label">品牌名称</text>
<nut-input
v-model="customBrand"
placeholder="请输入品牌名称"
class="custom-input"
/>
</view>
<view class="input-group">
<text class="input-label">型号名称</text>
<nut-input
v-model="customModel"
placeholder="请输入型号名称"
class="custom-input"
/>
</view>
<view class="button-group">
<nut-button
type="primary"
size="large"
color="orange"
@click="confirmCustomInput"
:disabled="!customBrand.trim() || !customModel.trim()"
class="confirm-button"
>
确认
</nut-button>
</view>
</view>
</view>
</nut-popup>
</view>
</template>
......@@ -80,10 +121,13 @@ const emit = defineEmits(['confirm', 'cancel'])
// 响应式数据
const brandPickerVisible = ref(false)
const modelPickerVisible = ref(false)
const customInputVisible = ref(false)
const selectedBrandName = ref('')
const currentBrandModels = ref([])
const contentHeight = ref(800) // 默认高度 rpx
const searchKeyword = ref('') // 搜索关键词
const customBrand = ref('') // 自定义品牌
const customModel = ref('') // 自定义型号
const allBrands = ref([
{ text: '爱玛', value: 'aima' },
......@@ -105,7 +149,8 @@ const allBrands = ref([
{ text: '小刀', value: 'xiaodao' },
{ text: '速派奇', value: 'supaiqi' },
{ text: '金箭', value: 'jinjian' },
{ text: '森蓝', value: 'senlan' }
{ text: '森蓝', value: 'senlan' },
{ text: '其他', value: 'other' }
])
// 品牌型号映射
......@@ -261,10 +306,19 @@ const show = () => {
// 选择品牌
const selectBrand = (brand) => {
selectedBrandName.value = brand.text
currentBrandModels.value = brandModelMap.value[brand.value] || []
brandPickerVisible.value = false
modelPickerVisible.value = true
if (brand.value === 'other') {
// 选择其他,显示自定义输入弹窗
brandPickerVisible.value = false
customInputVisible.value = true
// 清空之前的自定义输入
customBrand.value = ''
customModel.value = ''
} else {
selectedBrandName.value = brand.text
currentBrandModels.value = brandModelMap.value[brand.value] || []
brandPickerVisible.value = false
modelPickerVisible.value = true
}
}
// 选择型号
......@@ -283,9 +337,32 @@ const selectModel = (model) => {
closeAllPickers()
}
/**
* 确认自定义输入
*/
const confirmCustomInput = () => {
if (!customBrand.value.trim() || !customModel.value.trim()) {
return
}
const result = {
brand: customBrand.value.trim(),
model: customModel.value.trim(),
brandValue: customBrand.value.trim(),
modelValue: customModel.value.trim()
}
// 发送确认事件
emit('confirm', result)
// 关闭所有弹框
closeAllPickers()
}
// 返回品牌选择
const goBackToBrandPicker = () => {
modelPickerVisible.value = false
customInputVisible.value = false
brandPickerVisible.value = true
}
......@@ -299,8 +376,11 @@ const closePicker = () => {
const closeAllPickers = () => {
brandPickerVisible.value = false
modelPickerVisible.value = false
customInputVisible.value = false
selectedBrandName.value = ''
currentBrandModels.value = []
customBrand.value = ''
customModel.value = ''
}
// 组件挂载时初始化
......@@ -503,4 +583,70 @@ defineExpose({
}
}
}
/* 自定义输入弹窗样式 */
.custom-input-picker {
padding: 20rpx;
height: 100%;
display: flex;
flex-direction: column;
.picker-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
margin-bottom: 20rpx;
.picker-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
}
.custom-input-content {
flex: 1;
display: flex;
flex-direction: column;
.input-group {
margin-bottom: 40rpx;
.input-label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 20rpx;
font-weight: 500;
}
.custom-input {
width: 100%;
height: 80rpx;
border: 1rpx solid #e0e0e0;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
background: #fff;
&:focus {
border-color: #1890ff;
}
}
}
.button-group {
margin-top: auto;
padding-top: 40rpx;
.confirm-button {
width: 100%;
height: 80rpx;
border-radius: 8rpx;
}
}
}
}
</style>
......
......@@ -121,8 +121,7 @@
</nut-form-item>-->
<!-- 品牌型号选择(新版) -->
<nut-form-item label-position="top" label="品牌型号选择" prop="brandModel" required
:rules="[{ required: true, message: '请选择品牌型号' }]">
<nut-form-item label-position="top" label="品牌型号选择" prop="brandModel">
<view class="form-item-content" @click="showBrandModelPicker">
<text class="form-value">
{{ formData.brand && formData.model ? `${formData.brand} ${formData.model}` : '请选择品牌型号' }}
......@@ -295,10 +294,10 @@
</nut-popup>
<!-- 电池损耗度选择 -->
<nut-popup v-model:visible="batteryWearPickerVisible" position="bottom">
<!-- <nut-popup v-model:visible="batteryWearPickerVisible" position="bottom">
<nut-picker v-model="batteryWearValue" :columns="wearLevelOptions" title="选择电池损耗度"
@confirm="onBatteryWearConfirm" @cancel="batteryWearPickerVisible = false" />
</nut-popup>
</nut-popup> -->
<!-- 刹车磨损度选择 -->
<nut-popup v-model:visible="brakeWearPickerVisible" position="bottom">
......@@ -324,7 +323,8 @@ import { checkPermission, PERMISSION_TYPES } from '@/utils/permission'
import './index.less'
// 导入接口
import { addVehicleAPI, editVehicleAPI, getVehicleDetailAPI } from '@/api/car';
// import { addVehicleAPI, editVehicleAPI, getVehicleDetailAPI } from '@/api/car';
import { addVehicleAPI } from '@/api/car';
const themeVars = ref({
navbarBackground: '#fb923c',
......@@ -377,12 +377,17 @@ const formData = reactive({
range_km: '60',
maxSpeed: '25',
battery_capacity_ah: '20',
batteryWear: '',
// batteryWear: '',
brake_wear_level: '',
tire_wear_level: '',
price: '3200',
market_price: '6500',
note: ''
note: '',
// 车辆照片字段
front_photo: '',
left_photo: '',
right_photo: '',
other_photo: ''
})
// 选择器显示状态
......@@ -391,7 +396,7 @@ const brandPickerVisible = ref(false)
const modelPickerVisible = ref(false)
const yearPickerVisible = ref(false)
const conditionPickerVisible = ref(false)
const batteryWearPickerVisible = ref(false)
// const batteryWearPickerVisible = ref(false)
const brakeWearPickerVisible = ref(false)
const tireWearPickerVisible = ref(false)
......@@ -405,7 +410,7 @@ const brandValue = ref([])
const modelValue = ref([])
const yearValue = ref(new Date())
const conditionValue = ref([])
const batteryWearValue = ref([])
// const batteryWearValue = ref([])
const brakeWearValue = ref([])
const tireWearValue = ref([])
......@@ -508,6 +513,8 @@ const uploadImage = (filePath, type) => {
success: () => {
if (res.statusCode === 200) {
uploadedImages[type] = upload_data.data.src;
// 同时更新formData中对应的照片字段
formData[`${type}_photo`] = upload_data.data.src;
Taro.showToast({
title: '上传成功',
icon: 'success'
......@@ -552,6 +559,8 @@ const previewImage = (imageUrl) => {
*/
const deleteImage = (type) => {
uploadedImages[type] = ''
// 同时清空formData中对应的照片字段
formData[`${type}_photo`] = ''
Taro.showToast({
title: '删除成功',
icon: 'success'
......@@ -665,10 +674,10 @@ const onConditionConfirm = ({ selectedValue }) => {
/**
* 电池损耗度选择确认
*/
const onBatteryWearConfirm = ({ selectedValue }) => {
formData.batteryWear = selectedValue[0]
batteryWearPickerVisible.value = false
}
// const onBatteryWearConfirm = ({ selectedValue }) => {
// formData.batteryWear = selectedValue[0]
// batteryWearPickerVisible.value = false
// }
/**
* 刹车磨损度选择确认
......@@ -716,6 +725,58 @@ const onBrandModelCancel = () => {
// 可以在这里处理取消逻辑
}
/**
* 将文字转换为对应的数字分数
*/
const convertTextToScore = (text, type) => {
if (type === 'condition') {
const conditionMap = {
'全新': 5,
'8成新': 4,
'7成新': 3,
'6成新': 2,
'5成新及以下': 1
}
return conditionMap[text] || 0
} else if (type === 'wear') {
const wearMap = {
'全新': 5,
'轻微磨损': 4,
'中度磨损': 3,
'重度磨损': 2,
'需要更换': 1
}
return wearMap[text] || 0
}
return 0
}
/**
* 将数字分数转换为对应的文字
*/
const convertScoreToText = (score, type) => {
if (type === 'condition') {
const scoreMap = {
5: '全新',
4: '8成新',
3: '7成新',
2: '6成新',
1: '5成新及以下'
}
return scoreMap[score] || ''
} else if (type === 'wear') {
const scoreMap = {
5: '全新',
4: '轻微磨损',
3: '中度磨损',
2: '重度磨损',
1: '需要更换'
}
return scoreMap[score] || ''
}
return ''
}
const createCar = async (data) => {
const { code } = await addVehicleAPI({ ...data })
if (code) {
......@@ -747,19 +808,12 @@ const onPublish = async () => {
const loadingTitle = isEditMode.value ? '保存中...' : '发布中...'
Taro.showLoading({ title: loadingTitle })
// 收集所有上传的图片URL
const images = {
front: uploadedImages.front || '',
left: uploadedImages.left || '',
right: uploadedImages.right || '',
other: uploadedImages.other || ''
}
// 构建提交数据
// 构建提交数据,将文字转换为数字
const submitData = {
...formData,
images: images,
photo_meta_ids: Object.values(images).filter(url => url) // 过滤空URL
new_level: convertTextToScore(formData.new_level, 'condition'),
brake_wear_level: convertTextToScore(formData.brake_wear_level, 'wear'),
tire_wear_level: convertTextToScore(formData.tire_wear_level, 'wear')
}
console.warn(submitData);
......@@ -801,16 +855,16 @@ const onPublish = async () => {
*/
const validateForm = () => {
// 检查是否至少上传了一张图片
const hasImages = uploadedImages.front || uploadedImages.left || uploadedImages.right || uploadedImages.other
const hasImages = formData.front_photo || formData.left_photo || formData.right_photo || formData.other_photo
if (!hasImages) {
Taro.showToast({ title: '请上传车辆图片', icon: 'none' })
return false
}
if (!formData.brand) {
Taro.showToast({ title: '请选择车型品牌', icon: 'none' })
return false
}
// if (!formData.brand) {
// Taro.showToast({ title: '请选择车型品牌', icon: 'none' })
// return false
// }
// if (!formData.new_level) {
// Taro.showToast({ title: '请选择新旧程度', icon: 'none' })
......@@ -853,7 +907,7 @@ const loadCarData = async () => {
range_km: '60',
maxSpeed: '25',
battery_capacity_ah: '20',
batteryWear: '轻微磨损',
// batteryWear: '轻微磨损',
brake_wear_level: '轻微磨损',
tire_wear_level: '轻微磨损',
price: '3200',
......@@ -867,20 +921,20 @@ const loadCarData = async () => {
}
}
// 填充表单数据
// 填充表单数据,如果是数字则转换为文字
Object.assign(formData, {
school: mockCarData.school,
brand: mockCarData.brand,
model: mockCarData.model,
manufacture_year: mockCarData.manufacture_year,
new_level: mockCarData.new_level,
new_level: typeof mockCarData.new_level === 'number' ? convertScoreToText(mockCarData.new_level, 'condition') : mockCarData.new_level,
total_mileage_km: mockCarData.total_mileage_km,
range_km: mockCarData.range_km,
maxSpeed: mockCarData.maxSpeed,
battery_capacity_ah: mockCarData.battery_capacity_ah,
batteryWear: mockCarData.batteryWear,
brake_wear_level: mockCarData.brake_wear_level,
tire_wear_level: mockCarData.tire_wear_level,
// batteryWear: mockCarData.batteryWear,
brake_wear_level: typeof mockCarData.brake_wear_level === 'number' ? convertScoreToText(mockCarData.brake_wear_level, 'wear') : mockCarData.brake_wear_level,
tire_wear_level: typeof mockCarData.tire_wear_level === 'number' ? convertScoreToText(mockCarData.tire_wear_level, 'wear') : mockCarData.tire_wear_level,
price: mockCarData.price,
market_price: mockCarData.market_price,
note: mockCarData.note
......@@ -888,6 +942,11 @@ const loadCarData = async () => {
// 填充图片数据
Object.assign(uploadedImages, mockCarData.images)
// 同时填充formData中的照片字段
formData.front_photo = mockCarData.images.front || ''
formData.left_photo = mockCarData.images.left || ''
formData.right_photo = mockCarData.images.right || ''
formData.other_photo = mockCarData.images.other || ''
Taro.hideLoading()
} catch (error) {
......