hookehuyr

feat(品牌型号选择器): 添加品牌型号搜索功能并优化选择流程

- 在BrandModelPicker组件中添加搜索功能,支持按品牌名称过滤
- 将品牌和型号选择合并为单一流程,简化用户操作
- 更新setAuthCar页面使用新的品牌型号选择器组件
- 移除旧的独立品牌和型号选择器
...@@ -4,9 +4,18 @@ ...@@ -4,9 +4,18 @@
4 <nut-popup v-model:visible="brandPickerVisible" position="bottom" :style="{ height: '80%' }"> 4 <nut-popup v-model:visible="brandPickerVisible" position="bottom" :style="{ height: '80%' }">
5 <view class="brand-model-picker"> 5 <view class="brand-model-picker">
6 <view class="picker-header"> 6 <view class="picker-header">
7 - <text class="picker-title">选择品牌</text> 7 + <text class="picker-title">选择品牌型号</text>
8 <nut-button size="small" type="primary" @click="closePicker">关闭</nut-button> 8 <nut-button size="small" type="primary" @click="closePicker">关闭</nut-button>
9 </view> 9 </view>
10 + <view class="search-container">
11 + <nut-searchbar
12 + v-model="searchKeyword"
13 + placeholder="搜索品牌名称"
14 + shape="round"
15 + background="transparent"
16 + class="search-bar"
17 + />
18 + </view>
10 <view class="brand-container"> 19 <view class="brand-container">
11 <scroll-view 20 <scroll-view
12 class="brand-content" 21 class="brand-content"
...@@ -16,7 +25,7 @@ ...@@ -16,7 +25,7 @@
16 > 25 >
17 <view class="brand-list"> 26 <view class="brand-list">
18 <view 27 <view
19 - v-for="brand in allBrands" 28 + v-for="brand in filteredBrands"
20 :key="brand.value" 29 :key="brand.value"
21 class="brand-item" 30 class="brand-item"
22 @click="selectBrand(brand)" 31 @click="selectBrand(brand)"
...@@ -59,7 +68,7 @@ ...@@ -59,7 +68,7 @@
59 </template> 68 </template>
60 69
61 <script setup> 70 <script setup>
62 -import { ref } from 'vue' 71 +import { ref, computed } from 'vue'
63 import { Right } from '@nutui/icons-vue-taro' 72 import { Right } from '@nutui/icons-vue-taro'
64 73
65 // 定义事件 74 // 定义事件
...@@ -71,6 +80,7 @@ const modelPickerVisible = ref(false) ...@@ -71,6 +80,7 @@ const modelPickerVisible = ref(false)
71 const selectedBrandName = ref('') 80 const selectedBrandName = ref('')
72 const currentBrandModels = ref([]) 81 const currentBrandModels = ref([])
73 const contentHeight = ref(800) // 默认高度 rpx 82 const contentHeight = ref(800) // 默认高度 rpx
83 +const searchKeyword = ref('') // 搜索关键词
74 84
75 const allBrands = ref([ 85 const allBrands = ref([
76 { text: '爱玛', value: 'aima' }, 86 { text: '爱玛', value: 'aima' },
...@@ -189,6 +199,16 @@ const brandModelMap = ref({ ...@@ -189,6 +199,16 @@ const brandModelMap = ref({
189 ] 199 ]
190 }) 200 })
191 201
202 +// 过滤后的品牌列表
203 +const filteredBrands = computed(() => {
204 + if (!searchKeyword.value.trim()) {
205 + return allBrands.value
206 + }
207 + return allBrands.value.filter(brand =>
208 + brand.text.toLowerCase().includes(searchKeyword.value.toLowerCase())
209 + )
210 +})
211 +
192 // 计算内容高度 212 // 计算内容高度
193 const calculateContentHeight = () => { 213 const calculateContentHeight = () => {
194 // 弹框总高度 - 头部高度 214 // 弹框总高度 - 头部高度
...@@ -200,6 +220,7 @@ const calculateContentHeight = () => { ...@@ -200,6 +220,7 @@ const calculateContentHeight = () => {
200 // 显示品牌选择器 220 // 显示品牌选择器
201 const show = () => { 221 const show = () => {
202 calculateContentHeight() // 计算内容高度 222 calculateContentHeight() // 计算内容高度
223 + searchKeyword.value = '' // 清空搜索关键词
203 brandPickerVisible.value = true 224 brandPickerVisible.value = true
204 } 225 }
205 226
...@@ -276,6 +297,58 @@ defineExpose({ ...@@ -276,6 +297,58 @@ defineExpose({
276 } 297 }
277 } 298 }
278 299
300 + .search-container {
301 + padding: 0 0 24rpx 0;
302 + margin-bottom: 20rpx;
303 +
304 + .search-bar {
305 + // background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
306 + border-radius: 20rpx;
307 + padding: 4rpx;
308 + // box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.15);
309 + transition: all 0.3s ease;
310 +
311 + &:hover {
312 + box-shadow: 0 12rpx 32rpx rgba(102, 126, 234, 0.25);
313 + transform: translateY(-2rpx);
314 + }
315 +
316 + :deep(.nut-searchbar__search-input) {
317 + background-color: white;
318 + border-radius: 16rpx;
319 + border: none;
320 + padding: 24rpx 20rpx;
321 + font-size: 28rpx;
322 + color: #333;
323 + transition: all 0.3s ease;
324 +
325 + &::placeholder {
326 + color: #999;
327 + font-weight: 400;
328 + }
329 +
330 + &:focus {
331 + background-color: #fafbfc;
332 + box-shadow: inset 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
333 + }
334 + }
335 +
336 + :deep(.nut-searchbar__search-icon) {
337 + color: #666;
338 + width: 32rpx;
339 + height: 32rpx;
340 + }
341 +
342 + :deep(.nut-searchbar__input-clear) {
343 + color: #ccc;
344 +
345 + &:hover {
346 + color: #999;
347 + }
348 + }
349 + }
350 + }
351 +
279 .brand-container { 352 .brand-container {
280 flex: 1; 353 flex: 1;
281 height: 100%; 354 height: 100%;
......
...@@ -722,11 +722,11 @@ const onBrandModelConfirm = (result) => { ...@@ -722,11 +722,11 @@ const onBrandModelConfirm = (result) => {
722 // 设置组合字段用于表单验证 722 // 设置组合字段用于表单验证
723 formData.brandModel = `${result.brand} ${result.model}` 723 formData.brandModel = `${result.brand} ${result.model}`
724 724
725 - Taro.showToast({ 725 + // Taro.showToast({
726 - title: `已选择 ${result.brand} ${result.model}`, 726 + // title: `已选择 ${result.brand} ${result.model}`,
727 - icon: 'success', 727 + // icon: 'success',
728 - duration: 2000 728 + // duration: 2000
729 - }) 729 + // })
730 } 730 }
731 731
732 /** 732 /**
......
...@@ -86,20 +86,13 @@ ...@@ -86,20 +86,13 @@
86 <!-- 车辆详情表单 --> 86 <!-- 车辆详情表单 -->
87 <nut-form ref="formRef" :model-value="formData"> 87 <nut-form ref="formRef" :model-value="formData">
88 <view class="form-section"> 88 <view class="form-section">
89 - <!-- 车型品牌 --> 89 + <!-- 品牌型号选择 -->
90 - <nut-form-item label-position="top" label="车型品牌" prop="brand" required 90 + <nut-form-item label-position="top" label="品牌型号选择" prop="brandModel" required
91 - :rules="[{ required: true, message: '请选择车型品牌' }]"> 91 + :rules="[{ required: true, message: '请选择品牌型号' }]">
92 - <view class="form-item-content" @click="showBrandPicker"> 92 + <view class="form-item-content" @click="showBrandModelPicker">
93 - <text class="form-value">{{ formData.brand || '请选择' }}</text> 93 + <text class="form-value">
94 - <Right class="arrow-icon" /> 94 + {{ formData.brand && formData.model ? `${formData.brand} ${formData.model}` : '请选择品牌型号' }}
95 - </view> 95 + </text>
96 - </nut-form-item>
97 -
98 - <!-- 车辆型号 -->
99 - <nut-form-item label-position="top" label="车辆型号" prop="model" required
100 - :rules="[{ required: true, message: '请选择车辆型号' }]">
101 - <view class="form-item-content" @click="showModelPicker">
102 - <text class="form-value">{{ formData.model || '请选择' }}</text>
103 <Right class="arrow-icon" /> 96 <Right class="arrow-icon" />
104 </view> 97 </view>
105 </nut-form-item> 98 </nut-form-item>
...@@ -140,18 +133,12 @@ ...@@ -140,18 +133,12 @@
140 </nut-button> 133 </nut-button>
141 </view> 134 </view>
142 135
143 - <!-- 选择器弹窗 --> 136 + <!-- 品牌型号选择器 -->
144 - <!-- 品牌选择 --> 137 + <BrandModelPicker
145 - <nut-popup v-model:visible="brandPickerVisible" position="bottom"> 138 + ref="brandModelPickerRef"
146 - <nut-picker v-model="brandValue" :columns="brandOptions" title="选择车型品牌" @confirm="onBrandConfirm" 139 + @confirm="onBrandModelConfirm"
147 - @cancel="brandPickerVisible = false" /> 140 + @cancel="onBrandModelCancel"
148 - </nut-popup> 141 + />
149 -
150 - <!-- 型号选择 -->
151 - <nut-popup v-model:visible="modelPickerVisible" position="bottom">
152 - <nut-picker v-model="modelValue" :columns="modelOptions" title="选择车辆型号" @confirm="onModelConfirm"
153 - @cancel="modelPickerVisible = false" />
154 - </nut-popup>
155 </view> 142 </view>
156 </template> 143 </template>
157 144
...@@ -161,6 +148,7 @@ import { Plus, Right, Close } from '@nutui/icons-vue-taro' ...@@ -161,6 +148,7 @@ import { Plus, Right, Close } from '@nutui/icons-vue-taro'
161 import Taro from '@tarojs/taro' 148 import Taro from '@tarojs/taro'
162 import './index.less' 149 import './index.less'
163 import BASE_URL from '@/utils/config'; 150 import BASE_URL from '@/utils/config';
151 +import BrandModelPicker from '@/components/BrandModelPicker.vue'
164 152
165 // 获取页面参数 153 // 获取页面参数
166 const instance = Taro.getCurrentInstance() 154 const instance = Taro.getCurrentInstance()
...@@ -189,40 +177,14 @@ const closePreview = () => { ...@@ -189,40 +177,14 @@ const closePreview = () => {
189 const formData = reactive({ 177 const formData = reactive({
190 brand: '', 178 brand: '',
191 model: '', 179 model: '',
180 + brandModel: '', // 品牌型号组合字段,用于表单验证
192 range: '', 181 range: '',
193 maxSpeed: '', 182 maxSpeed: '',
194 description: '' 183 description: ''
195 }) 184 })
196 185
197 -// 选择器显示状态 186 +// 品牌型号选择器组件引用
198 -const brandPickerVisible = ref(false) 187 +const brandModelPickerRef = ref(null)
199 -const modelPickerVisible = ref(false)
200 -
201 -// 选择器值
202 -const brandValue = ref([])
203 -const modelValue = ref([])
204 -
205 -// 选择器选项数据
206 -const brandOptions = ref([
207 - { text: '小牛电动', value: '小牛电动' },
208 - { text: '雅迪', value: '雅迪' },
209 - { text: '爱玛', value: '爱玛' },
210 - { text: '台铃', value: '台铃' },
211 - { text: '绿源', value: '绿源' },
212 - { text: '新日', value: '新日' },
213 - { text: '立马', value: '立马' },
214 - { text: '其他', value: '其他' }
215 -])
216 -
217 -const modelOptions = ref([
218 - { text: 'NGT', value: 'NGT' },
219 - { text: 'UQi+', value: 'UQi+' },
220 - { text: 'Gova G2', value: 'Gova G2' },
221 - { text: 'MQi+', value: 'MQi+' },
222 - { text: 'NQi GTS', value: 'NQi GTS' },
223 - { text: 'RQi', value: 'RQi' },
224 - { text: '其他型号', value: '其他型号' }
225 -])
226 188
227 189
228 190
...@@ -325,35 +287,33 @@ const previewImage = (imageUrl) => { ...@@ -325,35 +287,33 @@ const previewImage = (imageUrl) => {
325 } 287 }
326 288
327 /** 289 /**
328 - * 显示品牌选择器 290 + * 显示品牌型号选择器
329 - */
330 -const showBrandPicker = () => {
331 - brandPickerVisible.value = true
332 -}
333 -
334 -/**
335 - * 显示型号选择器
336 */ 291 */
337 -const showModelPicker = () => { 292 +const showBrandModelPicker = () => {
338 - modelPickerVisible.value = true 293 + brandModelPickerRef.value?.show()
339 } 294 }
340 295
341 /** 296 /**
342 - * 品牌选择确认 297 + * 品牌型号选择确认
343 - * @param {Object} options - 选择的选项 298 + * @param {Object} data - 选择的品牌和型号数据
344 */ 299 */
345 -const onBrandConfirm = (options) => { 300 +const onBrandModelConfirm = (data) => {
346 - formData.brand = options.selectedOptions[0]?.text || '' 301 + formData.brand = data.brand
347 - brandPickerVisible.value = false 302 + formData.model = data.model
303 + formData.brandModel = `${data.brand} ${data.model}` // 设置组合字段用于表单验证
304 +
305 + // Taro.showToast({
306 + // title: `已选择 ${data.brand} ${data.model}`,
307 + // icon: 'success',
308 + // duration: 2000
309 + // })
348 } 310 }
349 311
350 /** 312 /**
351 - * 型号选择确认 313 + * 品牌型号选择取消回调
352 - * @param {Object} options - 选择的选项
353 */ 314 */
354 -const onModelConfirm = (options) => { 315 +const onBrandModelCancel = () => {
355 - formData.model = options.selectedOptions[0]?.text || '' 316 + // 可以在这里处理取消逻辑
356 - modelPickerVisible.value = false
357 } 317 }
358 318
359 /** 319 /**
...@@ -361,17 +321,9 @@ const onModelConfirm = (options) => { ...@@ -361,17 +321,9 @@ const onModelConfirm = (options) => {
361 */ 321 */
362 const onSubmit = () => { 322 const onSubmit = () => {
363 // 表单验证 323 // 表单验证
364 - if (!formData.brand) { 324 + if (!formData.brandModel) {
365 - Taro.showToast({
366 - title: '请选择车型品牌',
367 - icon: 'none'
368 - })
369 - return
370 - }
371 -
372 - if (!formData.model) {
373 Taro.showToast({ 325 Taro.showToast({
374 - title: '请选择车辆型号', 326 + title: '请选择品牌型号',
375 icon: 'none' 327 icon: 'none'
376 }) 328 })
377 return 329 return
...@@ -475,6 +427,7 @@ const loadAuthData = async () => { ...@@ -475,6 +427,7 @@ const loadAuthData = async () => {
475 Object.assign(formData, { 427 Object.assign(formData, {
476 brand: mockAuthData.brand, 428 brand: mockAuthData.brand,
477 model: mockAuthData.model, 429 model: mockAuthData.model,
430 + brandModel: `${mockAuthData.brand} ${mockAuthData.model}`, // 设置组合字段
478 range: mockAuthData.range, 431 range: mockAuthData.range,
479 maxSpeed: mockAuthData.maxSpeed, 432 maxSpeed: mockAuthData.maxSpeed,
480 description: mockAuthData.description 433 description: mockAuthData.description
......