hookehuyr

feat: 添加电动车交易平台核心功能

- 新增首页、分类、卖车、消息和个人中心五个主要页面
- 实现首页商品展示、搜索和分类功能
- 添加卖车表单页面,支持图片上传和基本信息填写
- 创建消息和个人中心页面,完善用户交互
- 配置底部导航栏和页面路由
- 优化UI样式,修复NutUI图标字体问题
- 添加多套SVG图标资源
- 更新.gitignore忽略.resource文件
...@@ -7,3 +7,4 @@ node_modules/ ...@@ -7,3 +7,4 @@ node_modules/
7 .swc 7 .swc
8 .history 8 .history
9 .trae 9 .trae
10 +.resource
......
...@@ -8,8 +8,7 @@ export {} ...@@ -8,8 +8,7 @@ export {}
8 declare module 'vue' { 8 declare module 'vue' {
9 export interface GlobalComponents { 9 export interface GlobalComponents {
10 NavBar: typeof import('./src/components/navBar.vue')['default'] 10 NavBar: typeof import('./src/components/navBar.vue')['default']
11 - NutButton: typeof import('@nutui/nutui-taro')['Button'] 11 + NutInput: typeof import('@nutui/nutui-taro')['Input']
12 - NutToast: typeof import('@nutui/nutui-taro')['Toast']
13 Picker: typeof import('./src/components/time-picker-data/picker.vue')['default'] 12 Picker: typeof import('./src/components/time-picker-data/picker.vue')['default']
14 PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default'] 13 PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default']
15 RouterLink: typeof import('vue-router')['RouterLink'] 14 RouterLink: typeof import('vue-router')['RouterLink']
......
1 /* 1 /*
2 * @Date: 2025-06-28 10:33:00 2 * @Date: 2025-06-28 10:33:00
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-06-28 11:05:47 4 + * @LastEditTime: 2025-07-01 17:55:25
5 - * @FilePath: /myApp/src/app.config.js 5 + * @FilePath: /jgdl/src/app.config.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
8 export default { 8 export default {
9 pages: [ 9 pages: [
10 'pages/index/index', 10 'pages/index/index',
11 + 'pages/post/index',
12 + 'pages/sell/index',
13 + 'pages/messages/index',
14 + 'pages/profile/index',
11 'pages/auth/index', 15 'pages/auth/index',
12 ], 16 ],
13 subpackages: [ // 配置在tabBar中的页面不能分包写到subpackages中去 17 subpackages: [ // 配置在tabBar中的页面不能分包写到subpackages中去
...@@ -21,5 +25,43 @@ export default { ...@@ -21,5 +25,43 @@ export default {
21 navigationBarBackgroundColor: '#fff', 25 navigationBarBackgroundColor: '#fff',
22 navigationBarTitleText: 'WeChat', 26 navigationBarTitleText: 'WeChat',
23 navigationBarTextStyle: 'black' 27 navigationBarTextStyle: 'black'
28 + },
29 + tabBar: {
30 + color: '#6b7280',
31 + selectedColor: '#f97316',
32 + backgroundColor: '#ffffff',
33 + borderStyle: 'black',
34 + list: [
35 + {
36 + pagePath: 'pages/index/index',
37 + text: '首页',
38 + iconPath: 'assets/images/icon/icon_home1@2x.png',
39 + selectedIconPath: 'assets/images/icon/icon_home2@2x.png'
40 + },
41 + {
42 + pagePath: 'pages/post/index',
43 + text: '分类',
44 + iconPath: 'assets/images/icon/icon_book1@2x.png',
45 + selectedIconPath: 'assets/images/icon/icon_book2@2x.png'
46 + },
47 + {
48 + pagePath: 'pages/sell/index',
49 + text: '我要卖车',
50 + iconPath: 'assets/images/icon/icon_server1.png',
51 + selectedIconPath: 'assets/images/icon/icon_server2.png'
52 + },
53 + {
54 + pagePath: 'pages/messages/index',
55 + text: '消息',
56 + iconPath: 'assets/images/icon/icon_book1@2x.png',
57 + selectedIconPath: 'assets/images/icon/icon_book2@2x.png'
58 + },
59 + {
60 + pagePath: 'pages/profile/index',
61 + text: '我的',
62 + iconPath: 'assets/images/icon/icon_my1@2x.png',
63 + selectedIconPath: 'assets/images/icon/icon_my2@2x.png'
64 + }
65 + ]
24 } 66 }
25 } 67 }
......
1 @tailwind base; 1 @tailwind base;
2 @tailwind components; 2 @tailwind components;
3 @tailwind utilities; 3 @tailwind utilities;
4 +
5 +/* 修复 NutUI 图标字体样式 */
6 +.nut-icon {
7 + font-style: normal !important;
8 + font-weight: normal !important;
9 +}
10 +
11 +/* 修复所有可能的图标字体 */
12 +[class*="nut-icon"] {
13 + font-style: normal !important;
14 + font-weight: normal !important;
15 +}
......
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M3 3H8V8H3V3Z" stroke="#f97316" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="#fef7ed"/>
3 +<path d="M16 3H21V8H16V3Z" stroke="#f97316" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="#fef7ed"/>
4 +<path d="M16 16H21V21H16V16Z" stroke="#f97316" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="#fef7ed"/>
5 +<path d="M3 16H8V21H3V16Z" stroke="#f97316" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="#fef7ed"/>
6 +</svg>
...\ No newline at end of file ...\ No newline at end of file
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M3 3H8V8H3V3Z" stroke="#6b7280" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
3 +<path d="M16 3H21V8H16V3Z" stroke="#6b7280" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
4 +<path d="M16 16H21V21H16V16Z" stroke="#6b7280" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
5 +<path d="M3 16H8V21H3V16Z" stroke="#6b7280" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
6 +</svg>
...\ No newline at end of file ...\ No newline at end of file
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M3 9L12 2L21 9V20C21 20.5304 20.7893 21.0391 20.4142 21.4142C20.0391 21.7893 19.5304 22 19 22H5C4.46957 22 3.96086 21.7893 3.58579 21.4142C3.21071 21.0391 3 20.5304 3 20V9Z" stroke="#f97316" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="#fef7ed"/>
3 +<path d="M9 22V12H15V22" stroke="#f97316" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
4 +</svg>
...\ No newline at end of file ...\ No newline at end of file
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M3 9L12 2L21 9V20C21 20.5304 20.7893 21.0391 20.4142 21.4142C20.0391 21.7893 19.5304 22 19 22H5C4.46957 22 3.96086 21.7893 3.58579 21.4142C3.21071 21.0391 3 20.5304 3 20V9Z" stroke="#6b7280" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
3 +<path d="M9 22V12H15V22" stroke="#6b7280" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
4 +</svg>
...\ No newline at end of file ...\ No newline at end of file
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M21 15C21 15.5304 20.7893 16.0391 20.4142 16.4142C20.0391 16.7893 19.5304 17 19 17H7L3 21V5C3 4.46957 3.21071 3.96086 3.58579 3.58579C3.96086 3.21071 4.46957 3 5 3H19C19.5304 3 20.0391 3.21071 20.4142 3.58579C20.7893 3.96086 21 4.46957 21 5V15Z" stroke="#f97316" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="#fef7ed"/>
3 +</svg>
...\ No newline at end of file ...\ No newline at end of file
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M21 15C21 15.5304 20.7893 16.0391 20.4142 16.4142C20.0391 16.7893 19.5304 17 19 17H7L3 21V5C3 4.46957 3.21071 3.96086 3.58579 3.58579C3.96086 3.21071 4.46957 3 5 3H19C19.5304 3 20.0391 3.21071 20.4142 3.58579C20.7893 3.96086 21 4.46957 21 5V15Z" stroke="#6b7280" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
3 +</svg>
...\ No newline at end of file ...\ No newline at end of file
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M20 21V19C20 17.9391 19.5786 16.9217 18.8284 16.1716C18.0783 15.4214 17.0609 15 16 15H8C6.93913 15 5.92172 15.4214 5.17157 16.1716C4.42143 16.9217 4 17.9391 4 19V21" stroke="#f97316" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
3 +<circle cx="12" cy="7" r="4" stroke="#f97316" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="#fef7ed"/>
4 +</svg>
...\ No newline at end of file ...\ No newline at end of file
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path d="M20 21V19C20 17.9391 19.5786 16.9217 18.8284 16.1716C18.0783 15.4214 17.0609 15 16 15H8C6.93913 15 5.92172 15.4214 5.17157 16.1716C4.42143 16.9217 4 17.9391 4 19V21" stroke="#6b7280" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
3 +<circle cx="12" cy="7" r="4" stroke="#6b7280" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
4 +</svg>
...\ No newline at end of file ...\ No newline at end of file
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<circle cx="12" cy="12" r="10" stroke="#f97316" stroke-width="2" fill="#fef7ed"/>
3 +<line x1="12" y1="8" x2="12" y2="16" stroke="#f97316" stroke-width="2" stroke-linecap="round"/>
4 +<line x1="8" y1="12" x2="16" y2="12" stroke="#f97316" stroke-width="2" stroke-linecap="round"/>
5 +</svg>
...\ No newline at end of file ...\ No newline at end of file
1 +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<circle cx="12" cy="12" r="10" stroke="#6b7280" stroke-width="2"/>
3 +<line x1="12" y1="8" x2="12" y2="16" stroke="#6b7280" stroke-width="2" stroke-linecap="round"/>
4 +<line x1="8" y1="12" x2="16" y2="12" stroke="#6b7280" stroke-width="2" stroke-linecap="round"/>
5 +</svg>
...\ No newline at end of file ...\ No newline at end of file
1 /** 1 /**
2 - * index页面样式 2 + * 捡个电驴首页样式
3 */ 3 */
4 -.index {
5 - padding: 20px;
6 4
7 - .nut-button { 5 +/* 搜索框样式 */
8 - margin-bottom: 20px; 6 +.nut-input {
9 - } 7 + --nut-input-border-radius: 9999px;
8 + --nut-input-padding: 8px 16px 8px 40px;
9 + --nut-input-font-size: 14px;
10 + --nut-input-background-color: #ffffff;
11 + --nut-input-border-color: transparent;
12 +}
13 +
14 +/* 网格布局修复 */
15 +.grid {
16 + display: grid;
17 +}
18 +
19 +.grid-cols-2 {
20 + grid-template-columns: repeat(2, minmax(0, 1fr));
21 +}
22 +
23 +/* 间距修复 */
24 +.gap-3 {
25 + gap: 12px;
26 +}
27 +
28 +.space-x-1 > * + * {
29 + margin-left: 4px;
30 +}
31 +
32 +/* 阴影效果 */
33 +.shadow-sm {
34 + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
35 +}
36 +
37 +/* 图片样式 */
38 +image {
39 + display: block;
40 + width: 100%;
41 + height: 100%;
42 +}
43 +
44 +/* 文本省略 */
45 +.text-ellipsis {
46 + overflow: hidden;
47 + text-overflow: ellipsis;
48 + white-space: nowrap;
49 +}
50 +
51 +/* 多行文本省略 */
52 +.line-clamp-2 {
53 + overflow: hidden;
54 + display: -webkit-box;
55 + -webkit-box-orient: vertical;
56 + -webkit-line-clamp: 2;
57 +}
58 +
59 +/* 修复flex布局在小程序中的问题 */
60 +.flex {
61 + display: flex;
62 +}
63 +
64 +.flex-col {
65 + flex-direction: column;
66 +}
67 +
68 +.items-center {
69 + align-items: center;
70 +}
71 +
72 +.justify-center {
73 + justify-content: center;
74 +}
75 +
76 +.justify-between {
77 + justify-content: space-between;
78 +}
79 +
80 +.justify-around {
81 + justify-content: space-around;
82 +}
83 +
84 +/* 响应式图片 */
85 +.aspect-fill {
86 + object-fit: cover;
87 +}
88 +
89 +/* 卡片悬停效果 */
90 +.card-hover {
91 + transition: transform 0.2s ease-in-out;
92 +}
93 +
94 +.card-hover:active {
95 + transform: scale(0.98);
10 } 96 }
...\ No newline at end of file ...\ No newline at end of file
......
1 <!-- 1 <!--
2 * @Date: 2025-06-28 10:33:00 2 * @Date: 2025-06-28 10:33:00
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-07-01 11:13:13 4 + * @LastEditTime: 2025-07-01 18:18:19
5 - * @FilePath: /myApp/src/pages/index/index.vue 5 + * @FilePath: /jgdl/src/pages/index/index.vue
6 - * @Description: 文件描述 6 + * @Description: 捡个电驴首页
7 --> 7 -->
8 <template> 8 <template>
9 - <view class="index"> 9 + <view class="flex flex-col bg-orange-50 min-h-screen">
10 - <nut-button type="primary" @click="onClick">按钮</nut-button> 10 + <!-- Header -->
11 - <nut-toast v-model:visible="show" msg="你成功了" /> 11 + <view class="bg-orange-400 p-4 pt-8 pb-6">
12 - <View className="text-[#acc855] text-[100px]">Hello world!</View> 12 + <view class="text-2xl font-bold text-white mb-3">捡个电驴</view>
13 + <!-- Search Bar -->
14 + <view class="relative">
15 + <view class="absolute inset-y-0 left-3 flex items-center pointer-events-none">
16 + <Search size="18" color="#9ca3af" />
17 + </view>
18 + <nut-input
19 + v-model="searchValue"
20 + placeholder="搜索品牌型号"
21 + class="w-full py-2 pl-10 pr-4 rounded-full bg-white"
22 + :border="false"
23 + />
24 + </view>
25 + </view>
26 +
27 + <!-- Banner -->
28 + <view class="px-4 mt-4">
29 + <view class="relative rounded-lg overflow-hidden">
30 + <image
31 + :src="bannerImages[0]"
32 + mode="aspectFill"
33 + class="w-full h-40 object-cover rounded-lg"
34 + />
35 + <view class="absolute bottom-2 right-2 flex space-x-1">
36 + <view class="w-2 h-2 bg-white rounded-full opacity-100"></view>
37 + <view class="w-2 h-2 bg-white rounded-full opacity-50"></view>
38 + <view class="w-2 h-2 bg-white rounded-full opacity-50"></view>
39 + </view>
40 + </view>
41 + </view>
42 +
43 + <!-- Category Icons -->
44 + <view class="px-4 mt-2">
45 + <view class="flex justify-around py-4">
46 + <view class="flex flex-col items-center">
47 + <view class="w-12 h-12 rounded-full bg-orange-100 flex items-center justify-center">
48 + <Clock size="20" color="#f97316" />
49 + </view>
50 + <text class="text-xs mt-1 text-gray-700">最新上架</text>
51 + </view>
52 + <view class="flex flex-col items-center">
53 + <view class="w-12 h-12 rounded-full bg-orange-100 flex items-center justify-center">
54 + <Star size="20" color="#f97316" />
55 + </view>
56 + <text class="text-xs mt-1 text-gray-700">特价好车</text>
57 + </view>
58 + <view class="flex flex-col items-center" @tap="onCertifiedClick">
59 + <view class="w-12 h-12 rounded-full bg-orange-100 flex items-center justify-center">
60 + <Service size="20" color="#f97316" />
61 + </view>
62 + <text class="text-xs mt-1 text-gray-700">认证车源</text>
63 + </view>
64 + </view>
65 + </view>
66 +
67 + <!-- Featured Recommendations -->
68 + <view class="px-4 mt-4">
69 + <view class="flex justify-between items-center mb-2">
70 + <text class="text-lg font-medium">精品推荐</text>
71 + <view class="text-sm text-gray-500 flex items-center">
72 + <text>更多</text>
73 + <Right size="16" />
74 + </view>
75 + </view>
76 + <view class="grid grid-cols-2 gap-3">
77 + <view
78 + v-for="scooter in featuredScooters"
79 + :key="scooter.id"
80 + class="bg-white rounded-lg shadow-sm overflow-hidden"
81 + @tap="() => onProductClick(scooter)"
82 + >
83 + <view class="relative p-2">
84 + <image
85 + :src="scooter.imageUrl"
86 + :alt="scooter.name"
87 + mode="aspectFill"
88 + class="w-full h-36 object-cover rounded-lg"
89 + />
90 + <view
91 + class="absolute top-4 right-4 p-1"
92 + @tap.stop="() => toggleFavorite(scooter.id)"
93 + >
94 + <Heart
95 + size="24"
96 + :color="favoriteIds.includes(scooter.id) ? '#f97316' : '#ffffff'"
97 + :fill="favoriteIds.includes(scooter.id) ? '#f97316' : 'none'"
98 + />
99 + </view>
100 + <view
101 + v-if="scooter.isVerified"
102 + class="absolute bottom-4 right-4 bg-orange-500 text-white text-xs px-1.5 py-0.5 rounded flex items-center"
103 + >
104 + <Check size="12" color="#ffffff" class="mr-0.5" />
105 + <text class="text-white">认证</text>
106 + </view>
107 + </view>
108 + <view class="p-2">
109 + <text class="font-medium text-sm block">{{ scooter.name }}</text>
110 + <text class="text-xs text-gray-500 block">
111 + {{ scooter.year }} ·
112 + <text v-if="scooter.batteryHealth">电池健康度{{ scooter.batteryHealth }}%</text>
113 + <text v-if="scooter.mileage"> 行驶{{ scooter.mileage }}公里</text>
114 + </text>
115 + <view class="mt-1">
116 + <text class="text-orange-500 font-bold">
117 + ¥{{ scooter.price.toLocaleString() }}
118 + </text>
119 + <text
120 + v-if="scooter.isVerified"
121 + class="ml-2 text-xs px-1 py-0.5 bg-orange-100 text-orange-500 rounded"
122 + >
123 + 低于市场价10%
124 + </text>
125 + <text class="text-xs text-gray-500 mt-1 block">{{ scooter.school }}</text>
126 + </view>
127 + </view>
128 + </view>
129 + </view>
130 + </view>
131 +
132 + <!-- Latest Listings -->
133 + <view class="px-4 mt-6 mb-20">
134 + <view class="flex justify-between items-center mb-2">
135 + <text class="text-lg font-medium">最新上架</text>
136 + <view class="text-sm text-gray-500 flex items-center">
137 + <text>更多</text>
138 + <Right size="16" />
139 + </view>
140 + </view>
141 + <view class="flex flex-col">
142 + <view
143 + v-for="scooter in latestScooters"
144 + :key="scooter.id"
145 + class="bg-white rounded-lg shadow-sm overflow-hidden mb-3"
146 + @tap="() => onProductClick(scooter)"
147 + >
148 + <view class="flex">
149 + <view class="w-32 h-24 relative p-2">
150 + <image
151 + :src="scooter.imageUrl"
152 + :alt="scooter.name"
153 + mode="aspectFill"
154 + class="w-full h-full object-cover rounded-lg"
155 + />
156 + <view
157 + v-if="scooter.isVerified"
158 + class="absolute bottom-3 right-3 bg-orange-500 text-white text-xs px-1 rounded flex items-center"
159 + >
160 + <Check size="12" color="#ffffff" class="mr-0.5" />
161 + <text class="text-white">认证</text>
162 + </view>
163 + </view>
164 + <view class="flex-1 p-3 relative">
165 + <view
166 + class="absolute top-2 right-2"
167 + @tap.stop="() => toggleFavorite(scooter.id)"
168 + >
169 + <Heart
170 + size="20"
171 + :color="favoriteIds.includes(scooter.id) ? '#f97316' : '#d1d5db'"
172 + :fill="favoriteIds.includes(scooter.id) ? '#f97316' : 'none'"
173 + />
174 + </view>
175 + <text class="font-medium text-sm block">{{ scooter.name }}</text>
176 + <text class="text-xs text-gray-600 mt-1 block">
177 + {{ scooter.year }} ·
178 + <text v-if="scooter.batteryHealth">电池健康度{{ scooter.batteryHealth }}%</text>
179 + <text v-if="scooter.mileage"> 行驶{{ scooter.mileage }}公里</text>
180 + </text>
181 + <view class="mt-2">
182 + <text class="text-orange-500 font-bold">
183 + ¥{{ scooter.price.toLocaleString() }}
184 + </text>
185 + <text class="text-xs text-gray-500 mt-1 block">{{ scooter.school }}</text>
186 + </view>
187 + </view>
188 + </view>
189 + </view>
190 + </view>
191 + </view>
13 </view> 192 </view>
14 </template> 193 </template>
15 194
...@@ -18,11 +197,135 @@ import Taro from '@tarojs/taro' ...@@ -18,11 +197,135 @@ import Taro from '@tarojs/taro'
18 import '@tarojs/taro/html.css' 197 import '@tarojs/taro/html.css'
19 import { ref, onMounted } from 'vue' 198 import { ref, onMounted } from 'vue'
20 import { useDidShow, useReady } from '@tarojs/taro' 199 import { useDidShow, useReady } from '@tarojs/taro'
200 +import { Search, Clock, Star, Service, Right, Heart, Check } from '@nutui/icons-vue-taro'
21 import "./index.less"; 201 import "./index.less";
22 202
23 -const show = ref(false) 203 +// 响应式数据
24 -const onClick = () => { 204 +const searchValue = ref('')
25 - show.value = true 205 +const favoriteIds = ref([])
206 +
207 +// Banner图片
208 +const bannerImages = ref([
209 + 'https://images.unsplash.com/photo-1558981806-ec527fa84c39?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60'
210 +])
211 +
212 +// 精品推荐数据
213 +const featuredScooters = ref([
214 + {
215 + id: '1',
216 + name: '小龟电动车',
217 + year: '2023',
218 + school: '上海理工大学',
219 + price: 3880,
220 + imageUrl: 'https://images.unsplash.com/photo-1558981285-6f0c94958bb6?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
221 + isVerified: true
222 + },
223 + {
224 + id: '2',
225 + name: '立马电动车',
226 + year: '2022',
227 + school: '上海复旦大学',
228 + price: 2999,
229 + imageUrl: 'https://images.unsplash.com/photo-1558981403-c5f9899a28bc?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
230 + isVerified: true
231 + },
232 + {
233 + id: '3',
234 + name: '雅迪电动车',
235 + year: '2023',
236 + school: '上海理工大学',
237 + price: 4299,
238 + imageUrl: 'https://images.unsplash.com/photo-1591637333184-19aa84b3e01f?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60'
239 + },
240 + {
241 + id: '4',
242 + name: '爱玛电动车',
243 + year: '2022',
244 + school: '上海复旦大学',
245 + price: 3599,
246 + imageUrl: 'https://images.unsplash.com/photo-1558980664-3a031cf67ea8?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
247 + isVerified: true
248 + }
249 +])
250 +
251 +// 最新上架数据
252 +const latestScooters = ref([
253 + {
254 + id: '5',
255 + name: '雅迪 豪华版',
256 + year: '2023',
257 + school: '上海理工大学',
258 + price: 3200,
259 + imageUrl: 'https://images.unsplash.com/photo-1558981403-c5f9899a28bc?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
260 + isVerified: true,
261 + batteryHealth: 98,
262 + mileage: 1200
263 + },
264 + {
265 + id: '6',
266 + name: '台铃 战速',
267 + year: '2022',
268 + school: '上海理工大学',
269 + price: 3800,
270 + imageUrl: 'https://images.unsplash.com/photo-1558981285-6f0c94958bb6?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
271 + batteryHealth: 92,
272 + mileage: 2500
273 + },
274 + {
275 + id: '7',
276 + name: '小鸟电车',
277 + year: '2023',
278 + school: '上海复旦大学',
279 + price: 3100,
280 + imageUrl: 'https://images.unsplash.com/photo-1558980664-3a031cf67ea8?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
281 + batteryHealth: 92,
282 + mileage: 2000
283 + },
284 + {
285 + id: '8',
286 + name: '新日电动车',
287 + year: '2024',
288 + school: '上海同济大学',
289 + price: 6700,
290 + imageUrl: 'https://images.unsplash.com/photo-1595941069915-4ebc5197c14a?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
291 + isVerified: true,
292 + batteryHealth: 96,
293 + mileage: 500
294 + }
295 +])
296 +
297 +/**
298 + * 切换收藏状态
299 + * @param {string} scooterId - 电动车ID
300 + */
301 +const toggleFavorite = (scooterId) => {
302 + const index = favoriteIds.value.indexOf(scooterId)
303 + if (index > -1) {
304 + favoriteIds.value.splice(index, 1)
305 + } else {
306 + favoriteIds.value.push(scooterId)
307 + }
308 +}
309 +
310 +/**
311 + * 点击产品卡片
312 + * @param {Object} scooter - 电动车信息
313 + */
314 +const onProductClick = (scooter) => {
315 + Taro.showToast({
316 + title: `查看${scooter.name}`,
317 + icon: 'none'
318 + })
319 +}
320 +
321 +/**
322 + * 点击认证车源
323 + */
324 +const onCertifiedClick = () => {
325 + Taro.showToast({
326 + title: '查看认证车源',
327 + icon: 'none'
328 + })
26 } 329 }
27 330
28 // 生命周期钩子 331 // 生命周期钩子
......
1 +export default {
2 + navigationBarTitleText: '首页'
3 +}
1 +<template>
2 + <view class="messages-page">
3 + <!-- 顶部搜索栏 -->
4 + <view class="search-container">
5 + <view class="search-box">
6 + <Search size="18" color="#9ca3af" />
7 + <input
8 + v-model="searchValue"
9 + placeholder="搜索聊天记录..."
10 + class="search-input"
11 + />
12 + </view>
13 + </view>
14 +
15 + <!-- 消息列表 -->
16 + <view class="messages-list">
17 + <view
18 + v-for="message in filteredMessages"
19 + :key="message.id"
20 + class="message-item"
21 + @click="onMessageClick(message)"
22 + >
23 + <view class="avatar-container">
24 + <image :src="message.avatar" class="avatar" mode="aspectFill" />
25 + <view v-if="message.unreadCount > 0" class="unread-badge">
26 + <text class="unread-count">{{ message.unreadCount > 99 ? '99+' : message.unreadCount }}</text>
27 + </view>
28 + </view>
29 +
30 + <view class="message-content">
31 + <view class="message-header">
32 + <text class="sender-name">{{ message.senderName }}</text>
33 + <text class="message-time">{{ formatTime(message.timestamp) }}</text>
34 + </view>
35 +
36 + <view class="message-preview">
37 + <text class="preview-text" :class="{ 'unread': message.unreadCount > 0 }">
38 + {{ message.lastMessage }}
39 + </text>
40 + <view v-if="message.type === 'image'" class="message-type-icon">
41 + <Image size="16" color="#9ca3af" />
42 + </view>
43 + </view>
44 + </view>
45 + </view>
46 + </view>
47 +
48 + <!-- 空状态 -->
49 + <view v-if="filteredMessages.length === 0" class="empty-state">
50 + <view class="empty-icon">
51 + <Message size="48" color="#d1d5db" />
52 + </view>
53 + <text class="empty-title">暂无消息</text>
54 + <text class="empty-subtitle">开始与买家或卖家聊天吧</text>
55 + </view>
56 +
57 + <!-- 浮动按钮 -->
58 + <view class="floating-btn" @click="onNewMessage">
59 + <Plus size="24" color="#ffffff" />
60 + </view>
61 + </view>
62 +</template>
63 +
64 +<script setup>
65 +import { ref, computed } from 'vue'
66 +import { Search, Message, Plus } from '@nutui/icons-vue-taro'
67 +import Taro from '@tarojs/taro'
68 +
69 +// 响应式数据
70 +const searchValue = ref('')
71 +
72 +// 消息数据
73 +const messages = ref([
74 + {
75 + id: 1,
76 + senderName: '张同学',
77 + avatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100&h=100&fit=crop&crop=face',
78 + lastMessage: '这辆车还在吗?可以看看实物吗?',
79 + timestamp: Date.now() - 300000, // 5分钟前
80 + unreadCount: 2,
81 + type: 'text'
82 + },
83 + {
84 + id: 2,
85 + senderName: '李小明',
86 + avatar: 'https://images.unsplash.com/photo-1599566150163-29194dcaad36?w=100&h=100&fit=crop&crop=face',
87 + lastMessage: '价格还能再便宜点吗?',
88 + timestamp: Date.now() - 1800000, // 30分钟前
89 + unreadCount: 0,
90 + type: 'text'
91 + },
92 + {
93 + id: 3,
94 + senderName: '王美丽',
95 + avatar: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?w=100&h=100&fit=crop&crop=face',
96 + lastMessage: '[图片]',
97 + timestamp: Date.now() - 3600000, // 1小时前
98 + unreadCount: 1,
99 + type: 'image'
100 + },
101 + {
102 + id: 4,
103 + senderName: '陈大华',
104 + avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face',
105 + lastMessage: '好的,谢谢!',
106 + timestamp: Date.now() - 7200000, // 2小时前
107 + unreadCount: 0,
108 + type: 'text'
109 + },
110 + {
111 + id: 5,
112 + senderName: '刘小红',
113 + avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=100&h=100&fit=crop&crop=face',
114 + lastMessage: '车子的电池还好用吗?大概能跑多远?',
115 + timestamp: Date.now() - 86400000, // 1天前
116 + unreadCount: 0,
117 + type: 'text'
118 + },
119 + {
120 + id: 6,
121 + senderName: '赵强',
122 + avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=100&h=100&fit=crop&crop=face',
123 + lastMessage: '明天下午有时间看车吗?',
124 + timestamp: Date.now() - 172800000, // 2天前
125 + unreadCount: 0,
126 + type: 'text'
127 + }
128 +])
129 +
130 +// 过滤后的消息列表
131 +const filteredMessages = computed(() => {
132 + if (!searchValue.value.trim()) {
133 + return messages.value
134 + }
135 +
136 + return messages.value.filter(message =>
137 + message.senderName.includes(searchValue.value) ||
138 + message.lastMessage.includes(searchValue.value)
139 + )
140 +})
141 +
142 +/**
143 + * 格式化时间
144 + * @param {number} timestamp - 时间戳
145 + * @returns {string} 格式化后的时间
146 + */
147 +const formatTime = (timestamp) => {
148 + const now = Date.now()
149 + const diff = now - timestamp
150 +
151 + if (diff < 60000) { // 1分钟内
152 + return '刚刚'
153 + } else if (diff < 3600000) { // 1小时内
154 + return `${Math.floor(diff / 60000)}分钟前`
155 + } else if (diff < 86400000) { // 1天内
156 + return `${Math.floor(diff / 3600000)}小时前`
157 + } else if (diff < 604800000) { // 1周内
158 + return `${Math.floor(diff / 86400000)}天前`
159 + } else {
160 + const date = new Date(timestamp)
161 + return `${date.getMonth() + 1}/${date.getDate()}`
162 + }
163 +}
164 +
165 +/**
166 + * 消息点击事件
167 + * @param {object} message - 消息对象
168 + */
169 +const onMessageClick = (message) => {
170 + // 清除未读数量
171 + message.unreadCount = 0
172 +
173 + // 跳转到聊天详情页面
174 + Taro.navigateTo({
175 + url: `/pages/chat/index?userId=${message.id}&userName=${message.senderName}`
176 + })
177 +}
178 +
179 +/**
180 + * 新建消息
181 + */
182 +const onNewMessage = async () => {
183 + try {
184 + await Taro.showToast({
185 + title: '新建消息',
186 + icon: 'none'
187 + })
188 + } catch (error) {
189 + console.error('新建消息失败:', error)
190 + }
191 +}
192 +</script>
193 +
194 +<style lang="less">
195 +.messages-page {
196 + min-height: 100vh;
197 + background-color: #f9fafb;
198 + padding-bottom: 100px;
199 +}
200 +
201 +.search-container {
202 + padding: 16px;
203 + background-color: #ffffff;
204 + border-bottom: 1px solid #f3f4f6;
205 +}
206 +
207 +.search-box {
208 + display: flex;
209 + align-items: center;
210 + background-color: #f9fafb;
211 + border-radius: 24px;
212 + padding: 12px 16px;
213 + gap: 8px;
214 +}
215 +
216 +.search-input {
217 + flex: 1;
218 + border: none;
219 + outline: none;
220 + background: transparent;
221 + font-size: 14px;
222 + color: #374151;
223 +}
224 +
225 +.messages-list {
226 + background-color: #ffffff;
227 +}
228 +
229 +.message-item {
230 + display: flex;
231 + align-items: center;
232 + padding: 16px;
233 + border-bottom: 1px solid #f3f4f6;
234 + transition: background-color 0.2s;
235 +}
236 +
237 +.message-item:active {
238 + background-color: #f9fafb;
239 +}
240 +
241 +.message-item:last-child {
242 + border-bottom: none;
243 +}
244 +
245 +.avatar-container {
246 + position: relative;
247 + margin-right: 12px;
248 +}
249 +
250 +.avatar {
251 + width: 48px;
252 + height: 48px;
253 + border-radius: 50%;
254 + object-fit: cover;
255 +}
256 +
257 +.unread-badge {
258 + position: absolute;
259 + top: -4px;
260 + right: -4px;
261 + min-width: 20px;
262 + height: 20px;
263 + background-color: #ef4444;
264 + border-radius: 10px;
265 + display: flex;
266 + align-items: center;
267 + justify-content: center;
268 + border: 2px solid #ffffff;
269 +}
270 +
271 +.unread-count {
272 + font-size: 12px;
273 + color: #ffffff;
274 + font-weight: 500;
275 + line-height: 1;
276 +}
277 +
278 +.message-content {
279 + flex: 1;
280 + min-width: 0;
281 +}
282 +
283 +.message-header {
284 + display: flex;
285 + justify-content: space-between;
286 + align-items: center;
287 + margin-bottom: 4px;
288 +}
289 +
290 +.sender-name {
291 + font-size: 16px;
292 + font-weight: 500;
293 + color: #111827;
294 +}
295 +
296 +.message-time {
297 + font-size: 12px;
298 + color: #9ca3af;
299 +}
300 +
301 +.message-preview {
302 + display: flex;
303 + align-items: center;
304 + gap: 4px;
305 +}
306 +
307 +.preview-text {
308 + flex: 1;
309 + font-size: 14px;
310 + color: #6b7280;
311 + overflow: hidden;
312 + text-overflow: ellipsis;
313 + white-space: nowrap;
314 +}
315 +
316 +.preview-text.unread {
317 + color: #374151;
318 + font-weight: 500;
319 +}
320 +
321 +.message-type-icon {
322 + flex-shrink: 0;
323 +}
324 +
325 +.empty-state {
326 + display: flex;
327 + flex-direction: column;
328 + align-items: center;
329 + justify-content: center;
330 + padding: 80px 20px;
331 + text-align: center;
332 +}
333 +
334 +.empty-icon {
335 + margin-bottom: 16px;
336 +}
337 +
338 +.empty-title {
339 + font-size: 18px;
340 + font-weight: 500;
341 + color: #374151;
342 + margin-bottom: 8px;
343 + display: block;
344 +}
345 +
346 +.empty-subtitle {
347 + font-size: 14px;
348 + color: #9ca3af;
349 + display: block;
350 +}
351 +
352 +.floating-btn {
353 + position: fixed;
354 + bottom: 100px;
355 + right: 20px;
356 + width: 56px;
357 + height: 56px;
358 + background-color: #f97316;
359 + border-radius: 50%;
360 + display: flex;
361 + align-items: center;
362 + justify-content: center;
363 + box-shadow: 0 4px 12px rgba(249, 115, 22, 0.4);
364 + transition: all 0.2s;
365 +}
366 +
367 +.floating-btn:active {
368 + transform: scale(0.95);
369 + box-shadow: 0 2px 8px rgba(249, 115, 22, 0.4);
370 +}
371 +</style>
...\ No newline at end of file ...\ No newline at end of file
1 +/*
2 + * @Date: 2025-07-01 17:55:04
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-07-01 18:04:14
5 + * @FilePath: /jgdl/src/pages/post/index.config.js
6 + * @Description: 文件描述
7 + */
8 +export default {
9 + navigationBarTitleText: '分类'
10 +}
1 +<template>
2 + <view class="post-page">
3 + <!-- 顶部搜索栏 -->
4 + <view class="search-container">
5 + <view class="search-box">
6 + <Search size="18" color="#9ca3af" />
7 + <input
8 + v-model="searchValue"
9 + placeholder="搜索电动车..."
10 + class="search-input"
11 + />
12 + </view>
13 + </view>
14 +
15 + <!-- 分类网格 -->
16 + <view class="categories-grid">
17 + <view
18 + v-for="category in categories"
19 + :key="category.id"
20 + class="category-item"
21 + @click="onCategoryClick(category)"
22 + >
23 + <view class="category-icon">
24 + <component :is="category.icon" size="32" color="#f97316" />
25 + </view>
26 + <text class="category-name">{{ category.name }}</text>
27 + <text class="category-count">{{ category.count }}辆</text>
28 + </view>
29 + </view>
30 +
31 + <!-- 热门推荐 -->
32 + <view class="section">
33 + <view class="section-header">
34 + <text class="section-title">热门推荐</text>
35 + <view class="section-more" @click="onViewMore('hot')">
36 + <text class="more-text">查看更多</text>
37 + <Right size="16" color="#9ca3af" />
38 + </view>
39 + </view>
40 + <view class="scooter-list">
41 + <view
42 + v-for="scooter in hotScooters"
43 + :key="scooter.id"
44 + class="scooter-card"
45 + @click="onProductClick(scooter)"
46 + >
47 + <image :src="scooter.image" class="scooter-image" mode="aspectFill" />
48 + <view class="scooter-info">
49 + <text class="scooter-name">{{ scooter.name }}</text>
50 + <text class="scooter-year">{{ scooter.year }}年</text>
51 + <text class="scooter-school">{{ scooter.school }}</text>
52 + <view class="scooter-footer">
53 + <text class="scooter-price">¥{{ scooter.price }}</text>
54 + <view class="favorite-btn" @click.stop="toggleFavorite(scooter.id)">
55 + <Heart
56 + size="20"
57 + :color="favoriteIds.includes(scooter.id) ? '#ef4444' : '#9ca3af'"
58 + :fill="favoriteIds.includes(scooter.id) ? '#ef4444' : 'none'"
59 + />
60 + </view>
61 + </view>
62 + </view>
63 + </view>
64 + </view>
65 + </view>
66 +
67 + <!-- 最新发布 -->
68 + <view class="section">
69 + <view class="section-header">
70 + <text class="section-title">最新发布</text>
71 + <view class="section-more" @click="onViewMore('latest')">
72 + <text class="more-text">查看更多</text>
73 + <Right size="16" color="#9ca3af" />
74 + </view>
75 + </view>
76 + <view class="scooter-list">
77 + <view
78 + v-for="scooter in latestScooters"
79 + :key="scooter.id"
80 + class="scooter-card"
81 + @click="onProductClick(scooter)"
82 + >
83 + <image :src="scooter.image" class="scooter-image" mode="aspectFill" />
84 + <view class="scooter-info">
85 + <text class="scooter-name">{{ scooter.name }}</text>
86 + <text class="scooter-year">{{ scooter.year }}年</text>
87 + <text class="scooter-school">{{ scooter.school }}</text>
88 + <view class="scooter-footer">
89 + <text class="scooter-price">¥{{ scooter.price }}</text>
90 + <view class="favorite-btn" @click.stop="toggleFavorite(scooter.id)">
91 + <Heart
92 + size="20"
93 + :color="favoriteIds.includes(scooter.id) ? '#ef4444' : '#9ca3af'"
94 + :fill="favoriteIds.includes(scooter.id) ? '#ef4444' : 'none'"
95 + />
96 + </view>
97 + </view>
98 + </view>
99 + </view>
100 + </view>
101 + </view>
102 + </view>
103 +</template>
104 +
105 +<script setup>
106 +import { ref } from 'vue'
107 +import { Search, Right, Cart, Star, Cart2, Category, Heart } from '@nutui/icons-vue-taro'
108 +
109 +// 响应式数据
110 +const searchValue = ref('')
111 +const favoriteIds = ref([1, 3, 5])
112 +
113 +// 分类数据
114 +const categories = ref([
115 + { id: 1, name: '电动自行车', icon: Cart2, count: 128 },
116 + { id: 2, name: '电动摩托车', icon: Cart2, count: 86 },
117 + { id: 3, name: '电动汽车', icon: Star, count: 45 },
118 + { id: 4, name: '电动货车', icon: Cart, count: 23 },
119 + { id: 5, name: '平衡车', icon: Category, count: 67 },
120 + { id: 6, name: '滑板车', icon: Category, count: 92 }
121 +])
122 +
123 +// 热门推荐数据
124 +const hotScooters = ref([
125 + {
126 + id: 1,
127 + name: '小牛电动 NGT',
128 + year: 2023,
129 + school: '清华大学',
130 + price: 3200,
131 + image: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=300&h=200&fit=crop'
132 + },
133 + {
134 + id: 2,
135 + name: '雅迪 DE2',
136 + year: 2022,
137 + school: '北京大学',
138 + price: 2800,
139 + image: 'https://images.unsplash.com/photo-1571068316344-75bc76f77890?w=300&h=200&fit=crop'
140 + }
141 +])
142 +
143 +// 最新发布数据
144 +const latestScooters = ref([
145 + {
146 + id: 3,
147 + name: '爱玛 A500',
148 + year: 2024,
149 + school: '人民大学',
150 + price: 2600,
151 + image: 'https://images.unsplash.com/photo-1544191696-15693072e0d8?w=300&h=200&fit=crop'
152 + },
153 + {
154 + id: 4,
155 + name: '台铃 TDR',
156 + year: 2023,
157 + school: '北京理工',
158 + price: 3500,
159 + image: 'https://images.unsplash.com/photo-1558618047-3c8c76ca7d13?w=300&h=200&fit=crop'
160 + }
161 +])
162 +
163 +/**
164 + * 切换收藏状态
165 + * @param {number} id - 电动车ID
166 + */
167 +const toggleFavorite = (id) => {
168 + const index = favoriteIds.value.indexOf(id)
169 + if (index > -1) {
170 + favoriteIds.value.splice(index, 1)
171 + } else {
172 + favoriteIds.value.push(id)
173 + }
174 +}
175 +
176 +// 事件处理函数
177 +const onCategoryClick = () => {
178 + Taro.showToast({
179 + title: '选择了分类',
180 + icon: 'none'
181 + })
182 +}
183 +
184 +const onProductClick = () => {
185 + Taro.navigateTo({
186 + url: '/pages/detail/index'
187 + })
188 +}
189 +
190 +const onSearch = () => {
191 + Taro.showToast({
192 + title: '搜索功能',
193 + icon: 'none'
194 + })
195 +}
196 +
197 +/**
198 + * 查看更多点击事件
199 + * @param {string} type - 类型(hot/latest)
200 + */
201 +const onViewMore = (type) => {
202 + // 跳转到列表页面
203 +}
204 +</script>
205 +
206 +<style lang="less">
207 +.post-page {
208 + min-height: 100vh;
209 + background-color: #fef7ed;
210 + padding-bottom: 100px;
211 +}
212 +
213 +.search-container {
214 + padding: 16px;
215 + background-color: #ffffff;
216 + border-bottom: 1px solid #f3f4f6;
217 +}
218 +
219 +.search-box {
220 + display: flex;
221 + align-items: center;
222 + background-color: #f9fafb;
223 + border-radius: 24px;
224 + padding: 12px 16px;
225 + gap: 8px;
226 +}
227 +
228 +.search-input {
229 + flex: 1;
230 + border: none;
231 + outline: none;
232 + background: transparent;
233 + font-size: 14px;
234 + color: #374151;
235 +}
236 +
237 +.categories-grid {
238 + display: grid;
239 + grid-template-columns: repeat(3, 1fr);
240 + gap: 16px;
241 + padding: 20px 16px;
242 + background-color: #ffffff;
243 + margin-bottom: 12px;
244 +}
245 +
246 +.category-item {
247 + display: flex;
248 + flex-direction: column;
249 + align-items: center;
250 + padding: 20px 12px;
251 + background-color: #fef7ed;
252 + border-radius: 12px;
253 + border: 1px solid #fed7aa;
254 + transition: all 0.2s;
255 +}
256 +
257 +.category-item:active {
258 + transform: scale(0.95);
259 + background-color: #fef3e2;
260 +}
261 +
262 +.category-icon {
263 + margin-bottom: 8px;
264 +}
265 +
266 +.category-name {
267 + font-size: 14px;
268 + font-weight: 500;
269 + color: #374151;
270 + margin-bottom: 4px;
271 +}
272 +
273 +.category-count {
274 + font-size: 12px;
275 + color: #9ca3af;
276 +}
277 +
278 +.section {
279 + background-color: #ffffff;
280 + margin-bottom: 12px;
281 + padding: 16px;
282 +}
283 +
284 +.section-header {
285 + display: flex;
286 + justify-content: space-between;
287 + align-items: center;
288 + margin-bottom: 16px;
289 +}
290 +
291 +.section-title {
292 + font-size: 18px;
293 + font-weight: 600;
294 + color: #111827;
295 +}
296 +
297 +.section-more {
298 + display: flex;
299 + align-items: center;
300 + gap: 4px;
301 +}
302 +
303 +.more-text {
304 + font-size: 14px;
305 + color: #9ca3af;
306 +}
307 +
308 +.scooter-list {
309 + display: grid;
310 + grid-template-columns: repeat(2, 1fr);
311 + gap: 12px;
312 +}
313 +
314 +.scooter-card {
315 + background-color: #ffffff;
316 + border-radius: 12px;
317 + overflow: hidden;
318 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
319 + transition: all 0.2s;
320 +}
321 +
322 +.scooter-card:active {
323 + transform: scale(0.98);
324 + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
325 +}
326 +
327 +.scooter-image {
328 + width: 100%;
329 + height: 120px;
330 + object-fit: cover;
331 +}
332 +
333 +.scooter-info {
334 + padding: 12px;
335 +}
336 +
337 +.scooter-name {
338 + font-size: 14px;
339 + font-weight: 600;
340 + color: #111827;
341 + margin-bottom: 4px;
342 + display: block;
343 +}
344 +
345 +.scooter-year {
346 + font-size: 12px;
347 + color: #6b7280;
348 + margin-bottom: 2px;
349 + display: block;
350 +}
351 +
352 +.scooter-school {
353 + font-size: 12px;
354 + color: #9ca3af;
355 + margin-bottom: 8px;
356 + display: block;
357 +}
358 +
359 +.scooter-footer {
360 + display: flex;
361 + justify-content: space-between;
362 + align-items: center;
363 +}
364 +
365 +.scooter-price {
366 + font-size: 16px;
367 + font-weight: 700;
368 + color: #f97316;
369 +}
370 +
371 +.favorite-btn {
372 + padding: 4px;
373 + border-radius: 50%;
374 + transition: all 0.2s;
375 +}
376 +
377 +.favorite-btn:active {
378 + transform: scale(0.9);
379 +}
380 +</style>
1 +export default {
2 + navigationBarTitleText: '首页'
3 +}
1 +<template>
2 + <view class="profile-page">
3 + <!-- 用户信息卡片 -->
4 + <view class="user-card">
5 + <view class="user-info">
6 + <image :src="userInfo.avatar" class="user-avatar" mode="aspectFill" />
7 + <view class="user-details">
8 + <text class="user-name">{{ userInfo.name }}</text>
9 + <text class="user-school">{{ userInfo.school }}</text>
10 + <view class="user-stats">
11 + <text class="stat-item">信用分: {{ userInfo.creditScore }}</text>
12 + <text class="stat-item">成交: {{ userInfo.dealCount }}笔</text>
13 + </view>
14 + </view>
15 + </view>
16 + <view class="edit-btn" @click="onEditProfile">
17 + <Edit size="20" color="#f97316" />
18 + </view>
19 + </view>
20 +
21 + <!-- 我的车辆 -->
22 + <view class="section">
23 + <view class="section-header">
24 + <text class="section-title">我的车辆</text>
25 + </view>
26 + <view class="vehicle-stats">
27 + <view class="stat-card" @click="onMyVehicles('selling')">
28 + <text class="stat-number">{{ vehicleStats.selling }}</text>
29 + <text class="stat-label">在售</text>
30 + </view>
31 + <view class="stat-card" @click="onMyVehicles('sold')">
32 + <text class="stat-number">{{ vehicleStats.sold }}</text>
33 + <text class="stat-label">已售</text>
34 + </view>
35 + <view class="stat-card" @click="onMyFavorites">
36 + <text class="stat-number">{{ vehicleStats.favorites }}</text>
37 + <text class="stat-label">收藏</text>
38 + </view>
39 + </view>
40 + </view>
41 +
42 + <!-- 功能菜单 -->
43 + <view class="menu-section">
44 + <view class="menu-item" @click="onOrderManagement">
45 + <view class="menu-icon">
46 + <Cart size="20" color="#f97316" />
47 + </view>
48 + <text class="menu-text">订单管理</text>
49 + <Right size="16" color="#9ca3af" />
50 + </view>
51 +
52 + <view class="menu-item" @click="onCertification">
53 + <view class="menu-icon">
54 + <Service size="20" color="#f97316" />
55 + </view>
56 + <text class="menu-text">车辆认证</text>
57 + <view class="certification-badge">
58 + <text class="badge-text">{{ userInfo.isCertified ? '已认证' : '未认证' }}</text>
59 + </view>
60 + <Right size="16" color="#9ca3af" />
61 + </view>
62 +
63 + <view class="menu-item" @click="onWallet">
64 + <view class="menu-icon">
65 + <Shop size="20" color="#f97316" />
66 + </view>
67 + <text class="menu-text">我的钱包</text>
68 + <text class="wallet-balance">¥{{ userInfo.balance }}</text>
69 + <Right size="16" color="#9ca3af" />
70 + </view>
71 +
72 + <view class="menu-item" @click="onAddress">
73 + <view class="menu-icon">
74 + <Location size="20" color="#f97316" />
75 + </view>
76 + <text class="menu-text">收货地址</text>
77 + <Right size="16" color="#9ca3af" />
78 + </view>
79 + </view>
80 +
81 + <!-- 服务菜单 -->
82 + <view class="menu-section">
83 + <view class="menu-item" @click="onCustomerService">
84 + <view class="menu-icon">
85 + <Voice size="20" color="#f97316" />
86 + </view>
87 + <text class="menu-text">客服中心</text>
88 + <Right size="16" color="#9ca3af" />
89 + </view>
90 +
91 + <view class="menu-item" @click="onFeedback">
92 + <view class="menu-icon">
93 + <Message size="20" color="#f97316" />
94 + </view>
95 + <text class="menu-text">意见反馈</text>
96 + <Right size="16" color="#9ca3af" />
97 + </view>
98 +
99 + <view class="menu-item" @click="onAbout">
100 + <view class="menu-icon">
101 + <Tips size="20" color="#f97316" />
102 + </view>
103 + <text class="menu-text">关于我们</text>
104 + <Right size="16" color="#9ca3af" />
105 + </view>
106 + </view>
107 +
108 + <!-- 设置菜单 -->
109 + <view class="menu-section">
110 + <view class="menu-item" @click="onSettings">
111 + <view class="menu-icon">
112 + <Setting size="20" color="#f97316" />
113 + </view>
114 + <text class="menu-text">设置</text>
115 + <Right size="16" color="#9ca3af" />
116 + </view>
117 + </view>
118 +
119 + <!-- 退出登录 -->
120 + <view class="logout-section">
121 + <button class="logout-btn" @click="onLogout">退出登录</button>
122 + </view>
123 + </view>
124 +</template>
125 +
126 +<script setup>
127 +import { ref } from 'vue'
128 +import {
129 + Edit, Right, Cart, Service, Shop, Location,
130 + Voice, Message, Tips, Setting
131 +} from '@nutui/icons-vue-taro'
132 +import Taro from '@tarojs/taro'
133 +
134 +// 用户信息
135 +const userInfo = ref({
136 + name: '张同学',
137 + school: '清华大学',
138 + avatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100&h=100&fit=crop&crop=face',
139 + creditScore: 95,
140 + dealCount: 12,
141 + isCertified: true,
142 + balance: 1580.50
143 +})
144 +
145 +// 车辆统计
146 +const vehicleStats = ref({
147 + selling: 3,
148 + sold: 8,
149 + favorites: 15
150 +})
151 +
152 +/**
153 + * 编辑个人资料
154 + */
155 +const onEditProfile = () => {
156 + Taro.navigateTo({
157 + url: '/pages/edit-profile/index'
158 + })
159 +}
160 +
161 +/**
162 + * 我的车辆
163 + * @param {string} type - 车辆类型
164 + */
165 +const onMyVehicles = (type) => {
166 + Taro.navigateTo({
167 + url: `/pages/my-vehicles/index?type=${type}`
168 + })
169 +}
170 +
171 +/**
172 + * 我的收藏
173 + */
174 +const onMyFavorites = () => {
175 + Taro.navigateTo({
176 + url: '/pages/my-favorites/index'
177 + })
178 +}
179 +
180 +/**
181 + * 订单管理
182 + */
183 +const onOrderManagement = () => {
184 + Taro.navigateTo({
185 + url: '/pages/order-management/index'
186 + })
187 +}
188 +
189 +/**
190 + * 车辆认证
191 + */
192 +const onCertification = () => {
193 + if (userInfo.value.isCertified) {
194 + Taro.navigateTo({
195 + url: '/pages/certification-status/index'
196 + })
197 + } else {
198 + Taro.navigateTo({
199 + url: '/pages/certification-apply/index'
200 + })
201 + }
202 +}
203 +
204 +/**
205 + * 我的钱包
206 + */
207 +const onWallet = () => {
208 + Taro.navigateTo({
209 + url: '/pages/wallet/index'
210 + })
211 +}
212 +
213 +/**
214 + * 收货地址
215 + */
216 +const onAddress = () => {
217 + Taro.navigateTo({
218 + url: '/pages/address/index'
219 + })
220 +}
221 +
222 +/**
223 + * 客服中心
224 + */
225 +const onCustomerService = () => {
226 + Taro.showActionSheet({
227 + itemList: ['在线客服', '电话客服', '常见问题'],
228 + success: (res) => {
229 + if (res.tapIndex === 0) {
230 + // 在线客服
231 + Taro.navigateTo({
232 + url: '/pages/online-service/index'
233 + })
234 + } else if (res.tapIndex === 1) {
235 + // 电话客服
236 + Taro.makePhoneCall({
237 + phoneNumber: '400-123-4567'
238 + })
239 + } else if (res.tapIndex === 2) {
240 + // 常见问题
241 + Taro.navigateTo({
242 + url: '/pages/faq/index'
243 + })
244 + }
245 + }
246 + })
247 +}
248 +
249 +/**
250 + * 意见反馈
251 + */
252 +const onFeedback = () => {
253 + Taro.navigateTo({
254 + url: '/pages/feedback/index'
255 + })
256 +}
257 +
258 +/**
259 + * 关于我们
260 + */
261 +const onAbout = () => {
262 + Taro.navigateTo({
263 + url: '/pages/about/index'
264 + })
265 +}
266 +
267 +/**
268 + * 设置
269 + */
270 +const onSettings = () => {
271 + Taro.navigateTo({
272 + url: '/pages/settings/index'
273 + })
274 +}
275 +
276 +/**
277 + * 退出登录
278 + */
279 +const onLogout = () => {
280 + Taro.showModal({
281 + title: '确认退出',
282 + content: '确定要退出登录吗?',
283 + success: (res) => {
284 + if (res.confirm) {
285 + // 清除用户数据
286 + Taro.clearStorageSync()
287 +
288 + // 跳转到登录页
289 + Taro.redirectTo({
290 + url: '/pages/auth/index'
291 + })
292 +
293 + Taro.showToast({
294 + title: '已退出登录',
295 + icon: 'success'
296 + })
297 + }
298 + }
299 + })
300 +}
301 +</script>
302 +
303 +<style lang="less">
304 +.profile-page {
305 + min-height: 100vh;
306 + background-color: #f9fafb;
307 + padding-bottom: 100px;
308 +}
309 +
310 +.user-card {
311 + background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
312 + padding: 24px 16px;
313 + display: flex;
314 + justify-content: space-between;
315 + align-items: center;
316 +}
317 +
318 +.user-info {
319 + display: flex;
320 + align-items: center;
321 + flex: 1;
322 +}
323 +
324 +.user-avatar {
325 + width: 64px;
326 + height: 64px;
327 + border-radius: 50%;
328 + object-fit: cover;
329 + border: 3px solid rgba(255, 255, 255, 0.3);
330 + margin-right: 16px;
331 +}
332 +
333 +.user-details {
334 + flex: 1;
335 +}
336 +
337 +.user-name {
338 + font-size: 20px;
339 + font-weight: 600;
340 + color: #ffffff;
341 + display: block;
342 + margin-bottom: 4px;
343 +}
344 +
345 +.user-school {
346 + font-size: 14px;
347 + color: rgba(255, 255, 255, 0.8);
348 + display: block;
349 + margin-bottom: 8px;
350 +}
351 +
352 +.user-stats {
353 + display: flex;
354 + gap: 16px;
355 +}
356 +
357 +.stat-item {
358 + font-size: 12px;
359 + color: rgba(255, 255, 255, 0.9);
360 + background-color: rgba(255, 255, 255, 0.2);
361 + padding: 4px 8px;
362 + border-radius: 12px;
363 +}
364 +
365 +.edit-btn {
366 + width: 40px;
367 + height: 40px;
368 + background-color: rgba(255, 255, 255, 0.2);
369 + border-radius: 50%;
370 + display: flex;
371 + align-items: center;
372 + justify-content: center;
373 + transition: all 0.2s;
374 +}
375 +
376 +.edit-btn:active {
377 + transform: scale(0.95);
378 + background-color: rgba(255, 255, 255, 0.3);
379 +}
380 +
381 +.section {
382 + background-color: #ffffff;
383 + margin: 12px 16px;
384 + border-radius: 12px;
385 + padding: 20px;
386 +}
387 +
388 +.section-header {
389 + margin-bottom: 16px;
390 +}
391 +
392 +.section-title {
393 + font-size: 18px;
394 + font-weight: 600;
395 + color: #111827;
396 + display: block;
397 +}
398 +
399 +.vehicle-stats {
400 + display: grid;
401 + grid-template-columns: repeat(3, 1fr);
402 + gap: 16px;
403 +}
404 +
405 +.stat-card {
406 + text-align: center;
407 + padding: 16px 8px;
408 + background-color: #fef7ed;
409 + border-radius: 8px;
410 + border: 1px solid #fed7aa;
411 + transition: all 0.2s;
412 +}
413 +
414 +.stat-card:active {
415 + transform: scale(0.95);
416 + background-color: #fef3e2;
417 +}
418 +
419 +.stat-number {
420 + font-size: 24px;
421 + font-weight: 700;
422 + color: #f97316;
423 + display: block;
424 + margin-bottom: 4px;
425 +}
426 +
427 +.stat-label {
428 + font-size: 14px;
429 + color: #6b7280;
430 + display: block;
431 +}
432 +
433 +.menu-section {
434 + background-color: #ffffff;
435 + margin: 12px 16px;
436 + border-radius: 12px;
437 + overflow: hidden;
438 +}
439 +
440 +.menu-item {
441 + display: flex;
442 + align-items: center;
443 + padding: 16px 20px;
444 + border-bottom: 1px solid #f3f4f6;
445 + transition: background-color 0.2s;
446 +}
447 +
448 +.menu-item:last-child {
449 + border-bottom: none;
450 +}
451 +
452 +.menu-item:active {
453 + background-color: #f9fafb;
454 +}
455 +
456 +.menu-icon {
457 + width: 40px;
458 + height: 40px;
459 + background-color: #fef7ed;
460 + border-radius: 8px;
461 + display: flex;
462 + align-items: center;
463 + justify-content: center;
464 + margin-right: 12px;
465 +}
466 +
467 +.menu-text {
468 + flex: 1;
469 + font-size: 16px;
470 + color: #374151;
471 +}
472 +
473 +.certification-badge {
474 + margin-right: 8px;
475 +}
476 +
477 +.badge-text {
478 + font-size: 12px;
479 + color: #059669;
480 + background-color: #d1fae5;
481 + padding: 4px 8px;
482 + border-radius: 12px;
483 +}
484 +
485 +.wallet-balance {
486 + font-size: 16px;
487 + font-weight: 600;
488 + color: #f97316;
489 + margin-right: 8px;
490 +}
491 +
492 +.logout-section {
493 + margin: 24px 16px;
494 +}
495 +
496 +.logout-btn {
497 + width: 100%;
498 + padding: 16px;
499 + background-color: #ffffff;
500 + border: 1px solid #f87171;
501 + border-radius: 12px;
502 + color: #ef4444;
503 + font-size: 16px;
504 + font-weight: 500;
505 + transition: all 0.2s;
506 +}
507 +
508 +.logout-btn:active {
509 + background-color: #fef2f2;
510 + transform: scale(0.98);
511 +}
512 +</style>
...\ No newline at end of file ...\ No newline at end of file
1 +export default {
2 + navigationBarTitleText: '首页'
3 +}
1 +<template>
2 + <view class="sell-page">
3 + <!-- 顶部标题 -->
4 + <view class="header">
5 + <text class="header-title">发布车辆</text>
6 + <text class="header-subtitle">填写车辆信息,快速出售</text>
7 + </view>
8 +
9 + <!-- 表单内容 -->
10 + <view class="form-container">
11 + <!-- 车辆图片 -->
12 + <view class="form-section">
13 + <text class="section-title">车辆图片 <text class="required">*</text></text>
14 + <view class="image-upload-container">
15 + <view
16 + v-for="(image, index) in vehicleImages"
17 + :key="index"
18 + class="image-item"
19 + >
20 + <image :src="image" class="uploaded-image" mode="aspectFill" />
21 + <view class="image-delete" @click="removeImage(index)">
22 + <Close size="16" color="#ffffff" />
23 + </view>
24 + </view>
25 + <view
26 + v-if="vehicleImages.length < 6"
27 + class="image-upload-btn"
28 + @click="uploadImage"
29 + >
30 + <Plus size="24" color="#9ca3af" />
31 + <text class="upload-text">添加图片</text>
32 + </view>
33 + </view>
34 + <text class="form-tip">最多上传6张图片,第一张为封面图</text>
35 + </view>
36 +
37 + <!-- 基本信息 -->
38 + <view class="form-section">
39 + <text class="section-title">基本信息</text>
40 +
41 + <view class="form-item">
42 + <text class="form-label">车辆名称 <text class="required">*</text></text>
43 + <input
44 + v-model="formData.name"
45 + placeholder="请输入车辆名称"
46 + class="form-input"
47 + />
48 + </view>
49 +
50 + <view class="form-item">
51 + <text class="form-label">车辆品牌 <text class="required">*</text></text>
52 + <picker
53 + :range="brands"
54 + :value="brandIndex"
55 + @change="onBrandChange"
56 + class="form-picker"
57 + >
58 + <view class="picker-content">
59 + <text class="picker-text">{{ brands[brandIndex] || '请选择品牌' }}</text>
60 + <Right size="16" color="#9ca3af" />
61 + </view>
62 + </picker>
63 + </view>
64 +
65 + <view class="form-item">
66 + <text class="form-label">购买年份 <text class="required">*</text></text>
67 + <picker
68 + :range="years"
69 + :value="yearIndex"
70 + @change="onYearChange"
71 + class="form-picker"
72 + >
73 + <view class="picker-content">
74 + <text class="picker-text">{{ years[yearIndex] || '请选择年份' }}</text>
75 + <Right size="16" color="#9ca3af" />
76 + </view>
77 + </picker>
78 + </view>
79 +
80 + <view class="form-item">
81 + <text class="form-label">车辆类型 <text class="required">*</text></text>
82 + <picker
83 + :range="types"
84 + :value="typeIndex"
85 + @change="onTypeChange"
86 + class="form-picker"
87 + >
88 + <view class="picker-content">
89 + <text class="picker-text">{{ types[typeIndex] || '请选择类型' }}</text>
90 + <Right size="16" color="#9ca3af" />
91 + </view>
92 + </picker>
93 + </view>
94 +
95 + <view class="form-item">
96 + <text class="form-label">出售价格 <text class="required">*</text></text>
97 + <view class="price-input-container">
98 + <text class="price-symbol">¥</text>
99 + <input
100 + v-model="formData.price"
101 + placeholder="请输入价格"
102 + type="number"
103 + class="price-input"
104 + />
105 + </view>
106 + </view>
107 + </view>
108 +
109 + <!-- 车辆状况 -->
110 + <view class="form-section">
111 + <text class="section-title">车辆状况</text>
112 +
113 + <view class="form-item">
114 + <text class="form-label">使用时长</text>
115 + <picker
116 + :range="usagePeriods"
117 + :value="usageIndex"
118 + @change="onUsageChange"
119 + class="form-picker"
120 + >
121 + <view class="picker-content">
122 + <text class="picker-text">{{ usagePeriods[usageIndex] || '请选择使用时长' }}</text>
123 + <Right size="16" color="#9ca3af" />
124 + </view>
125 + </picker>
126 + </view>
127 +
128 + <view class="form-item">
129 + <text class="form-label">车辆描述</text>
130 + <textarea
131 + v-model="formData.description"
132 + placeholder="请描述车辆的具体情况,如外观、性能、配件等"
133 + class="form-textarea"
134 + maxlength="500"
135 + />
136 + <text class="char-count">{{ formData.description.length }}/500</text>
137 + </view>
138 + </view>
139 +
140 + <!-- 联系方式 -->
141 + <view class="form-section">
142 + <text class="section-title">联系方式</text>
143 +
144 + <view class="form-item">
145 + <text class="form-label">手机号码 <text class="required">*</text></text>
146 + <input
147 + v-model="formData.phone"
148 + placeholder="请输入手机号码"
149 + type="number"
150 + class="form-input"
151 + />
152 + </view>
153 +
154 + <view class="form-item">
155 + <text class="form-label">所在学校 <text class="required">*</text></text>
156 + <input
157 + v-model="formData.school"
158 + placeholder="请输入所在学校"
159 + class="form-input"
160 + />
161 + </view>
162 +
163 + <view class="form-item">
164 + <text class="form-label">交易地点</text>
165 + <input
166 + v-model="formData.location"
167 + placeholder="请输入具体交易地点"
168 + class="form-input"
169 + />
170 + </view>
171 + </view>
172 + </view>
173 +
174 + <!-- 底部按钮 -->
175 + <view class="bottom-actions">
176 + <button class="preview-btn" @click="onPreview">预览</button>
177 + <button class="publish-btn" @click="onPublish">发布车辆</button>
178 + </view>
179 + </view>
180 +</template>
181 +
182 +<script setup>
183 +import { ref, reactive } from 'vue'
184 +import { Close, Plus, Right } from '@nutui/icons-vue-taro'
185 +import Taro from '@tarojs/taro'
186 +
187 +// 响应式数据
188 +const vehicleImages = ref([])
189 +const brandIndex = ref(-1)
190 +const yearIndex = ref(-1)
191 +const typeIndex = ref(-1)
192 +const usageIndex = ref(-1)
193 +
194 +// 表单数据
195 +const formData = reactive({
196 + name: '',
197 + brand: '',
198 + year: '',
199 + type: '',
200 + price: '',
201 + usage: '',
202 + description: '',
203 + phone: '',
204 + school: '',
205 + location: ''
206 +})
207 +
208 +// 选择器数据
209 +const brands = ['小牛电动', '雅迪', '爱玛', '台铃', '绿源', '新日', '立马', '其他']
210 +const years = ['2024年', '2023年', '2022年', '2021年', '2020年', '2019年', '2018年', '更早']
211 +const types = ['电动自行车', '电动摩托车', '电动汽车', '平衡车', '滑板车', '其他']
212 +const usagePeriods = ['3个月以内', '3-6个月', '6个月-1年', '1-2年', '2-3年', '3年以上']
213 +
214 +/**
215 + * 上传图片
216 + */
217 +const uploadImage = () => {
218 + Taro.chooseImage({
219 + count: 6 - vehicleImages.value.length,
220 + sizeType: ['compressed'],
221 + sourceType: ['album', 'camera'],
222 + success: (res) => {
223 + vehicleImages.value.push(...res.tempFilePaths)
224 + }
225 + })
226 +}
227 +
228 +/**
229 + * 删除图片
230 + * @param {number} index - 图片索引
231 + */
232 +const removeImage = (index) => {
233 + vehicleImages.value.splice(index, 1)
234 +}
235 +
236 +/**
237 + * 品牌选择事件
238 + * @param {object} e - 事件对象
239 + */
240 +const onBrandChange = (e) => {
241 + brandIndex.value = e.detail.value
242 + formData.brand = brands[e.detail.value]
243 +}
244 +
245 +/**
246 + * 年份选择事件
247 + * @param {object} e - 事件对象
248 + */
249 +const onYearChange = (e) => {
250 + yearIndex.value = e.detail.value
251 + formData.year = years[e.detail.value]
252 +}
253 +
254 +/**
255 + * 类型选择事件
256 + * @param {object} e - 事件对象
257 + */
258 +const onTypeChange = (e) => {
259 + typeIndex.value = e.detail.value
260 + formData.type = types[e.detail.value]
261 +}
262 +
263 +/**
264 + * 使用时长选择事件
265 + * @param {object} e - 事件对象
266 + */
267 +const onUsageChange = (e) => {
268 + usageIndex.value = e.detail.value
269 + formData.usage = usagePeriods[e.detail.value]
270 +}
271 +
272 +/**
273 + * 预览功能
274 + */
275 +const onPreview = () => {
276 + if (!validateForm()) return
277 +
278 + Taro.showToast({
279 + title: '预览功能开发中',
280 + icon: 'none'
281 + })
282 +}
283 +
284 +/**
285 + * 发布车辆
286 + */
287 +const onPublish = () => {
288 + if (!validateForm()) return
289 +
290 + Taro.showLoading({ title: '发布中...' })
291 +
292 + // 模拟发布请求
293 + setTimeout(() => {
294 + Taro.hideLoading()
295 + Taro.showToast({
296 + title: '发布成功',
297 + icon: 'success'
298 + })
299 +
300 + // 发布成功后跳转到首页
301 + setTimeout(() => {
302 + Taro.switchTab({ url: '/pages/index/index' })
303 + }, 1500)
304 + }, 2000)
305 +}
306 +
307 +/**
308 + * 表单验证
309 + * @returns {boolean} 验证结果
310 + */
311 +const validateForm = () => {
312 + if (vehicleImages.value.length === 0) {
313 + Taro.showToast({ title: '请上传车辆图片', icon: 'none' })
314 + return false
315 + }
316 +
317 + if (!formData.name.trim()) {
318 + Taro.showToast({ title: '请输入车辆名称', icon: 'none' })
319 + return false
320 + }
321 +
322 + if (!formData.brand) {
323 + Taro.showToast({ title: '请选择车辆品牌', icon: 'none' })
324 + return false
325 + }
326 +
327 + if (!formData.year) {
328 + Taro.showToast({ title: '请选择购买年份', icon: 'none' })
329 + return false
330 + }
331 +
332 + if (!formData.type) {
333 + Taro.showToast({ title: '请选择车辆类型', icon: 'none' })
334 + return false
335 + }
336 +
337 + if (!formData.price || formData.price <= 0) {
338 + Taro.showToast({ title: '请输入正确的价格', icon: 'none' })
339 + return false
340 + }
341 +
342 + if (!formData.phone.trim()) {
343 + Taro.showToast({ title: '请输入手机号码', icon: 'none' })
344 + return false
345 + }
346 +
347 + if (!/^1[3-9]\d{9}$/.test(formData.phone)) {
348 + Taro.showToast({ title: '请输入正确的手机号码', icon: 'none' })
349 + return false
350 + }
351 +
352 + if (!formData.school.trim()) {
353 + Taro.showToast({ title: '请输入所在学校', icon: 'none' })
354 + return false
355 + }
356 +
357 + return true
358 +}
359 +</script>
360 +
361 +<style lang="less">
362 +.sell-page {
363 + min-height: 100vh;
364 + background-color: #f9fafb;
365 + padding-bottom: 80px;
366 +}
367 +
368 +.header {
369 + background-color: #ffffff;
370 + padding: 20px 16px;
371 + border-bottom: 1px solid #f3f4f6;
372 +}
373 +
374 +.header-title {
375 + font-size: 24px;
376 + font-weight: 700;
377 + color: #111827;
378 + display: block;
379 + margin-bottom: 4px;
380 +}
381 +
382 +.header-subtitle {
383 + font-size: 14px;
384 + color: #6b7280;
385 + display: block;
386 +}
387 +
388 +.form-container {
389 + padding: 0 16px;
390 +}
391 +
392 +.form-section {
393 + background-color: #ffffff;
394 + border-radius: 12px;
395 + padding: 20px;
396 + margin: 12px 0;
397 +}
398 +
399 +.section-title {
400 + font-size: 18px;
401 + font-weight: 600;
402 + color: #111827;
403 + margin-bottom: 16px;
404 + display: block;
405 +}
406 +
407 +.required {
408 + color: #ef4444;
409 +}
410 +
411 +.image-upload-container {
412 + display: grid;
413 + grid-template-columns: repeat(3, 1fr);
414 + gap: 12px;
415 + margin-bottom: 8px;
416 +}
417 +
418 +.image-item {
419 + position: relative;
420 + aspect-ratio: 1;
421 + border-radius: 8px;
422 + overflow: hidden;
423 +}
424 +
425 +.uploaded-image {
426 + width: 100%;
427 + height: 100%;
428 + object-fit: cover;
429 +}
430 +
431 +.image-delete {
432 + position: absolute;
433 + top: 4px;
434 + right: 4px;
435 + width: 24px;
436 + height: 24px;
437 + background-color: rgba(0, 0, 0, 0.6);
438 + border-radius: 50%;
439 + display: flex;
440 + align-items: center;
441 + justify-content: center;
442 +}
443 +
444 +.image-upload-btn {
445 + aspect-ratio: 1;
446 + border: 2px dashed #d1d5db;
447 + border-radius: 8px;
448 + display: flex;
449 + flex-direction: column;
450 + align-items: center;
451 + justify-content: center;
452 + gap: 4px;
453 + background-color: #f9fafb;
454 +}
455 +
456 +.upload-text {
457 + font-size: 12px;
458 + color: #9ca3af;
459 +}
460 +
461 +.form-tip {
462 + font-size: 12px;
463 + color: #9ca3af;
464 + display: block;
465 +}
466 +
467 +.form-item {
468 + margin-bottom: 16px;
469 +}
470 +
471 +.form-label {
472 + font-size: 14px;
473 + font-weight: 500;
474 + color: #374151;
475 + margin-bottom: 8px;
476 + display: block;
477 +}
478 +
479 +.form-input {
480 + width: 100%;
481 + padding: 12px 16px;
482 + border: 1px solid #d1d5db;
483 + border-radius: 8px;
484 + font-size: 14px;
485 + color: #374151;
486 + background-color: #ffffff;
487 +}
488 +
489 +.form-input:focus {
490 + border-color: #f97316;
491 + outline: none;
492 +}
493 +
494 +.form-picker {
495 + width: 100%;
496 +}
497 +
498 +.picker-content {
499 + display: flex;
500 + justify-content: space-between;
501 + align-items: center;
502 + padding: 12px 16px;
503 + border: 1px solid #d1d5db;
504 + border-radius: 8px;
505 + background-color: #ffffff;
506 +}
507 +
508 +.picker-text {
509 + font-size: 14px;
510 + color: #374151;
511 +}
512 +
513 +.price-input-container {
514 + display: flex;
515 + align-items: center;
516 + border: 1px solid #d1d5db;
517 + border-radius: 8px;
518 + background-color: #ffffff;
519 +}
520 +
521 +.price-symbol {
522 + padding: 12px 0 12px 16px;
523 + font-size: 14px;
524 + color: #374151;
525 + font-weight: 500;
526 +}
527 +
528 +.price-input {
529 + flex: 1;
530 + padding: 12px 16px 12px 4px;
531 + border: none;
532 + font-size: 14px;
533 + color: #374151;
534 + background: transparent;
535 +}
536 +
537 +.form-textarea {
538 + width: 100%;
539 + min-height: 80px;
540 + padding: 12px 16px;
541 + border: 1px solid #d1d5db;
542 + border-radius: 8px;
543 + font-size: 14px;
544 + color: #374151;
545 + background-color: #ffffff;
546 + resize: none;
547 +}
548 +
549 +.form-textarea:focus {
550 + border-color: #f97316;
551 + outline: none;
552 +}
553 +
554 +.char-count {
555 + font-size: 12px;
556 + color: #9ca3af;
557 + text-align: right;
558 + margin-top: 4px;
559 + display: block;
560 +}
561 +
562 +.bottom-actions {
563 + position: fixed;
564 + bottom: 0;
565 + left: 0;
566 + right: 0;
567 + background-color: #ffffff;
568 + padding: 12px 16px;
569 + border-top: 1px solid #f3f4f6;
570 + display: flex;
571 + gap: 12px;
572 +}
573 +
574 +.preview-btn {
575 + flex: 1;
576 + padding: 12px;
577 + border: 1px solid #f97316;
578 + border-radius: 8px;
579 + background-color: #ffffff;
580 + color: #f97316;
581 + font-size: 16px;
582 + font-weight: 500;
583 +}
584 +
585 +.publish-btn {
586 + flex: 2;
587 + padding: 12px;
588 + border: none;
589 + border-radius: 8px;
590 + background-color: #f97316;
591 + color: #ffffff;
592 + font-size: 16px;
593 + font-weight: 500;
594 +}
595 +
596 +.preview-btn:active {
597 + background-color: #fef7ed;
598 +}
599 +
600 +.publish-btn:active {
601 + background-color: #ea580c;
602 +}
603 +</style>
...\ No newline at end of file ...\ No newline at end of file