hookehuyr

feat(认证): 添加车辆认证页面及功能

实现车辆认证页面,包括图片上传、表单填写和提交功能
将样式从vue文件拆分到单独的less文件
更新路由配置添加认证页面
修复认证跳转链接错误
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-02 17:15:42 4 + * @LastEditTime: 2025-07-02 18:16:09
5 * @FilePath: /jgdl/src/app.config.js 5 * @FilePath: /jgdl/src/app.config.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
...@@ -15,6 +15,7 @@ export default { ...@@ -15,6 +15,7 @@ export default {
15 'pages/editProfile/index', 15 'pages/editProfile/index',
16 'pages/register/index', 16 'pages/register/index',
17 'pages/authCar/index', 17 'pages/authCar/index',
18 + 'pages/setAuthCar/index',
18 'pages/auth/index', 19 'pages/auth/index',
19 ], 20 ],
20 subpackages: [ // 配置在tabBar中的页面不能分包写到subpackages中去 21 subpackages: [ // 配置在tabBar中的页面不能分包写到subpackages中去
......
1 <!-- 1 <!--
2 * @Date: 2022-09-19 14:11:06 2 * @Date: 2022-09-19 14:11:06
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-07-02 17:36:43 4 + * @LastEditTime: 2025-07-02 18:15:19
5 * @FilePath: /jgdl/src/pages/authCar/index.vue 5 * @FilePath: /jgdl/src/pages/authCar/index.vue
6 * @Description: 认证车源 6 * @Description: 认证车源
7 --> 7 -->
...@@ -107,6 +107,7 @@ ...@@ -107,6 +107,7 @@
107 </template> 107 </template>
108 108
109 <script setup> 109 <script setup>
110 +import Taro from '@tarojs/taro'
110 import { ref, computed, onMounted } from 'vue' 111 import { ref, computed, onMounted } from 'vue'
111 import { Check, RectRight, Addfollow, HeartFill } from '@nutui/icons-vue-taro' 112 import { Check, RectRight, Addfollow, HeartFill } from '@nutui/icons-vue-taro'
112 import './index.less' 113 import './index.less'
...@@ -189,9 +190,8 @@ const scrollStyle = computed(() => { ...@@ -189,9 +190,8 @@ const scrollStyle = computed(() => {
189 * 处理认证按钮点击 190 * 处理认证按钮点击
190 */ 191 */
191 const handleAuth = () => { 192 const handleAuth = () => {
192 - showToast('跳转到认证页面', 'success')
193 // TODO: 跳转到认证页面 193 // TODO: 跳转到认证页面
194 - // Taro.navigateTo({ url: '/pages/auth/index' }) 194 + Taro.navigateTo({ url: '/pages/setAuthCar/index' })
195 } 195 }
196 196
197 /** 197 /**
......
...@@ -141,7 +141,7 @@ ...@@ -141,7 +141,7 @@
141 <script setup> 141 <script setup>
142 import { ref, reactive, computed, onMounted } from 'vue' 142 import { ref, reactive, computed, onMounted } from 'vue'
143 import Taro from '@tarojs/taro' 143 import Taro from '@tarojs/taro'
144 -import { RectLeft, Camera, Right } from '@nutui/icons-vue-taro' 144 +import { RectLeft, Right } from '@nutui/icons-vue-taro'
145 import './index.less' 145 import './index.less'
146 146
147 // 主题配置 147 // 主题配置
......
1 +.sell-page {
2 + min-height: 100vh;
3 + background-color: #f5f5f5;
4 + padding-bottom: 120rpx;
5 +}
6 +
7 +.form-container {
8 + padding: 0 32rpx;
9 + margin-bottom: 2rem;
10 +}
11 +
12 +.form-section {
13 + background-color: #ffffff;
14 + border-radius: 24rpx;
15 + padding: 32rpx;
16 + margin: 24rpx 0;
17 +}
18 +
19 +.section-title {
20 + font-size: 32rpx;
21 + font-weight: 600;
22 + color: #111827;
23 + margin-bottom: 32rpx;
24 + display: block;
25 +}
26 +
27 +.upload-grid {
28 + display: grid;
29 + grid-template-columns: repeat(2, 1fr);
30 + gap: 32rpx;
31 + margin-top: 24rpx;
32 +}
33 +
34 +.upload-item {
35 + display: flex;
36 + flex-direction: column;
37 + align-items: center;
38 + gap: 16rpx;
39 +}
40 +
41 +.upload-button {
42 + width: 160rpx;
43 + height: 160rpx;
44 + border: 2rpx dashed #d1d5db;
45 + border-radius: 16rpx;
46 + display: flex;
47 + align-items: center;
48 + justify-content: center;
49 + background-color: #f9fafb;
50 + transition: all 0.3s ease;
51 +}
52 +
53 +.upload-button:active {
54 + background-color: #f3f4f6;
55 + border-color: #f97316;
56 +}
57 +
58 +.upload-icon {
59 + font-size: 48rpx;
60 + color: #9ca3af;
61 +}
62 +
63 +.upload-label {
64 + font-size: 24rpx;
65 + color: #6b7280;
66 + text-align: center;
67 +}
68 +
69 +.image-preview {
70 + position: relative;
71 + width: 160rpx;
72 + height: 160rpx;
73 + border-radius: 16rpx;
74 + overflow: hidden;
75 +}
76 +
77 +.preview-image {
78 + width: 100%;
79 + height: 100%;
80 + object-fit: cover;
81 +}
82 +
83 +.delete-btn {
84 + position: absolute;
85 + top: 8rpx;
86 + right: 8rpx;
87 + width: 32rpx;
88 + height: 32rpx;
89 + background-color: #ef4444;
90 + border-radius: 50%;
91 + display: flex;
92 + align-items: center;
93 + justify-content: center;
94 + color: white;
95 + font-size: 16rpx;
96 + z-index: 10;
97 + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
98 +}
99 +
100 +.delete-icon {
101 + font-size: 16rpx;
102 + color: white;
103 +}
104 +
105 +.form-item {
106 + display: flex;
107 + justify-content: space-between;
108 + align-items: center;
109 + padding: 32rpx 0;
110 + border-bottom: 1rpx solid #f3f4f6;
111 +}
112 +
113 +.form-item:last-child {
114 + border-bottom: none;
115 +}
116 +
117 +.form-item-left {
118 + display: flex;
119 + align-items: center;
120 +}
121 +
122 +.form-icon {
123 + margin-right: 16rpx;
124 + color: #9ca3af;
125 +}
126 +
127 +.form-label {
128 + font-size: 28rpx;
129 + color: #374151;
130 +}
131 +
132 +.form-item-right {
133 + display: flex;
134 + align-items: center;
135 +}
136 +
137 +.form-value {
138 + font-size: 28rpx;
139 + color: #9ca3af;
140 + margin-right: 16rpx;
141 +}
142 +
143 +.arrow-icon {
144 + color: #9ca3af;
145 +}
146 +
147 +.form-item-content {
148 + display: flex;
149 + justify-content: space-between;
150 + align-items: center;
151 + width: 100%;
152 + padding: 24rpx 0;
153 +}
154 +
155 +.unit {
156 + font-size: 28rpx;
157 + color: #9ca3af;
158 + margin-left: 16rpx;
159 +}
160 +
161 +.price-symbol {
162 + font-size: 32rpx;
163 + color: #f97316;
164 + font-weight: 600;
165 + margin-right: 8rpx;
166 +}
167 +
168 +.price-input {
169 + font-size: 32rpx;
170 + color: #f97316;
171 + font-weight: 600;
172 + text-align: right;
173 + border: none;
174 + outline: none;
175 + background: transparent;
176 + width: 160rpx;
177 +}
178 +
179 +.market-price-symbol {
180 + font-size: 28rpx;
181 + color: #9ca3af;
182 + margin-right: 8rpx;
183 +}
184 +
185 +.market-price-input {
186 + font-size: 28rpx;
187 + color: #9ca3af;
188 + text-align: right;
189 + border: none;
190 + outline: none;
191 + background: transparent;
192 + width: 160rpx;
193 +}
194 +
195 +.bottom-actions {
196 + position: fixed;
197 + bottom: 0;
198 + left: 0;
199 + right: 0;
200 + background-color: #ffffff;
201 + padding: 24rpx 32rpx;
202 + border-top: 1rpx solid #f3f4f6;
203 + z-index: 100;
204 +}
205 +
206 +// NutUI组件样式覆盖
207 +:deep(.nut-form-item) {
208 + padding: 0;
209 + margin-bottom: 0;
210 + border-bottom: 1rpx solid #f3f4f6;
211 +}
212 +
213 +// :deep(.nut-form-item:last-child) {
214 +// border-bottom: none;
215 +// }
216 +
217 +:deep(.nut-form-item__body) {
218 + padding: 32rpx 0;
219 +}
220 +
221 +:deep(.nut-form-item__label) {
222 + font-size: 28rpx;
223 + color: #374151;
224 + margin-right: 32rpx;
225 +}
226 +
227 +:deep(.nut-input) {
228 + text-align: right;
229 +}
230 +
231 +:deep(.nut-input__input) {
232 + font-size: 28rpx;
233 + color: #374151;
234 + text-align: right;
235 +}
236 +
237 +:deep(.nut-textarea) {
238 + border: 1rpx solid #e5e7eb;
239 + border-radius: 16rpx;
240 + padding: 24rpx;
241 +}
242 +
243 +:deep(.nut-textarea__textarea) {
244 + font-size: 28rpx;
245 + color: #374151;
246 + line-height: 1.5;
247 +}
248 +
249 +:deep(.nut-uploader) {
250 + margin-bottom: 0;
251 +}
252 +
253 +:deep(.nut-uploader__preview) {
254 + margin-bottom: 0;
255 +}
256 +
257 +:deep(.nut-uploader__preview-img) {
258 + width: 160rpx;
259 + height: 160rpx;
260 + border-radius: 16rpx;
261 + object-fit: cover;
262 +}
263 +
264 +:deep(.nut-uploader__upload) {
265 + width: 160rpx;
266 + height: 160rpx;
267 +}
268 +
269 +:deep(.nut-uploader__input) {
270 + width: 100%;
271 + height: 100%;
272 +}
273 +
274 +:deep(.nut-uploader__preview-delete) {
275 + position: absolute;
276 + top: 8rpx;
277 + right: 8rpx;
278 + width: 32rpx;
279 + height: 32rpx;
280 + background-color: rgba(0, 0, 0, 0.6);
281 + border-radius: 50%;
282 + display: flex;
283 + align-items: center;
284 + justify-content: center;
285 + color: #ffffff;
286 + font-size: 20rpx;
287 +}
288 +
289 +:deep(.nut-picker__toolbar) {
290 + padding: 24rpx 32rpx;
291 +}
292 +
293 +:deep(.nut-picker__cancel) {
294 + color: #9ca3af;
295 +}
296 +
297 +:deep(.nut-picker__confirm) {
298 + color: #f97316;
299 +}
300 +
301 +:deep(.nut-picker__title) {
302 + font-size: 32rpx;
303 + font-weight: 600;
304 +}
...@@ -296,6 +296,7 @@ import { ref, reactive } from 'vue' ...@@ -296,6 +296,7 @@ import { ref, reactive } from 'vue'
296 import { Plus, Right, Location, RectLeft, Close } from '@nutui/icons-vue-taro' 296 import { Plus, Right, Location, RectLeft, Close } from '@nutui/icons-vue-taro'
297 import Taro from '@tarojs/taro' 297 import Taro from '@tarojs/taro'
298 // import BASE_URL from '@/utils/config'; 298 // import BASE_URL from '@/utils/config';
299 +import './index.less'
299 300
300 const themeVars = ref({ 301 const themeVars = ref({
301 navbarBackground: '#fb923c', 302 navbarBackground: '#fb923c',
...@@ -761,310 +762,3 @@ const validateForm = () => { ...@@ -761,310 +762,3 @@ const validateForm = () => {
761 return true 762 return true
762 } 763 }
763 </script> 764 </script>
764 -
765 -<style lang="less">
766 -.sell-page {
767 - min-height: 100vh;
768 - background-color: #f5f5f5;
769 - padding-bottom: 120rpx;
770 -}
771 -
772 -.form-container {
773 - padding: 0 32rpx;
774 - margin-bottom: 2rem;
775 -}
776 -
777 -.form-section {
778 - background-color: #ffffff;
779 - border-radius: 24rpx;
780 - padding: 32rpx;
781 - margin: 24rpx 0;
782 -}
783 -
784 -.section-title {
785 - font-size: 32rpx;
786 - font-weight: 600;
787 - color: #111827;
788 - margin-bottom: 32rpx;
789 - display: block;
790 -}
791 -
792 -.upload-grid {
793 - display: grid;
794 - grid-template-columns: repeat(2, 1fr);
795 - gap: 32rpx;
796 - margin-top: 24rpx;
797 -}
798 -
799 -.upload-item {
800 - display: flex;
801 - flex-direction: column;
802 - align-items: center;
803 - gap: 16rpx;
804 -}
805 -
806 -.upload-button {
807 - width: 160rpx;
808 - height: 160rpx;
809 - border: 2rpx dashed #d1d5db;
810 - border-radius: 16rpx;
811 - display: flex;
812 - align-items: center;
813 - justify-content: center;
814 - background-color: #f9fafb;
815 - transition: all 0.3s ease;
816 -}
817 -
818 -.upload-button:active {
819 - background-color: #f3f4f6;
820 - border-color: #f97316;
821 -}
822 -
823 -.upload-icon {
824 - font-size: 48rpx;
825 - color: #9ca3af;
826 -}
827 -
828 -.upload-label {
829 - font-size: 24rpx;
830 - color: #6b7280;
831 - text-align: center;
832 -}
833 -
834 -.image-preview {
835 - position: relative;
836 - width: 160rpx;
837 - height: 160rpx;
838 - border-radius: 16rpx;
839 - overflow: hidden;
840 -}
841 -
842 -.preview-image {
843 - width: 100%;
844 - height: 100%;
845 - object-fit: cover;
846 -}
847 -
848 -.delete-btn {
849 - position: absolute;
850 - top: 8rpx;
851 - right: 8rpx;
852 - width: 32rpx;
853 - height: 32rpx;
854 - background-color: #ef4444;
855 - border-radius: 50%;
856 - display: flex;
857 - align-items: center;
858 - justify-content: center;
859 - color: white;
860 - font-size: 16rpx;
861 - z-index: 10;
862 - box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
863 -}
864 -
865 -.delete-icon {
866 - font-size: 16rpx;
867 - color: white;
868 -}
869 -
870 -.form-item {
871 - display: flex;
872 - justify-content: space-between;
873 - align-items: center;
874 - padding: 32rpx 0;
875 - border-bottom: 1rpx solid #f3f4f6;
876 -}
877 -
878 -.form-item:last-child {
879 - border-bottom: none;
880 -}
881 -
882 -.form-item-left {
883 - display: flex;
884 - align-items: center;
885 -}
886 -
887 -.form-icon {
888 - margin-right: 16rpx;
889 - color: #9ca3af;
890 -}
891 -
892 -.form-label {
893 - font-size: 28rpx;
894 - color: #374151;
895 -}
896 -
897 -.form-item-right {
898 - display: flex;
899 - align-items: center;
900 -}
901 -
902 -.form-value {
903 - font-size: 28rpx;
904 - color: #9ca3af;
905 - margin-right: 16rpx;
906 -}
907 -
908 -.arrow-icon {
909 - color: #9ca3af;
910 -}
911 -
912 -.form-item-content {
913 - display: flex;
914 - justify-content: space-between;
915 - align-items: center;
916 - width: 100%;
917 - padding: 24rpx 0;
918 -}
919 -
920 -.unit {
921 - font-size: 28rpx;
922 - color: #9ca3af;
923 - margin-left: 16rpx;
924 -}
925 -
926 -.price-symbol {
927 - font-size: 32rpx;
928 - color: #f97316;
929 - font-weight: 600;
930 - margin-right: 8rpx;
931 -}
932 -
933 -.price-input {
934 - font-size: 32rpx;
935 - color: #f97316;
936 - font-weight: 600;
937 - text-align: right;
938 - border: none;
939 - outline: none;
940 - background: transparent;
941 - width: 160rpx;
942 -}
943 -
944 -.market-price-symbol {
945 - font-size: 28rpx;
946 - color: #9ca3af;
947 - margin-right: 8rpx;
948 -}
949 -
950 -.market-price-input {
951 - font-size: 28rpx;
952 - color: #9ca3af;
953 - text-align: right;
954 - border: none;
955 - outline: none;
956 - background: transparent;
957 - width: 160rpx;
958 -}
959 -
960 -.bottom-actions {
961 - position: fixed;
962 - bottom: 0;
963 - left: 0;
964 - right: 0;
965 - background-color: #ffffff;
966 - padding: 24rpx 32rpx;
967 - border-top: 1rpx solid #f3f4f6;
968 - z-index: 100;
969 -}
970 -
971 -// NutUI组件样式覆盖
972 -:deep(.nut-form-item) {
973 - padding: 0;
974 - margin-bottom: 0;
975 - border-bottom: 1rpx solid #f3f4f6;
976 -}
977 -
978 -// :deep(.nut-form-item:last-child) {
979 -// border-bottom: none;
980 -// }
981 -
982 -:deep(.nut-form-item__body) {
983 - padding: 32rpx 0;
984 -}
985 -
986 -:deep(.nut-form-item__label) {
987 - font-size: 28rpx;
988 - color: #374151;
989 - margin-right: 32rpx;
990 -}
991 -
992 -:deep(.nut-input) {
993 - text-align: right;
994 -}
995 -
996 -:deep(.nut-input__input) {
997 - font-size: 28rpx;
998 - color: #374151;
999 - text-align: right;
1000 -}
1001 -
1002 -:deep(.nut-textarea) {
1003 - border: 1rpx solid #e5e7eb;
1004 - border-radius: 16rpx;
1005 - padding: 24rpx;
1006 -}
1007 -
1008 -:deep(.nut-textarea__textarea) {
1009 - font-size: 28rpx;
1010 - color: #374151;
1011 - line-height: 1.5;
1012 -}
1013 -
1014 -:deep(.nut-uploader) {
1015 - margin-bottom: 0;
1016 -}
1017 -
1018 -:deep(.nut-uploader__preview) {
1019 - margin-bottom: 0;
1020 -}
1021 -
1022 -:deep(.nut-uploader__preview-img) {
1023 - width: 160rpx;
1024 - height: 160rpx;
1025 - border-radius: 16rpx;
1026 - object-fit: cover;
1027 -}
1028 -
1029 -:deep(.nut-uploader__upload) {
1030 - width: 160rpx;
1031 - height: 160rpx;
1032 -}
1033 -
1034 -:deep(.nut-uploader__input) {
1035 - width: 100%;
1036 - height: 100%;
1037 -}
1038 -
1039 -:deep(.nut-uploader__preview-delete) {
1040 - position: absolute;
1041 - top: 8rpx;
1042 - right: 8rpx;
1043 - width: 32rpx;
1044 - height: 32rpx;
1045 - background-color: rgba(0, 0, 0, 0.6);
1046 - border-radius: 50%;
1047 - display: flex;
1048 - align-items: center;
1049 - justify-content: center;
1050 - color: #ffffff;
1051 - font-size: 20rpx;
1052 -}
1053 -
1054 -:deep(.nut-picker__toolbar) {
1055 - padding: 24rpx 32rpx;
1056 -}
1057 -
1058 -:deep(.nut-picker__cancel) {
1059 - color: #9ca3af;
1060 -}
1061 -
1062 -:deep(.nut-picker__confirm) {
1063 - color: #f97316;
1064 -}
1065 -
1066 -:deep(.nut-picker__title) {
1067 - font-size: 32rpx;
1068 - font-weight: 600;
1069 -}
1070 -</style>
......
1 +/*
2 + * @Date: 2025-07-02 17:52:43
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-07-02 17:53:28
5 + * @FilePath: /jgdl/src/pages/setAuthCar/index.config.js
6 + * @Description: 文件描述
7 + */
8 +export default {
9 + navigationBarTitleText: '申请认证',
10 + usingComponents: {
11 + },
12 +}
1 +/* 申请认证页面样式 */
2 +.auth-car-page {
3 + min-height: 100vh;
4 + background-color: #f5f5f5;
5 + padding-bottom: 200rpx;
6 +}
7 +
8 +.form-container {
9 + padding: 20rpx 32rpx;
10 + margin-bottom: 2rem;
11 +}
12 +
13 +.form-section {
14 + background-color: #ffffff;
15 + border-radius: 24rpx;
16 + padding: 32rpx;
17 + margin: 24rpx 0;
18 +}
19 +
20 +.section-title {
21 + font-size: 32rpx;
22 + font-weight: 600;
23 + color: #111827;
24 + margin-bottom: 32rpx;
25 + display: block;
26 +}
27 +
28 +.upload-grid {
29 + display: grid;
30 + grid-template-columns: repeat(2, 1fr);
31 + gap: 32rpx;
32 + margin-top: 24rpx;
33 +}
34 +
35 +.upload-item {
36 + display: flex;
37 + flex-direction: column;
38 + align-items: center;
39 + gap: 16rpx;
40 +}
41 +
42 +.upload-button {
43 + width: 160rpx;
44 + height: 160rpx;
45 + border: 2rpx dashed #d1d5db;
46 + border-radius: 16rpx;
47 + display: flex;
48 + align-items: center;
49 + justify-content: center;
50 + background-color: #f9fafb;
51 + transition: all 0.3s ease;
52 +}
53 +
54 +.upload-button:active {
55 + background-color: #f3f4f6;
56 + border-color: #f97316;
57 +}
58 +
59 +.upload-icon {
60 + font-size: 48rpx;
61 + color: #9ca3af;
62 +}
63 +
64 +.upload-label {
65 + font-size: 24rpx;
66 + color: #6b7280;
67 + text-align: center;
68 +}
69 +
70 +.image-preview {
71 + position: relative;
72 + width: 160rpx;
73 + height: 160rpx;
74 + border-radius: 16rpx;
75 + overflow: hidden;
76 +}
77 +
78 +.preview-image {
79 + width: 100%;
80 + height: 100%;
81 + object-fit: cover;
82 +}
83 +
84 +.delete-btn {
85 + position: absolute;
86 + top: 8rpx;
87 + right: 8rpx;
88 + width: 32rpx;
89 + height: 32rpx;
90 + background-color: #ef4444;
91 + border-radius: 50%;
92 + display: flex;
93 + align-items: center;
94 + justify-content: center;
95 + color: white;
96 + font-size: 16rpx;
97 + z-index: 10;
98 + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
99 +}
100 +
101 +.delete-icon {
102 + font-size: 16rpx;
103 + color: white;
104 +}
105 +
106 +.form-item {
107 + display: flex;
108 + justify-content: space-between;
109 + align-items: center;
110 + padding: 32rpx 0;
111 + border-bottom: 1rpx solid #f3f4f6;
112 +}
113 +
114 +.form-item:last-child {
115 + border-bottom: none;
116 +}
117 +
118 +.form-item-left {
119 + display: flex;
120 + align-items: center;
121 +}
122 +
123 +.form-icon {
124 + margin-right: 16rpx;
125 + color: #9ca3af;
126 +}
127 +
128 +.form-label {
129 + font-size: 28rpx;
130 + color: #374151;
131 +}
132 +
133 +.form-item-right {
134 + display: flex;
135 + align-items: center;
136 +}
137 +
138 +.form-value {
139 + font-size: 28rpx;
140 + color: #9ca3af;
141 + margin-right: 16rpx;
142 +}
143 +
144 +.arrow-icon {
145 + color: #9ca3af;
146 +}
147 +
148 +.form-item-content {
149 + display: flex;
150 + justify-content: space-between;
151 + align-items: center;
152 + width: 100%;
153 + padding: 24rpx 0;
154 +}
155 +
156 +.unit {
157 + font-size: 28rpx;
158 + color: #9ca3af;
159 + margin-left: 16rpx;
160 +}
161 +
162 +.price-symbol {
163 + font-size: 32rpx;
164 + color: #f97316;
165 + font-weight: 600;
166 + margin-right: 8rpx;
167 +}
168 +
169 +.price-input {
170 + font-size: 32rpx;
171 + color: #f97316;
172 + font-weight: 600;
173 + text-align: right;
174 + border: none;
175 + outline: none;
176 + background: transparent;
177 + width: 160rpx;
178 +}
179 +
180 +.market-price-symbol {
181 + font-size: 28rpx;
182 + color: #9ca3af;
183 + margin-right: 8rpx;
184 +}
185 +
186 +.market-price-input {
187 + font-size: 28rpx;
188 + color: #9ca3af;
189 + text-align: right;
190 + border: none;
191 + outline: none;
192 + background: transparent;
193 + width: 160rpx;
194 +}
195 +
196 +.bottom-actions {
197 + position: fixed;
198 + bottom: 0;
199 + left: 0;
200 + right: 0;
201 + background-color: #ffffff;
202 + padding: 24rpx 32rpx;
203 + border-top: 1rpx solid #f3f4f6;
204 + z-index: 100;
205 +}
206 +
207 +// NutUI组件样式覆盖
208 +:deep(.nut-form-item) {
209 + padding: 0;
210 + margin-bottom: 0;
211 + border-bottom: 1rpx solid #f3f4f6;
212 +}
213 +
214 +// :deep(.nut-form-item:last-child) {
215 +// border-bottom: none;
216 +// }
217 +
218 +:deep(.nut-form-item__body) {
219 + padding: 32rpx 0;
220 +}
221 +
222 +:deep(.nut-form-item__label) {
223 + font-size: 28rpx;
224 + color: #374151;
225 + margin-right: 32rpx;
226 +}
227 +
228 +:deep(.nut-input) {
229 + text-align: right;
230 +}
231 +
232 +:deep(.nut-input__input) {
233 + font-size: 28rpx;
234 + color: #374151;
235 + text-align: right;
236 +}
237 +
238 +:deep(.nut-textarea) {
239 + border: 1rpx solid #e5e7eb;
240 + border-radius: 16rpx;
241 + padding: 24rpx;
242 +}
243 +
244 +:deep(.nut-textarea__textarea) {
245 + font-size: 28rpx;
246 + color: #374151;
247 + line-height: 1.5;
248 +}
249 +
250 +:deep(.nut-uploader) {
251 + margin-bottom: 0;
252 +}
253 +
254 +:deep(.nut-uploader__preview) {
255 + margin-bottom: 0;
256 +}
257 +
258 +:deep(.nut-uploader__preview-img) {
259 + width: 160rpx;
260 + height: 160rpx;
261 + border-radius: 16rpx;
262 + object-fit: cover;
263 +}
264 +
265 +:deep(.nut-uploader__upload) {
266 + width: 160rpx;
267 + height: 160rpx;
268 +}
269 +
270 +:deep(.nut-uploader__input) {
271 + width: 100%;
272 + height: 100%;
273 +}
274 +
275 +:deep(.nut-uploader__preview-delete) {
276 + position: absolute;
277 + top: 8rpx;
278 + right: 8rpx;
279 + width: 32rpx;
280 + height: 32rpx;
281 + background-color: rgba(0, 0, 0, 0.6);
282 + border-radius: 50%;
283 + display: flex;
284 + align-items: center;
285 + justify-content: center;
286 + color: #ffffff;
287 + font-size: 20rpx;
288 +}
289 +
290 +:deep(.nut-picker__toolbar) {
291 + padding: 24rpx 32rpx;
292 +}
293 +
294 +:deep(.nut-picker__cancel) {
295 + color: #9ca3af;
296 +}
297 +
298 +:deep(.nut-picker__confirm) {
299 + color: #f97316;
300 +}
301 +
302 +:deep(.nut-picker__title) {
303 + font-size: 32rpx;
304 + font-weight: 600;
305 +}
1 +<!--
2 + * @Date: 2022-09-19 14:11:06
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-07-02 18:23:27
5 + * @FilePath: /jgdl/src/pages/setAuthCar/index.vue
6 + * @Description: 申请认证
7 +-->
8 +<template>
9 + <view class="auth-car-page">
10 + <!-- 表单内容 -->
11 + <view class="form-container">
12 + <!-- 车辆照片上传 -->
13 + <view class="form-section">
14 + <text class="section-title">添加车辆照片</text>
15 + <view class="upload-grid">
16 + <!-- 正面照 -->
17 + <view class="upload-item">
18 + <!-- 上传按钮 -->
19 + <view v-if="!uploadedImages.front" class="upload-button" @click="triggerUpload('front')">
20 + <Plus class="upload-icon" />
21 + </view>
22 + <!-- 图片预览 -->
23 + <view v-else class="image-preview" @click="previewImage(uploadedImages.front)">
24 + <image :src="uploadedImages.front" class="preview-image" mode="aspectFill" />
25 + <view class="delete-btn" @click.stop="deleteImage('front')">
26 + <Close class="delete-icon" />
27 + </view>
28 + </view>
29 + <text class="upload-label">正面照</text>
30 + </view>
31 +
32 + <!-- 左侧照 -->
33 + <view class="upload-item">
34 + <!-- 上传按钮 -->
35 + <view v-if="!uploadedImages.left" class="upload-button" @click="triggerUpload('left')">
36 + <Plus class="upload-icon" />
37 + </view>
38 + <!-- 图片预览 -->
39 + <view v-else class="image-preview" @click="previewImage(uploadedImages.left)">
40 + <image :src="uploadedImages.left" class="preview-image" mode="aspectFill" />
41 + <view class="delete-btn" @click.stop="deleteImage('left')">
42 + <Close class="delete-icon" />
43 + </view>
44 + </view>
45 + <text class="upload-label">左侧照</text>
46 + </view>
47 +
48 + <!-- 右侧照 -->
49 + <view class="upload-item">
50 + <!-- 上传按钮 -->
51 + <view v-if="!uploadedImages.right" class="upload-button" @click="triggerUpload('right')">
52 + <Plus class="upload-icon" />
53 + </view>
54 + <!-- 图片预览 -->
55 + <view v-else class="image-preview" @click="previewImage(uploadedImages.right)">
56 + <image :src="uploadedImages.right" class="preview-image" mode="aspectFill" />
57 + <view class="delete-btn" @click.stop="deleteImage('right')">
58 + <Close class="delete-icon" />
59 + </view>
60 + </view>
61 + <text class="upload-label">右侧照</text>
62 + </view>
63 +
64 + <!-- 其他 -->
65 + <view class="upload-item">
66 + <!-- 上传按钮 -->
67 + <view v-if="!uploadedImages.other" class="upload-button" @click="triggerUpload('other')">
68 + <Plus class="upload-icon" />
69 + </view>
70 + <!-- 图片预览 -->
71 + <view v-else class="image-preview" @click="previewImage(uploadedImages.other)">
72 + <image :src="uploadedImages.other" class="preview-image" mode="aspectFill" />
73 + <view class="delete-btn" @click.stop="deleteImage('other')">
74 + <Close class="delete-icon" />
75 + </view>
76 + </view>
77 + <text class="upload-label">其他</text>
78 + </view>
79 + </view>
80 +
81 + <!-- 图片预览组件 -->
82 + <nut-image-preview v-model:show="previewVisible" :images="previewImages" :init-no="previewIndex" />
83 + </view>
84 +
85 + <!-- 车辆详情表单 -->
86 + <nut-form ref="formRef" :model-value="formData">
87 + <view class="form-section">
88 + <!-- 车型品牌 -->
89 + <nut-form-item label-position="top" label="车型品牌" prop="brand" required :rules="[{ required: true, message: '请选择车型品牌' }]">
90 + <view class="form-item-content" @click="showBrandPicker">
91 + <text class="form-value">{{ formData.brand || '请选择' }}</text>
92 + <Right class="arrow-icon" />
93 + </view>
94 + </nut-form-item>
95 +
96 + <!-- 车辆型号 -->
97 + <nut-form-item label-position="top" label="车辆型号" prop="model" required :rules="[{ required: true, message: '请选择车辆型号' }]">
98 + <view class="form-item-content" @click="showModelPicker">
99 + <text class="form-value">{{ formData.model || '请选择' }}</text>
100 + <Right class="arrow-icon" />
101 + </view>
102 + </nut-form-item>
103 +
104 + <!-- 续航里程 -->
105 + <nut-form-item label="续航里程" prop="range" required :rules="[{ required: true, message: '请输入续航里程' }]">
106 + <nut-input v-model="formData.range" placeholder="60" type="number" input-align="right">
107 + <template #right>
108 + <text class="unit">公里</text>
109 + </template>
110 + </nut-input>
111 + </nut-form-item>
112 +
113 + <!-- 最高时速 -->
114 + <nut-form-item label="最高时速" prop="maxSpeed" required :rules="[{ required: true, message: '请输入最高时速' }]">
115 + <nut-input v-model="formData.maxSpeed" placeholder="25" type="number" input-align="right">
116 + <template #right>
117 + <text class="unit">km/h</text>
118 + </template>
119 + </nut-input>
120 + </nut-form-item>
121 + </view>
122 + </nut-form>
123 +
124 + <!-- 车辆描述 -->
125 + <view class="form-section">
126 + <text class="section-title">车辆描述</text>
127 + <nut-textarea v-model="formData.description" placeholder="请描述车辆详情,如使用感受、车况特点等" :max-length="200" :rows="4" show-word-limit />
128 + </view>
129 + </view>
130 +
131 + <!-- 底部按钮 -->
132 + <view class="bottom-actions">
133 + <nut-button color="#f97316" size="large" block @click="onSubmit">
134 + 提交申请
135 + </nut-button>
136 + </view>
137 +
138 + <!-- 选择器弹窗 -->
139 + <!-- 品牌选择 -->
140 + <nut-popup v-model:visible="brandPickerVisible" position="bottom">
141 + <nut-picker v-model="brandValue" :columns="brandOptions" title="选择车型品牌" @confirm="onBrandConfirm" @cancel="brandPickerVisible = false" />
142 + </nut-popup>
143 +
144 + <!-- 型号选择 -->
145 + <nut-popup v-model:visible="modelPickerVisible" position="bottom">
146 + <nut-picker v-model="modelValue" :columns="modelOptions" title="选择车辆型号" @confirm="onModelConfirm" @cancel="modelPickerVisible = false" />
147 + </nut-popup>
148 + </view>
149 +</template>
150 +
151 +<script setup>
152 +import { ref, reactive } from 'vue'
153 +import { Plus, Right, RectLeft, Close } from '@nutui/icons-vue-taro'
154 +import Taro from '@tarojs/taro'
155 +import './index.less'
156 +
157 +const themeVars = ref({
158 + navbarBackground: '#fb923c',
159 + navbarColor: '#ffffff',
160 +})
161 +
162 +// 已上传图片的URL
163 +const uploadedImages = reactive({
164 + front: '',
165 + left: '',
166 + right: '',
167 + other: ''
168 +})
169 +
170 +// 图片预览相关
171 +const previewVisible = ref(false)
172 +const previewImages = ref([])
173 +const previewIndex = ref(0)
174 +
175 +// 表单数据
176 +const formData = reactive({
177 + brand: '',
178 + model: '',
179 + range: '',
180 + maxSpeed: '',
181 + description: ''
182 +})
183 +
184 +// 选择器显示状态
185 +const brandPickerVisible = ref(false)
186 +const modelPickerVisible = ref(false)
187 +
188 +// 选择器值
189 +const brandValue = ref([])
190 +const modelValue = ref([])
191 +
192 +// 选择器选项数据
193 +const brandOptions = ref([
194 + { text: '小牛电动', value: '小牛电动' },
195 + { text: '雅迪', value: '雅迪' },
196 + { text: '爱玛', value: '爱玛' },
197 + { text: '台铃', value: '台铃' },
198 + { text: '绿源', value: '绿源' },
199 + { text: '新日', value: '新日' },
200 + { text: '立马', value: '立马' },
201 + { text: '其他', value: '其他' }
202 +])
203 +
204 +const modelOptions = ref([
205 + { text: 'NGT', value: 'NGT' },
206 + { text: 'UQi+', value: 'UQi+' },
207 + { text: 'Gova G2', value: 'Gova G2' },
208 + { text: 'MQi+', value: 'MQi+' },
209 + { text: 'NQi GTS', value: 'NQi GTS' },
210 + { text: 'RQi', value: 'RQi' },
211 + { text: '其他型号', value: '其他型号' }
212 +])
213 +
214 +/**
215 + * 返回上一页
216 + */
217 +const goBack = () => {
218 + Taro.navigateBack()
219 +}
220 +
221 +/**
222 + * 触发图片上传
223 + * @param {String} type - 图片类型 (front/left/right/other)
224 + */
225 +const triggerUpload = (type) => {
226 + Taro.chooseImage({
227 + count: 1,
228 + sizeType: ['compressed'],
229 + sourceType: ['album', 'camera'],
230 + success: function (res) {
231 + const tempFilePath = res.tempFilePaths[0]
232 + uploadImage(tempFilePath, type)
233 + },
234 + fail: function () {
235 + Taro.showToast({
236 + title: '选择图片失败',
237 + icon: 'none'
238 + })
239 + }
240 + })
241 +}
242 +
243 +/**
244 + * 上传图片到服务器
245 + * @param {String} filePath - 图片文件路径
246 + * @param {String} type - 图片类型 (front/left/right/other)
247 + */
248 +const uploadImage = (filePath, type) => {
249 + // 显示上传中提示
250 + Taro.showLoading({
251 + title: '上传中',
252 + mask: true
253 + })
254 +
255 + // 模拟上传成功(实际项目中替换为真实的上传逻辑)
256 + setTimeout(() => {
257 + Taro.hideLoading()
258 +
259 + // 模拟服务器返回的图片URL
260 + const mockImageUrl = filePath // 在实际项目中,这里应该是服务器返回的URL
261 +
262 + // 更新对应类型的图片
263 + uploadedImages[type] = mockImageUrl
264 +
265 + Taro.showToast({
266 + title: '上传成功',
267 + icon: 'success'
268 + })
269 + }, 1500)
270 +}
271 +
272 +/**
273 + * 删除图片
274 + * @param {String} type - 图片类型
275 + */
276 +const deleteImage = (type) => {
277 + uploadedImages[type] = ''
278 + Taro.showToast({
279 + title: '删除成功',
280 + icon: 'success'
281 + })
282 +}
283 +
284 +/**
285 + * 预览图片
286 + * @param {String} imageUrl - 图片URL
287 + */
288 +const previewImage = (imageUrl) => {
289 + previewImages.value = [imageUrl]
290 + previewIndex.value = 0
291 + previewVisible.value = true
292 +}
293 +
294 +/**
295 + * 显示品牌选择器
296 + */
297 +const showBrandPicker = () => {
298 + brandPickerVisible.value = true
299 +}
300 +
301 +/**
302 + * 显示型号选择器
303 + */
304 +const showModelPicker = () => {
305 + modelPickerVisible.value = true
306 +}
307 +
308 +/**
309 + * 品牌选择确认
310 + * @param {Object} options - 选择的选项
311 + */
312 +const onBrandConfirm = (options) => {
313 + formData.brand = options.selectedOptions[0]?.text || ''
314 + brandPickerVisible.value = false
315 +}
316 +
317 +/**
318 + * 型号选择确认
319 + * @param {Object} options - 选择的选项
320 + */
321 +const onModelConfirm = (options) => {
322 + formData.model = options.selectedOptions[0]?.text || ''
323 + modelPickerVisible.value = false
324 +}
325 +
326 +/**
327 + * 提交申请
328 + */
329 +const onSubmit = () => {
330 + // 表单验证
331 + if (!formData.brand) {
332 + Taro.showToast({
333 + title: '请选择车型品牌',
334 + icon: 'none'
335 + })
336 + return
337 + }
338 +
339 + if (!formData.model) {
340 + Taro.showToast({
341 + title: '请选择车辆型号',
342 + icon: 'none'
343 + })
344 + return
345 + }
346 +
347 + if (!formData.range) {
348 + Taro.showToast({
349 + title: '请输入续航里程',
350 + icon: 'none'
351 + })
352 + return
353 + }
354 +
355 + if (!formData.maxSpeed) {
356 + Taro.showToast({
357 + title: '请输入最高时速',
358 + icon: 'none'
359 + })
360 + return
361 + }
362 +
363 + // 检查是否至少上传了一张照片
364 + const hasImage = Object.values(uploadedImages).some(img => img)
365 + if (!hasImage) {
366 + Taro.showToast({
367 + title: '请至少上传一张车辆照片',
368 + icon: 'none'
369 + })
370 + return
371 + }
372 +
373 + // 提交数据
374 + const submitData = {
375 + ...formData,
376 + images: uploadedImages
377 + }
378 +
379 + Taro.showLoading({
380 + title: '提交中',
381 + mask: true
382 + })
383 +
384 + // 模拟提交成功
385 + setTimeout(() => {
386 + Taro.hideLoading()
387 + Taro.showToast({
388 + title: '申请提交成功',
389 + icon: 'success'
390 + })
391 +
392 + // 返回上一页
393 + setTimeout(() => {
394 + Taro.navigateBack()
395 + }, 1500)
396 + }, 2000)
397 +
398 + // 实际项目中的API调用
399 + // submitAuthApplication(submitData)
400 +}
401 +</script>
402 +
403 +<script>
404 +export default {
405 + name: "setAuthCarPage",
406 +};
407 +</script>