hookehuyr

feat(发布页): 重构车辆图片上传组件并添加图片预览功能

- 将原有的通用上传组件替换为分类上传网格布局
- 为每种车辆照片类型添加独立的上传和删除功能
- 新增图片预览组件支持点击查看大图
- 优化表单样式和布局
- 移除不再使用的NutUploader组件声明
...@@ -12,6 +12,7 @@ declare module 'vue' { ...@@ -12,6 +12,7 @@ declare module 'vue' {
12 NutConfigProvider: typeof import('@nutui/nutui-taro')['ConfigProvider'] 12 NutConfigProvider: typeof import('@nutui/nutui-taro')['ConfigProvider']
13 NutForm: typeof import('@nutui/nutui-taro')['Form'] 13 NutForm: typeof import('@nutui/nutui-taro')['Form']
14 NutFormItem: typeof import('@nutui/nutui-taro')['FormItem'] 14 NutFormItem: typeof import('@nutui/nutui-taro')['FormItem']
15 + NutImagePreview: typeof import('@nutui/nutui-taro')['ImagePreview']
15 NutInput: typeof import('@nutui/nutui-taro')['Input'] 16 NutInput: typeof import('@nutui/nutui-taro')['Input']
16 NutMenu: typeof import('@nutui/nutui-taro')['Menu'] 17 NutMenu: typeof import('@nutui/nutui-taro')['Menu']
17 NutMenuItem: typeof import('@nutui/nutui-taro')['MenuItem'] 18 NutMenuItem: typeof import('@nutui/nutui-taro')['MenuItem']
...@@ -22,7 +23,6 @@ declare module 'vue' { ...@@ -22,7 +23,6 @@ declare module 'vue' {
22 NutSwiper: typeof import('@nutui/nutui-taro')['Swiper'] 23 NutSwiper: typeof import('@nutui/nutui-taro')['Swiper']
23 NutSwiperItem: typeof import('@nutui/nutui-taro')['SwiperItem'] 24 NutSwiperItem: typeof import('@nutui/nutui-taro')['SwiperItem']
24 NutTextarea: typeof import('@nutui/nutui-taro')['Textarea'] 25 NutTextarea: typeof import('@nutui/nutui-taro')['Textarea']
25 - NutUploader: typeof import('@nutui/nutui-taro')['Uploader']
26 Picker: typeof import('./src/components/time-picker-data/picker.vue')['default'] 26 Picker: typeof import('./src/components/time-picker-data/picker.vue')['default']
27 PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default'] 27 PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default']
28 RouterLink: typeof import('vue-router')['RouterLink'] 28 RouterLink: typeof import('vue-router')['RouterLink']
......
...@@ -2,15 +2,11 @@ ...@@ -2,15 +2,11 @@
2 <view class="sell-page"> 2 <view class="sell-page">
3 <!-- 顶部导航 --> 3 <!-- 顶部导航 -->
4 <nut-config-provider :theme-vars="themeVars"> 4 <nut-config-provider :theme-vars="themeVars">
5 - <nut-navbar 5 + <nut-navbar title="发布车源" left-show @on-click-back="goBack">
6 - title="发布车源" 6 + <template #left-show>
7 - left-show 7 + <RectLeft />
8 - @on-click-back="goBack" 8 + </template>
9 - > 9 + </nut-navbar>
10 - <template #left-show>
11 - <RectLeft />
12 - </template>
13 - </nut-navbar>
14 </nut-config-provider> 10 </nut-config-provider>
15 11
16 <!-- 表单内容 --> 12 <!-- 表单内容 -->
...@@ -18,27 +14,74 @@ ...@@ -18,27 +14,74 @@
18 <!-- 车辆照片上传 --> 14 <!-- 车辆照片上传 -->
19 <view class="form-section"> 15 <view class="form-section">
20 <text class="section-title">添加车辆照片</text> 16 <text class="section-title">添加车辆照片</text>
21 - <nut-uploader 17 + <view class="upload-grid">
22 - v-model:file-list="fileList" 18 + <!-- 正面照 -->
23 - :maximum="4" 19 + <view class="upload-item">
24 - :multiple="true" 20 + <!-- 上传按钮 -->
25 - :preview-type="'picture'" 21 + <view v-if="!uploadedImages.front" class="upload-button" @click="triggerUpload('front')">
26 - @oversize="onOversize" 22 + <Plus class="upload-icon" />
27 - @delete="onDelete" 23 + </view>
28 - > 24 + <!-- 图片预览 -->
29 - <nut-button type="success" size="small"> 25 + <view v-else class="image-preview" @click="previewImage(uploadedImages.front)">
30 - <template #icon> 26 + <image :src="uploadedImages.front" class="preview-image" mode="aspectFill" />
31 - <Plus /> 27 + <view class="delete-btn" @click.stop="deleteImage('front')">
32 - </template> 28 + <Close class="delete-icon" />
33 - 上传文件 29 + </view>
34 - </nut-button> 30 + </view>
35 - </nut-uploader> 31 + <text class="upload-label">正面照</text>
36 - <view class="upload-tips"> 32 + </view>
37 - <text class="tip-item">正面照</text> 33 +
38 - <text class="tip-item">左侧照</text> 34 + <!-- 左侧照 -->
39 - <text class="tip-item">右侧照</text> 35 + <view class="upload-item">
40 - <text class="tip-item">其他</text> 36 + <!-- 上传按钮 -->
37 + <view v-if="!uploadedImages.left" class="upload-button" @click="triggerUpload('left')">
38 + <Plus class="upload-icon" />
39 + </view>
40 + <!-- 图片预览 -->
41 + <view v-else class="image-preview" @click="previewImage(uploadedImages.left)">
42 + <image :src="uploadedImages.left" class="preview-image" mode="aspectFill" />
43 + <view class="delete-btn" @click.stop="deleteImage('left')">
44 + <Close class="delete-icon" />
45 + </view>
46 + </view>
47 + <text class="upload-label">左侧照</text>
48 + </view>
49 +
50 + <!-- 右侧照 -->
51 + <view class="upload-item">
52 + <!-- 上传按钮 -->
53 + <view v-if="!uploadedImages.right" class="upload-button" @click="triggerUpload('right')">
54 + <Plus class="upload-icon" />
55 + </view>
56 + <!-- 图片预览 -->
57 + <view v-else class="image-preview" @click="previewImage(uploadedImages.right)">
58 + <image :src="uploadedImages.right" class="preview-image" mode="aspectFill" />
59 + <view class="delete-btn" @click.stop="deleteImage('right')">
60 + <Close class="delete-icon" />
61 + </view>
62 + </view>
63 + <text class="upload-label">右侧照</text>
64 + </view>
65 +
66 + <!-- 其他 -->
67 + <view class="upload-item">
68 + <!-- 上传按钮 -->
69 + <view v-if="!uploadedImages.other" class="upload-button" @click="triggerUpload('other')">
70 + <Plus class="upload-icon" />
71 + </view>
72 + <!-- 图片预览 -->
73 + <view v-else class="image-preview" @click="previewImage(uploadedImages.other)">
74 + <image :src="uploadedImages.other" class="preview-image" mode="aspectFill" />
75 + <view class="delete-btn" @click.stop="deleteImage('other')">
76 + <Close class="delete-icon" />
77 + </view>
78 + </view>
79 + <text class="upload-label">其他</text>
80 + </view>
41 </view> 81 </view>
82 +
83 + <!-- 图片预览组件 -->
84 + <nut-image-preview v-model:show="previewVisible" :images="previewImages" :init-no="previewIndex" />
42 </view> 85 </view>
43 86
44 <!-- 所在学校 --> 87 <!-- 所在学校 -->
...@@ -59,12 +102,7 @@ ...@@ -59,12 +102,7 @@
59 <nut-form ref="formRef" :model-value="formData"> 102 <nut-form ref="formRef" :model-value="formData">
60 <view class="form-section"> 103 <view class="form-section">
61 <!-- 车型品牌 --> 104 <!-- 车型品牌 -->
62 - <nut-form-item 105 + <nut-form-item label-position="top" label="车型品牌" prop="brand" required :rules="[{ required: true, message: '请选择车型品牌' }]">
63 - label="车型品牌"
64 - prop="brand"
65 - required
66 - :rules="[{ required: true, message: '请选择车型品牌' }]"
67 - >
68 <view class="form-item-content" @click="showBrandPicker"> 106 <view class="form-item-content" @click="showBrandPicker">
69 <text class="form-value">{{ formData.brand || '请选择' }}</text> 107 <text class="form-value">{{ formData.brand || '请选择' }}</text>
70 <Right class="arrow-icon" /> 108 <Right class="arrow-icon" />
...@@ -72,7 +110,7 @@ ...@@ -72,7 +110,7 @@
72 </nut-form-item> 110 </nut-form-item>
73 111
74 <!-- 车辆型号 --> 112 <!-- 车辆型号 -->
75 - <nut-form-item label="车辆型号" prop="model"> 113 + <nut-form-item label-position="top" label="车辆型号" prop="model">
76 <view class="form-item-content" @click="showModelPicker"> 114 <view class="form-item-content" @click="showModelPicker">
77 <text class="form-value">{{ formData.model || '请选择' }}</text> 115 <text class="form-value">{{ formData.model || '请选择' }}</text>
78 <Right class="arrow-icon" /> 116 <Right class="arrow-icon" />
...@@ -80,7 +118,7 @@ ...@@ -80,7 +118,7 @@
80 </nut-form-item> 118 </nut-form-item>
81 119
82 <!-- 车辆出厂年份 --> 120 <!-- 车辆出厂年份 -->
83 - <nut-form-item label="车辆出厂年份" prop="year"> 121 + <nut-form-item label-position="top" label="车辆出厂年份" prop="year">
84 <view class="form-item-content" @click="showYearPicker"> 122 <view class="form-item-content" @click="showYearPicker">
85 <text class="form-value">{{ formData.year || '请选择' }}</text> 123 <text class="form-value">{{ formData.year || '请选择' }}</text>
86 <Right class="arrow-icon" /> 124 <Right class="arrow-icon" />
...@@ -88,12 +126,8 @@ ...@@ -88,12 +126,8 @@
88 </nut-form-item> 126 </nut-form-item>
89 127
90 <!-- 新旧程度 --> 128 <!-- 新旧程度 -->
91 - <nut-form-item 129 + <nut-form-item label-position="top" label="新旧程度" prop="condition" required
92 - label="新旧程度" 130 + :rules="[{ required: true, message: '请选择新旧程度' }]">
93 - prop="condition"
94 - required
95 - :rules="[{ required: true, message: '请选择新旧程度' }]"
96 - >
97 <view class="form-item-content" @click="showConditionPicker"> 131 <view class="form-item-content" @click="showConditionPicker">
98 <text class="form-value">{{ formData.condition || '请选择' }}</text> 132 <text class="form-value">{{ formData.condition || '请选择' }}</text>
99 <Right class="arrow-icon" /> 133 <Right class="arrow-icon" />
...@@ -101,18 +135,9 @@ ...@@ -101,18 +135,9 @@
101 </nut-form-item> 135 </nut-form-item>
102 136
103 <!-- 行驶里程 --> 137 <!-- 行驶里程 -->
104 - <nut-form-item 138 + <nut-form-item label="行驶里程" prop="mileage" required
105 - label="行驶里程" 139 + :rules="[{ required: true, message: '请输入行驶里程' }]">
106 - prop="mileage" 140 + <nut-input v-model="formData.mileage" placeholder="1200" type="number" input-align="right">
107 - required
108 - :rules="[{ required: true, message: '请输入行驶里程' }]"
109 - >
110 - <nut-input
111 - v-model="formData.mileage"
112 - placeholder="1200"
113 - type="number"
114 - input-align="right"
115 - >
116 <template #right> 141 <template #right>
117 <text class="unit">公里</text> 142 <text class="unit">公里</text>
118 </template> 143 </template>
...@@ -121,12 +146,7 @@ ...@@ -121,12 +146,7 @@
121 146
122 <!-- 续航里程 --> 147 <!-- 续航里程 -->
123 <nut-form-item label="续航里程" prop="range"> 148 <nut-form-item label="续航里程" prop="range">
124 - <nut-input 149 + <nut-input v-model="formData.range" placeholder="60" type="number" input-align="right">
125 - v-model="formData.range"
126 - placeholder="60"
127 - type="number"
128 - input-align="right"
129 - >
130 <template #right> 150 <template #right>
131 <text class="unit">公里</text> 151 <text class="unit">公里</text>
132 </template> 152 </template>
...@@ -135,12 +155,7 @@ ...@@ -135,12 +155,7 @@
135 155
136 <!-- 最高时速 --> 156 <!-- 最高时速 -->
137 <nut-form-item label="最高时速" prop="maxSpeed"> 157 <nut-form-item label="最高时速" prop="maxSpeed">
138 - <nut-input 158 + <nut-input v-model="formData.maxSpeed" placeholder="25" type="number" input-align="right">
139 - v-model="formData.maxSpeed"
140 - placeholder="25"
141 - type="number"
142 - input-align="right"
143 - >
144 <template #right> 159 <template #right>
145 <text class="unit">km/h</text> 160 <text class="unit">km/h</text>
146 </template> 161 </template>
...@@ -149,12 +164,8 @@ ...@@ -149,12 +164,8 @@
149 164
150 <!-- 电池容量 --> 165 <!-- 电池容量 -->
151 <nut-form-item label="电池容量" prop="batteryCapacity"> 166 <nut-form-item label="电池容量" prop="batteryCapacity">
152 - <nut-input 167 + <nut-input v-model="formData.batteryCapacity" placeholder="20" type="number"
153 - v-model="formData.batteryCapacity" 168 + input-align="right">
154 - placeholder="20"
155 - type="number"
156 - input-align="right"
157 - >
158 <template #right> 169 <template #right>
159 <text class="unit">Ah</text> 170 <text class="unit">Ah</text>
160 </template> 171 </template>
...@@ -162,7 +173,7 @@ ...@@ -162,7 +173,7 @@
162 </nut-form-item> 173 </nut-form-item>
163 174
164 <!-- 电池损耗度 --> 175 <!-- 电池损耗度 -->
165 - <nut-form-item label="电池损耗度" prop="batteryWear"> 176 + <nut-form-item label-position="top" label="电池损耗度" prop="batteryWear">
166 <view class="form-item-content" @click="showBatteryWearPicker"> 177 <view class="form-item-content" @click="showBatteryWearPicker">
167 <text class="form-value">{{ formData.batteryWear || '请选择' }}</text> 178 <text class="form-value">{{ formData.batteryWear || '请选择' }}</text>
168 <Right class="arrow-icon" /> 179 <Right class="arrow-icon" />
...@@ -170,7 +181,7 @@ ...@@ -170,7 +181,7 @@
170 </nut-form-item> 181 </nut-form-item>
171 182
172 <!-- 刹车磨损度 --> 183 <!-- 刹车磨损度 -->
173 - <nut-form-item label="刹车磨损度" prop="brakeWear"> 184 + <nut-form-item label-position="top" label="刹车磨损度" prop="brakeWear">
174 <view class="form-item-content" @click="showBrakeWearPicker"> 185 <view class="form-item-content" @click="showBrakeWearPicker">
175 <text class="form-value">{{ formData.brakeWear || '请选择' }}</text> 186 <text class="form-value">{{ formData.brakeWear || '请选择' }}</text>
176 <Right class="arrow-icon" /> 187 <Right class="arrow-icon" />
...@@ -178,7 +189,7 @@ ...@@ -178,7 +189,7 @@
178 </nut-form-item> 189 </nut-form-item>
179 190
180 <!-- 轮胎磨损度 --> 191 <!-- 轮胎磨损度 -->
181 - <nut-form-item label="轮胎磨损度" prop="tireWear"> 192 + <nut-form-item label-position="top" label="轮胎磨损度" prop="tireWear">
182 <view class="form-item-content" @click="showTireWearPicker"> 193 <view class="form-item-content" @click="showTireWearPicker">
183 <text class="form-value">{{ formData.tireWear || '请选择' }}</text> 194 <text class="form-value">{{ formData.tireWear || '请选择' }}</text>
184 <Right class="arrow-icon" /> 195 <Right class="arrow-icon" />
...@@ -196,12 +207,7 @@ ...@@ -196,12 +207,7 @@
196 </view> 207 </view>
197 <view class="form-item-right"> 208 <view class="form-item-right">
198 <text class="price-symbol">¥</text> 209 <text class="price-symbol">¥</text>
199 - <input 210 + <input v-model="formData.sellingPrice" placeholder="3,200" type="text" class="price-input" />
200 - v-model="formData.sellingPrice"
201 - placeholder="3,200"
202 - type="text"
203 - class="price-input"
204 - />
205 </view> 211 </view>
206 </view> 212 </view>
207 213
...@@ -212,36 +218,22 @@ ...@@ -212,36 +218,22 @@
212 </view> 218 </view>
213 <view class="form-item-right"> 219 <view class="form-item-right">
214 <text class="market-price-symbol">¥</text> 220 <text class="market-price-symbol">¥</text>
215 - <input 221 + <input v-model="formData.marketPrice" placeholder="6,500" type="text"
216 - v-model="formData.marketPrice" 222 + class="market-price-input" />
217 - placeholder="6,500"
218 - type="text"
219 - class="market-price-input"
220 - />
221 </view> 223 </view>
222 </view> 224 </view>
223 </view> 225 </view>
224 226
225 <!-- 车辆描述 --> 227 <!-- 车辆描述 -->
226 <view class="form-section"> 228 <view class="form-section">
227 - <nut-textarea 229 + <nut-textarea v-model="formData.description" placeholder="请描述车辆详情,如使用感受、车况特点等" :max-length="200"
228 - v-model="formData.description" 230 + :rows="4" show-word-limit />
229 - placeholder="请描述车辆详情,如使用感受、车况特点等"
230 - :max-length="200"
231 - :rows="4"
232 - show-word-limit
233 - />
234 </view> 231 </view>
235 </view> 232 </view>
236 233
237 <!-- 底部按钮 --> 234 <!-- 底部按钮 -->
238 <view class="bottom-actions"> 235 <view class="bottom-actions">
239 - <nut-button 236 + <nut-button color="#f97316" size="large" block @click="onPublish">
240 - color="#f97316"
241 - size="large"
242 - block
243 - @click="onPublish"
244 - >
245 确认发布 237 确认发布
246 </nut-button> 238 </nut-button>
247 </view> 239 </view>
...@@ -249,106 +241,83 @@ ...@@ -249,106 +241,83 @@
249 <!-- 选择器弹窗 --> 241 <!-- 选择器弹窗 -->
250 <!-- 学校选择 --> 242 <!-- 学校选择 -->
251 <nut-popup v-model:visible="schoolPickerVisible" position="bottom"> 243 <nut-popup v-model:visible="schoolPickerVisible" position="bottom">
252 - <nut-picker 244 + <nut-picker v-model="schoolValue" :columns="schoolOptions" title="选择学校" @confirm="onSchoolConfirm"
253 - v-model="schoolValue" 245 + @cancel="schoolPickerVisible = false" />
254 - :columns="schoolOptions"
255 - title="选择学校"
256 - @confirm="onSchoolConfirm"
257 - @cancel="schoolPickerVisible = false"
258 - />
259 </nut-popup> 246 </nut-popup>
260 247
261 <!-- 品牌选择 --> 248 <!-- 品牌选择 -->
262 <nut-popup v-model:visible="brandPickerVisible" position="bottom"> 249 <nut-popup v-model:visible="brandPickerVisible" position="bottom">
263 - <nut-picker 250 + <nut-picker v-model="brandValue" :columns="brandOptions" title="选择车型品牌" @confirm="onBrandConfirm"
264 - v-model="brandValue" 251 + @cancel="brandPickerVisible = false" />
265 - :columns="brandOptions"
266 - title="选择车型品牌"
267 - @confirm="onBrandConfirm"
268 - @cancel="brandPickerVisible = false"
269 - />
270 </nut-popup> 252 </nut-popup>
271 253
272 <!-- 型号选择 --> 254 <!-- 型号选择 -->
273 <nut-popup v-model:visible="modelPickerVisible" position="bottom"> 255 <nut-popup v-model:visible="modelPickerVisible" position="bottom">
274 - <nut-picker 256 + <nut-picker v-model="modelValue" :columns="modelOptions" title="选择车辆型号" @confirm="onModelConfirm"
275 - v-model="modelValue" 257 + @cancel="modelPickerVisible = false" />
276 - :columns="modelOptions"
277 - title="选择车辆型号"
278 - @confirm="onModelConfirm"
279 - @cancel="modelPickerVisible = false"
280 - />
281 </nut-popup> 258 </nut-popup>
282 259
283 <!-- 年份选择 --> 260 <!-- 年份选择 -->
284 <nut-popup v-model:visible="yearPickerVisible" position="bottom"> 261 <nut-popup v-model:visible="yearPickerVisible" position="bottom">
285 - <nut-picker 262 + <nut-picker v-model="yearValue" :columns="yearOptions" title="选择出厂年份" @confirm="onYearConfirm"
286 - v-model="yearValue" 263 + @cancel="yearPickerVisible = false" />
287 - :columns="yearOptions"
288 - title="选择出厂年份"
289 - @confirm="onYearConfirm"
290 - @cancel="yearPickerVisible = false"
291 - />
292 </nut-popup> 264 </nut-popup>
293 265
294 <!-- 新旧程度选择 --> 266 <!-- 新旧程度选择 -->
295 <nut-popup v-model:visible="conditionPickerVisible" position="bottom"> 267 <nut-popup v-model:visible="conditionPickerVisible" position="bottom">
296 - <nut-picker 268 + <nut-picker v-model="conditionValue" :columns="conditionOptions" title="选择新旧程度"
297 - v-model="conditionValue" 269 + @confirm="onConditionConfirm" @cancel="conditionPickerVisible = false" />
298 - :columns="conditionOptions"
299 - title="选择新旧程度"
300 - @confirm="onConditionConfirm"
301 - @cancel="conditionPickerVisible = false"
302 - />
303 </nut-popup> 270 </nut-popup>
304 271
305 <!-- 电池损耗度选择 --> 272 <!-- 电池损耗度选择 -->
306 <nut-popup v-model:visible="batteryWearPickerVisible" position="bottom"> 273 <nut-popup v-model:visible="batteryWearPickerVisible" position="bottom">
307 - <nut-picker 274 + <nut-picker v-model="batteryWearValue" :columns="wearLevelOptions" title="选择电池损耗度"
308 - v-model="batteryWearValue" 275 + @confirm="onBatteryWearConfirm" @cancel="batteryWearPickerVisible = false" />
309 - :columns="wearLevelOptions"
310 - title="选择电池损耗度"
311 - @confirm="onBatteryWearConfirm"
312 - @cancel="batteryWearPickerVisible = false"
313 - />
314 </nut-popup> 276 </nut-popup>
315 277
316 <!-- 刹车磨损度选择 --> 278 <!-- 刹车磨损度选择 -->
317 <nut-popup v-model:visible="brakeWearPickerVisible" position="bottom"> 279 <nut-popup v-model:visible="brakeWearPickerVisible" position="bottom">
318 - <nut-picker 280 + <nut-picker v-model="brakeWearValue" :columns="wearLevelOptions" title="选择刹车磨损度"
319 - v-model="brakeWearValue" 281 + @confirm="onBrakeWearConfirm" @cancel="brakeWearPickerVisible = false" />
320 - :columns="wearLevelOptions"
321 - title="选择刹车磨损度"
322 - @confirm="onBrakeWearConfirm"
323 - @cancel="brakeWearPickerVisible = false"
324 - />
325 </nut-popup> 282 </nut-popup>
326 283
327 <!-- 轮胎磨损度选择 --> 284 <!-- 轮胎磨损度选择 -->
328 <nut-popup v-model:visible="tireWearPickerVisible" position="bottom"> 285 <nut-popup v-model:visible="tireWearPickerVisible" position="bottom">
329 - <nut-picker 286 + <nut-picker v-model="tireWearValue" :columns="wearLevelOptions" title="选择轮胎磨损度" @confirm="onTireWearConfirm"
330 - v-model="tireWearValue" 287 + @cancel="tireWearPickerVisible = false" />
331 - :columns="wearLevelOptions"
332 - title="选择轮胎磨损度"
333 - @confirm="onTireWearConfirm"
334 - @cancel="tireWearPickerVisible = false"
335 - />
336 </nut-popup> 288 </nut-popup>
337 </view> 289 </view>
338 </template> 290 </template>
339 291
340 <script setup> 292 <script setup>
341 import { ref, reactive } from 'vue' 293 import { ref, reactive } from 'vue'
342 -import { Plus, Right, Location, RectLeft } from '@nutui/icons-vue-taro' 294 +import { Plus, Right, Location, RectLeft, Close } from '@nutui/icons-vue-taro'
343 import Taro from '@tarojs/taro' 295 import Taro from '@tarojs/taro'
296 +// import BASE_URL from '@/utils/config';
344 297
345 const themeVars = ref({ 298 const themeVars = ref({
346 // navbarBackground: '#FFA135', 299 // navbarBackground: '#FFA135',
347 // navbarColor: '#ffffff', 300 // navbarColor: '#ffffff',
348 }) 301 })
349 302
350 -// 文件上传列表 303 +// 文件上传相关
351 -const fileList = ref([]) 304 +// const frontFileList = ref([]) // 正面照(保留用于兼容旧代码)
305 +// const leftFileList = ref([]) // 左侧照(保留用于兼容旧代码)
306 +// const rightFileList = ref([]) // 右侧照(保留用于兼容旧代码)
307 +// const otherFileList = ref([]) // 其他照片(保留用于兼容旧代码)
308 +
309 +// 已上传图片的URL
310 +const uploadedImages = reactive({
311 + front: '',
312 + left: '',
313 + right: '',
314 + other: ''
315 +})
316 +
317 +// 图片预览相关
318 +const previewVisible = ref(false)
319 +const previewImages = ref([])
320 +const previewIndex = ref(0)
352 321
353 // 表单数据 322 // 表单数据
354 const formData = reactive({ 323 const formData = reactive({
...@@ -459,23 +428,133 @@ const goBack = () => { ...@@ -459,23 +428,133 @@ const goBack = () => {
459 } 428 }
460 429
461 /** 430 /**
462 - * 文件上传超出限制 431 + * 触发图片上传
432 + * @param {String} type - 图片类型 (front/left/right/other)
463 */ 433 */
464 -const onOversize = () => { 434 +const triggerUpload = (type) => {
465 - Taro.showToast({ 435 + Taro.chooseImage({
466 - title: '文件大小超出限制', 436 + count: 1,
467 - icon: 'none' 437 + sizeType: ['compressed'],
438 + sourceType: ['album', 'camera'],
439 + success: function (res) {
440 + const tempFilePath = res.tempFilePaths[0]
441 + uploadImage(tempFilePath, type)
442 + },
443 + fail: function () {
444 + Taro.showToast({
445 + title: '选择图片失败',
446 + icon: 'none'
447 + })
448 + }
468 }) 449 })
469 } 450 }
470 451
471 /** 452 /**
472 - * 删除上传的文件 453 + * 上传图片到服务器
454 + * @param {String} filePath - 图片文件路径
455 + * @param {String} type - 图片类型 (front/left/right/other)
473 */ 456 */
474 -const onDelete = () => { 457 +const uploadImage = (filePath, type) => {
475 - // 删除逻辑已由组件内部处理 458 + // 显示上传中提示
459 + Taro.showLoading({
460 + title: '上传中',
461 + mask: true
462 + })
463 +
464 + // 模拟上传成功(实际项目中替换为真实的上传逻辑)
465 + setTimeout(() => {
466 + Taro.hideLoading()
467 +
468 + // 模拟服务器返回的图片URL
469 + const mockImageUrl = filePath // 在实际项目中,这里应该是服务器返回的URL
470 +
471 + // 更新对应类型的图片
472 + uploadedImages[type] = mockImageUrl
473 +
474 + Taro.showToast({
475 + title: '上传成功',
476 + icon: 'success'
477 + })
478 + }, 1500)
479 +
480 + // 真实上传逻辑(注释掉的代码供参考)
481 + /*
482 + Taro.uploadFile({
483 + url: BASE_URL + '/admin/?m=srv&a=upload',
484 + filePath: filePath,
485 + name: 'file',
486 + header: {
487 + 'content-type': 'multipart/form-data'
488 + },
489 + success: function (res) {
490 + try {
491 + const upload_data = JSON.parse(res.data)
492 + Taro.hideLoading()
493 +
494 + if (res.statusCode === 200 && upload_data.data) {
495 + // 上传成功,更新对应类型的图片
496 + uploadedImages[type] = upload_data.data.src
497 +
498 + Taro.showToast({
499 + title: '上传成功',
500 + icon: 'success'
501 + })
502 + } else {
503 + throw new Error('上传失败')
504 + }
505 + } catch (error) {
506 + Taro.hideLoading()
507 + Taro.showToast({
508 + icon: 'error',
509 + title: '服务器错误,稍后重试!',
510 + mask: true
511 + })
512 + }
513 + },
514 + fail: function () {
515 + Taro.hideLoading()
516 + Taro.showToast({
517 + icon: 'error',
518 + title: '上传失败,请重试',
519 + mask: true
520 + })
521 + }
522 + })
523 + */
476 } 524 }
477 525
478 /** 526 /**
527 + * 预览图片
528 + * @param {String} imageUrl - 图片URL
529 + */
530 +const previewImage = (imageUrl) => {
531 + previewImages.value = [{ src: imageUrl }]
532 + previewIndex.value = 0
533 + previewVisible.value = true
534 +}
535 +
536 +/**
537 + * 删除图片
538 + * @param {String} type - 图片类型 (front/left/right/other)
539 + */
540 +const deleteImage = (type) => {
541 + uploadedImages[type] = ''
542 + Taro.showToast({
543 + title: '删除成功',
544 + icon: 'success'
545 + })
546 +}
547 +
548 +// 保留旧的上传逻辑作为备用(已注释)
549 +// const afterRead = (event, type) => { ... }
550 +
551 +// 保留旧的删除逻辑作为备用(已注释)
552 +// const onDeleteFront = (file, index) => { frontFileList.value.splice(index, 1) }
553 +// const onDeleteLeft = (file, index) => { leftFileList.value.splice(index, 1) }
554 +// const onDeleteRight = (file, index) => { rightFileList.value.splice(index, 1) }
555 +// const onDeleteOther = (file, index) => { otherFileList.value.splice(index, 1) }
556 +
557 +/**
479 * 显示学校选择器 558 * 显示学校选择器
480 */ 559 */
481 const showSchoolPicker = () => { 560 const showSchoolPicker = () => {
...@@ -611,6 +690,25 @@ const onPublish = () => { ...@@ -611,6 +690,25 @@ const onPublish = () => {
611 690
612 Taro.showLoading({ title: '发布中...' }) 691 Taro.showLoading({ title: '发布中...' })
613 692
693 + // 收集所有上传的图片URL
694 + const images = {
695 + front: uploadedImages.front || '',
696 + left: uploadedImages.left || '',
697 + right: uploadedImages.right || '',
698 + other: uploadedImages.other || ''
699 + }
700 +
701 + // 构建提交数据
702 + const submitData = {
703 + ...formData,
704 + images: images,
705 + imageUrls: Object.values(images).filter(url => url) // 过滤空URL
706 + }
707 +
708 + // 发布车辆信息数据已准备完成
709 + // TODO: 在此处调用实际的API接口提交数据
710 + // console.log('提交数据:', submitData)
711 +
614 // 模拟发布请求 712 // 模拟发布请求
615 setTimeout(() => { 713 setTimeout(() => {
616 Taro.hideLoading() 714 Taro.hideLoading()
...@@ -631,7 +729,9 @@ const onPublish = () => { ...@@ -631,7 +729,9 @@ const onPublish = () => {
631 * @returns {boolean} 验证结果 729 * @returns {boolean} 验证结果
632 */ 730 */
633 const validateForm = () => { 731 const validateForm = () => {
634 - if (fileList.value.length === 0) { 732 + // 检查是否至少上传了一张图片
733 + const hasImages = uploadedImages.front || uploadedImages.left || uploadedImages.right || uploadedImages.other
734 + if (!hasImages) {
635 Taro.showToast({ title: '请上传车辆图片', icon: 'none' }) 735 Taro.showToast({ title: '请上传车辆图片', icon: 'none' })
636 return false 736 return false
637 } 737 }
...@@ -669,6 +769,7 @@ const validateForm = () => { ...@@ -669,6 +769,7 @@ const validateForm = () => {
669 769
670 .form-container { 770 .form-container {
671 padding: 0 32rpx; 771 padding: 0 32rpx;
772 + margin-bottom: 2rem;
672 } 773 }
673 774
674 .form-section { 775 .form-section {
...@@ -686,17 +787,84 @@ const validateForm = () => { ...@@ -686,17 +787,84 @@ const validateForm = () => {
686 display: block; 787 display: block;
687 } 788 }
688 789
689 -.upload-tips { 790 +.upload-grid {
690 - display: flex; 791 + display: grid;
691 - margin-top: 16rpx; 792 + grid-template-columns: repeat(2, 1fr);
692 gap: 32rpx; 793 gap: 32rpx;
794 + margin-top: 24rpx;
693 } 795 }
694 796
695 -.tip-item { 797 +.upload-item {
696 - font-size: 24rpx; 798 + display: flex;
799 + flex-direction: column;
800 + align-items: center;
801 + gap: 16rpx;
802 +}
803 +
804 +.upload-button {
805 + width: 160rpx;
806 + height: 160rpx;
807 + border: 2rpx dashed #d1d5db;
808 + border-radius: 16rpx;
809 + display: flex;
810 + align-items: center;
811 + justify-content: center;
812 + background-color: #f9fafb;
813 + transition: all 0.3s ease;
814 +}
815 +
816 +.upload-button:active {
817 + background-color: #f3f4f6;
818 + border-color: #f97316;
819 +}
820 +
821 +.upload-icon {
822 + font-size: 48rpx;
697 color: #9ca3af; 823 color: #9ca3af;
698 } 824 }
699 825
826 +.upload-label {
827 + font-size: 24rpx;
828 + color: #6b7280;
829 + text-align: center;
830 +}
831 +
832 +.image-preview {
833 + position: relative;
834 + width: 160rpx;
835 + height: 160rpx;
836 + border-radius: 16rpx;
837 + overflow: hidden;
838 +}
839 +
840 +.preview-image {
841 + width: 100%;
842 + height: 100%;
843 + object-fit: cover;
844 +}
845 +
846 +.delete-btn {
847 + position: absolute;
848 + top: 8rpx;
849 + right: 8rpx;
850 + width: 32rpx;
851 + height: 32rpx;
852 + background-color: #ef4444;
853 + border-radius: 50%;
854 + display: flex;
855 + align-items: center;
856 + justify-content: center;
857 + color: white;
858 + font-size: 16rpx;
859 + z-index: 10;
860 + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
861 +}
862 +
863 +.delete-icon {
864 + font-size: 16rpx;
865 + color: white;
866 +}
867 +
700 .form-item { 868 .form-item {
701 display: flex; 869 display: flex;
702 justify-content: space-between; 870 justify-content: space-between;
...@@ -842,20 +1010,43 @@ const validateForm = () => { ...@@ -842,20 +1010,43 @@ const validateForm = () => {
842 } 1010 }
843 1011
844 :deep(.nut-uploader) { 1012 :deep(.nut-uploader) {
845 - margin-bottom: 16rpx; 1013 + margin-bottom: 0;
846 } 1014 }
847 1015
848 :deep(.nut-uploader__preview) { 1016 :deep(.nut-uploader__preview) {
849 - display: grid; 1017 + margin-bottom: 0;
850 - grid-template-columns: repeat(4, 1fr);
851 - gap: 24rpx;
852 - margin-bottom: 24rpx;
853 } 1018 }
854 1019
855 :deep(.nut-uploader__preview-img) { 1020 :deep(.nut-uploader__preview-img) {
856 width: 160rpx; 1021 width: 160rpx;
857 height: 160rpx; 1022 height: 160rpx;
858 border-radius: 16rpx; 1023 border-radius: 16rpx;
1024 + object-fit: cover;
1025 +}
1026 +
1027 +:deep(.nut-uploader__upload) {
1028 + width: 160rpx;
1029 + height: 160rpx;
1030 +}
1031 +
1032 +:deep(.nut-uploader__input) {
1033 + width: 100%;
1034 + height: 100%;
1035 +}
1036 +
1037 +:deep(.nut-uploader__preview-delete) {
1038 + position: absolute;
1039 + top: 8rpx;
1040 + right: 8rpx;
1041 + width: 32rpx;
1042 + height: 32rpx;
1043 + background-color: rgba(0, 0, 0, 0.6);
1044 + border-radius: 50%;
1045 + display: flex;
1046 + align-items: center;
1047 + justify-content: center;
1048 + color: #ffffff;
1049 + font-size: 20rpx;
859 } 1050 }
860 1051
861 :deep(.nut-picker__toolbar) { 1052 :deep(.nut-picker__toolbar) {
......