hookehuyr

feat(productDetail): 新增商品详情页及相关组件

添加商品详情页面,包含图片轮播、基本信息展示、车辆评估、卖家信息等功能模块
引入支付组件payCard,实现订单支付流程
更新app.config.js添加新页面路由
优化多个页面的收藏图标统一使用Heart1组件
新增底部操作栏样式和深色模式适配
...@@ -8,10 +8,12 @@ export {} ...@@ -8,10 +8,12 @@ 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 + NutActionSheet: typeof import('@nutui/nutui-taro')['ActionSheet']
11 NutButton: typeof import('@nutui/nutui-taro')['Button'] 12 NutButton: typeof import('@nutui/nutui-taro')['Button']
12 NutCol: typeof import('@nutui/nutui-taro')['Col'] 13 NutCol: typeof import('@nutui/nutui-taro')['Col']
13 NutConfigProvider: typeof import('@nutui/nutui-taro')['ConfigProvider'] 14 NutConfigProvider: typeof import('@nutui/nutui-taro')['ConfigProvider']
14 NutDatePicker: typeof import('@nutui/nutui-taro')['DatePicker'] 15 NutDatePicker: typeof import('@nutui/nutui-taro')['DatePicker']
16 + NutDialog: typeof import('@nutui/nutui-taro')['Dialog']
15 NutForm: typeof import('@nutui/nutui-taro')['Form'] 17 NutForm: typeof import('@nutui/nutui-taro')['Form']
16 NutFormItem: typeof import('@nutui/nutui-taro')['FormItem'] 18 NutFormItem: typeof import('@nutui/nutui-taro')['FormItem']
17 NutImagePreview: typeof import('@nutui/nutui-taro')['ImagePreview'] 19 NutImagePreview: typeof import('@nutui/nutui-taro')['ImagePreview']
...@@ -32,6 +34,7 @@ declare module 'vue' { ...@@ -32,6 +34,7 @@ declare module 'vue' {
32 NutTabs: typeof import('@nutui/nutui-taro')['Tabs'] 34 NutTabs: typeof import('@nutui/nutui-taro')['Tabs']
33 NutTextarea: typeof import('@nutui/nutui-taro')['Textarea'] 35 NutTextarea: typeof import('@nutui/nutui-taro')['Textarea']
34 NutToast: typeof import('@nutui/nutui-taro')['Toast'] 36 NutToast: typeof import('@nutui/nutui-taro')['Toast']
37 + PayCard: typeof import('./src/components/payCard.vue')['default']
35 Picker: typeof import('./src/components/time-picker-data/picker.vue')['default'] 38 Picker: typeof import('./src/components/time-picker-data/picker.vue')['default']
36 PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default'] 39 PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default']
37 RouterLink: typeof import('vue-router')['RouterLink'] 40 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-07-02 22:18:10 4 + * @LastEditTime: 2025-07-03 09:34:59
5 * @FilePath: /jgdl/src/app.config.js 5 * @FilePath: /jgdl/src/app.config.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
...@@ -18,6 +18,7 @@ export default { ...@@ -18,6 +18,7 @@ export default {
18 'pages/setAuthCar/index', 18 'pages/setAuthCar/index',
19 'pages/newCarList/index', 19 'pages/newCarList/index',
20 'pages/goodCarList/index', 20 'pages/goodCarList/index',
21 + 'pages/productDetail/index',
21 'pages/auth/index', 22 'pages/auth/index',
22 ], 23 ],
23 subpackages: [ // 配置在tabBar中的页面不能分包写到subpackages中去 24 subpackages: [ // 配置在tabBar中的页面不能分包写到subpackages中去
......
...@@ -13,3 +13,28 @@ ...@@ -13,3 +13,28 @@
13 font-style: normal !important; 13 font-style: normal !important;
14 font-weight: normal !important; 14 font-weight: normal !important;
15 } 15 }
16 +
17 +button {
18 + margin: 0;
19 + padding: 0;
20 + background-color: inherit;
21 + position: static;
22 +}
23 +
24 +button:after {
25 + content: none;
26 +}
27 +button::after {
28 + border: none;
29 +}
30 +
31 +.bottom-actions {
32 + position: fixed;
33 + bottom: 0;
34 + left: 0;
35 + right: 0;
36 + background-color: #ffffff;
37 + padding: 24rpx 32rpx;
38 + border-top: 1rpx solid #f3f4f6;
39 + z-index: 100;
40 +}
......
1 +<!--
2 + * @Date: 2023-12-20 14:11:11
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-07-03 11:43:29
5 + * @FilePath: /jgdl/src/components/payCard.vue
6 + * @Description: 文件描述
7 +-->
8 +<template>
9 + <div class="pay-card">
10 + <nut-action-sheet v-model:visible="props.visible" title="" @close="onClose">
11 + <view style="padding: 2rem 1rem; text-align: center;">
12 + <view style="font-size: 32rpx;">实付金额</view>
13 + <view style="color: red; margin: 10rpx 0;"><text style="font-size: 50rpx;">¥</text><text style="font-size: 80rpx;">{{ price }}</text></view>
14 + <view style="font-size: 28rpx; margin-bottom: 20rpx;">支付剩余时间 <text style="color: red;">{{ formatTime(remain_time) }}</text></view>
15 + <nut-button block color="#fb923c" @tap="goToPay">立即支付</nut-button>
16 + </view>
17 + </nut-action-sheet>
18 + </div>
19 +</template>
20 +
21 +<script setup>
22 +import Taro from '@tarojs/taro'
23 +import { ref, watch, onMounted, onUnmounted } from 'vue'
24 +import { getCurrentPageUrl } from "@/utils/weapp";
25 +import { payAPI, payCheckAPI, orderSuccessAPI } from '@/api/index'
26 +
27 +/**
28 + * 格式化时间
29 + * @param {*} seconds
30 + */
31 +function formatTime(seconds) {
32 + const hours = Math.floor(seconds / 3600); // 计算小时数
33 + const minutes = Math.floor((seconds % 3600) / 60); // 计算分钟数
34 + const remainingSeconds = seconds % 60; // 计算剩余的秒数
35 +
36 + const formattedHours = String(hours).padStart(2, "0"); // 格式化小时数,保证两位数
37 + const formattedMinutes = String(minutes).padStart(2, "0"); // 格式化分钟数,保证两位数
38 + const formattedSeconds = String(remainingSeconds).padStart(2, "0"); // 格式化剩余的秒数,保证两位数
39 +
40 + return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
41 +}
42 +
43 +const props = defineProps({
44 + visible: {
45 + type: Boolean,
46 + default: false,
47 + },
48 + data: {
49 + type: Object,
50 + default: {},
51 + },
52 +});
53 +
54 +const emit = defineEmits(['close']);
55 +
56 +const onClose = () => {
57 + emit('close');
58 +}
59 +
60 +const id = ref('');
61 +const price = ref('');
62 +const remain_time = ref('');
63 +
64 +let timeId = null;
65 +
66 +watch(
67 + () => props.visible,
68 + (val) => {
69 + if (val) {
70 + id.value = props.data.id;
71 + price.value = props.data.price;
72 + remain_time.value = props.data.remain_time;
73 + }
74 + }
75 +)
76 +
77 +onMounted(() => {
78 + // 进入页面后,开始倒计时
79 + timeId = setInterval(() => {
80 + remain_time.value ? remain_time.value -= 1 : 0;
81 + if (remain_time.value === 0) { // 倒计时结束
82 + clearInterval(timeId);
83 + emit('close');
84 + }
85 + }, 1000);
86 +})
87 +
88 +onUnmounted(() => {
89 + timeId && clearInterval(timeId);
90 +})
91 +
92 +const goToPay = async () => {
93 + if (price.value > 0) { // 金额大于0
94 + // 获取支付参数
95 + const { code, data } = await payAPI({ order_id: id.value });
96 + if (code) {
97 + let pay = data;
98 + // 触发微信支付操作
99 + wx.requestPayment({
100 + timeStamp: pay.timeStamp,
101 + nonceStr: pay.nonceStr,
102 + package: pay.package,
103 + signType: pay.signType,
104 + paySign: pay.paySign,
105 + success: async (result) => {
106 + emit('close'); // 关闭支付弹框
107 + Taro.showToast({
108 + title: '支付成功',
109 + icon: 'success',
110 + duration: 1000
111 + });
112 + // 支付成功后,调用检查接口
113 + const pay_success = await payCheckAPI({ order_id: id.value });
114 + if (pay_success.code) {
115 + let current_page = getCurrentPageUrl();
116 + if (current_page === 'pages/my/index') { // 我的页面打开
117 + // 刷新当前页面
118 + Taro.reLaunch({
119 + url: '/pages/my/index?tab_index=5'
120 + });
121 + }
122 + if (current_page === 'pages/detail/index') { // 订房确认页打开
123 + // 跳转订单成功页
124 + Taro.navigateTo({
125 + url: '/pages/payInfo/index',
126 + });
127 + }
128 + }
129 + }
130 + });
131 + }
132 + }
133 +}
134 +</script>
135 +
136 +<style lang="less">
137 +
138 +</style>
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 18:15:19 4 + * @LastEditTime: 2025-07-03 12:40:46
5 * @FilePath: /jgdl/src/pages/authCar/index.vue 5 * @FilePath: /jgdl/src/pages/authCar/index.vue
6 * @Description: 认证车源 6 * @Description: 认证车源
7 --> 7 -->
...@@ -61,8 +61,8 @@ ...@@ -61,8 +61,8 @@
61 </view> 61 </view>
62 <view class="flex-1 p-3 relative"> 62 <view class="flex-1 p-3 relative">
63 <view class="absolute top-3 right-4" @tap.stop="() => toggleFavorite(car.id)"> 63 <view class="absolute top-3 right-4" @tap.stop="() => toggleFavorite(car.id)">
64 - <Addfollow v-if="!favoriteIds.includes(car.id)" size="16" color="#9ca3af" /> 64 + <Heart1 v-if="!favoriteIds.includes(car.id)" size="16" :color="'#9ca3af'" />
65 - <HeartFill v-else size="16" color="#ef4444" /> 65 + <HeartFill v-else size="16" :color="'#ef4444'" />
66 </view> 66 </view>
67 <text class="font-medium text-sm block">{{ car.name }}</text> 67 <text class="font-medium text-sm block">{{ car.name }}</text>
68 <text class="text-xs text-gray-600 mt-1 block"> 68 <text class="text-xs text-gray-600 mt-1 block">
...@@ -109,7 +109,7 @@ ...@@ -109,7 +109,7 @@
109 <script setup> 109 <script setup>
110 import Taro from '@tarojs/taro' 110 import Taro from '@tarojs/taro'
111 import { ref, computed, onMounted } from 'vue' 111 import { ref, computed, onMounted } from 'vue'
112 -import { Check, RectRight, Addfollow, HeartFill } from '@nutui/icons-vue-taro' 112 +import { Check, Addfollow, Follow, Heart1, HeartFill } from '@nutui/icons-vue-taro'
113 import './index.less' 113 import './index.less'
114 114
115 // Banner图片数据 115 // Banner图片数据
......
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
68 </view> 68 </view>
69 <view class="flex-1 p-3 relative"> 69 <view class="flex-1 p-3 relative">
70 <view class="absolute top-3 right-4" @tap.stop="() => toggleFavorite(car.id)"> 70 <view class="absolute top-3 right-4" @tap.stop="() => toggleFavorite(car.id)">
71 - <Addfollow v-if="!favoriteIds.includes(car.id)" size="16" color="#9ca3af" /> 71 + <Heart1 v-if="!favoriteIds.includes(car.id)" size="16" color="#9ca3af" />
72 <HeartFill v-else size="16" color="#ef4444" /> 72 <HeartFill v-else size="16" color="#ef4444" />
73 </view> 73 </view>
74 <text class="font-medium text-sm block">{{ car.name }}</text> 74 <text class="font-medium text-sm block">{{ car.name }}</text>
...@@ -125,7 +125,7 @@ ...@@ -125,7 +125,7 @@
125 125
126 <script setup> 126 <script setup>
127 import { ref, computed, onMounted } from 'vue' 127 import { ref, computed, onMounted } from 'vue'
128 -import { Search2, Addfollow, HeartFill } from '@nutui/icons-vue-taro' 128 +import { Search2, Addfollow, Follow, Heart1, HeartFill } from '@nutui/icons-vue-taro'
129 import TabBar from '@/components/TabBar.vue' 129 import TabBar from '@/components/TabBar.vue'
130 import './index.less' 130 import './index.less'
131 131
......
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 22:58:32 4 + * @LastEditTime: 2025-07-03 12:43:02
5 * @FilePath: /jgdl/src/pages/index/index.vue 5 * @FilePath: /jgdl/src/pages/index/index.vue
6 * @Description: 捡个电驴首页 6 * @Description: 捡个电驴首页
7 --> 7 -->
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
77 <image :src="scooter.imageUrl" :alt="scooter.name" mode="aspectFill" 77 <image :src="scooter.imageUrl" :alt="scooter.name" mode="aspectFill"
78 class="w-full h-36 object-cover rounded-lg" /> 78 class="w-full h-36 object-cover rounded-lg" />
79 <view class="absolute top-4 right-4 p-1" @tap.stop="() => toggleFavorite(scooter.id)"> 79 <view class="absolute top-4 right-4 p-1" @tap.stop="() => toggleFavorite(scooter.id)">
80 - <Addfollow v-if="!favoriteIds.includes(scooter.id)" size="20" :color="'#ffffff'" /> 80 + <Heart1 v-if="!favoriteIds.includes(scooter.id)" size="20" :color="'#ffffff'" />
81 <HeartFill v-else size="20" :color="'#ef4444'" /> 81 <HeartFill v-else size="20" :color="'#ef4444'" />
82 </view> 82 </view>
83 <view v-if="scooter.isVerified" 83 <view v-if="scooter.isVerified"
...@@ -131,7 +131,7 @@ ...@@ -131,7 +131,7 @@
131 </view> 131 </view>
132 <view class="flex-1 p-3 relative"> 132 <view class="flex-1 p-3 relative">
133 <view class="absolute top-2 right-2" @tap.stop="() => toggleFavorite(scooter.id)"> 133 <view class="absolute top-2 right-2" @tap.stop="() => toggleFavorite(scooter.id)">
134 - <Addfollow v-if="!favoriteIds.includes(scooter.id)" size="16" :color="'#ffffff'" /> 134 + <Heart1 v-if="!favoriteIds.includes(scooter.id)" size="16" :color="'#ffffff'" />
135 <HeartFill v-else size="16" :color="'#ef4444'" /> 135 <HeartFill v-else size="16" :color="'#ef4444'" />
136 </view> 136 </view>
137 <text class="font-medium text-sm block">{{ scooter.name }}</text> 137 <text class="font-medium text-sm block">{{ scooter.name }}</text>
...@@ -162,7 +162,7 @@ import Taro from '@tarojs/taro' ...@@ -162,7 +162,7 @@ import Taro from '@tarojs/taro'
162 import '@tarojs/taro/html5.css' //和 nutui组件居然有冲突? 162 import '@tarojs/taro/html5.css' //和 nutui组件居然有冲突?
163 import { ref, onMounted } from 'vue' 163 import { ref, onMounted } from 'vue'
164 import { useDidShow, useReady } from '@tarojs/taro' 164 import { useDidShow, useReady } from '@tarojs/taro'
165 -import { Clock, Star, RectRight, Addfollow, HeartFill, Check, Search2, Shop } from '@nutui/icons-vue-taro' 165 +import { Clock, Star, RectRight, Addfollow, Follow, Check, Search2, Shop, Heart1, HeartFill } from '@nutui/icons-vue-taro'
166 import TabBar from '@/components/TabBar.vue' 166 import TabBar from '@/components/TabBar.vue'
167 import "./index.less"; 167 import "./index.less";
168 168
...@@ -280,9 +280,8 @@ const toggleFavorite = (scooterId) => { ...@@ -280,9 +280,8 @@ const toggleFavorite = (scooterId) => {
280 * @param {Object} scooter - 电动车信息 280 * @param {Object} scooter - 电动车信息
281 */ 281 */
282 const onProductClick = (scooter) => { 282 const onProductClick = (scooter) => {
283 - Taro.showToast({ 283 + Taro.navigateTo({
284 - title: `查看${scooter.name}`, 284 + url: `/pages/productDetail/index?id=${scooter.id}`
285 - icon: 'none'
286 }) 285 })
287 } 286 }
288 287
......
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
63 </view> 63 </view>
64 <view class="flex-1 p-3 relative"> 64 <view class="flex-1 p-3 relative">
65 <view class="absolute top-3 right-4" @tap.stop="() => toggleFavorite(car.id)"> 65 <view class="absolute top-3 right-4" @tap.stop="() => toggleFavorite(car.id)">
66 - <Addfollow v-if="!favoriteIds.includes(car.id)" size="16" color="#9ca3af" /> 66 + <Heart1 v-if="!favoriteIds.includes(car.id)" size="16" color="#9ca3af" />
67 <HeartFill v-else size="16" color="#ef4444" /> 67 <HeartFill v-else size="16" color="#ef4444" />
68 </view> 68 </view>
69 <text class="font-medium text-sm block">{{ car.name }}</text> 69 <text class="font-medium text-sm block">{{ car.name }}</text>
...@@ -114,7 +114,7 @@ ...@@ -114,7 +114,7 @@
114 114
115 <script setup> 115 <script setup>
116 import { ref, computed, onMounted } from 'vue' 116 import { ref, computed, onMounted } from 'vue'
117 -import { Search2, Addfollow, HeartFill } from '@nutui/icons-vue-taro' 117 +import { Search2, Heart1, HeartFill } from '@nutui/icons-vue-taro'
118 import TabBar from '@/components/TabBar.vue' 118 import TabBar from '@/components/TabBar.vue'
119 import './index.less' 119 import './index.less'
120 120
...@@ -234,12 +234,12 @@ const scrollStyle = computed(() => { ...@@ -234,12 +234,12 @@ const scrollStyle = computed(() => {
234 * @param {string} carId - 车辆ID 234 * @param {string} carId - 车辆ID
235 */ 235 */
236 const toggleFavorite = (carId) => { 236 const toggleFavorite = (carId) => {
237 - const index = favoriteIds.value.indexOf(carId.toString()) 237 + const index = favoriteIds.value.indexOf(carId)
238 if (index > -1) { 238 if (index > -1) {
239 favoriteIds.value.splice(index, 1) 239 favoriteIds.value.splice(index, 1)
240 showToast('取消收藏', 'success') 240 showToast('取消收藏', 'success')
241 } else { 241 } else {
242 - favoriteIds.value.push(carId.toString()) 242 + favoriteIds.value.push(carId)
243 showToast('收藏成功', 'success') 243 showToast('收藏成功', 'success')
244 } 244 }
245 } 245 }
......
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
45 </view> 45 </view>
46 <view class="flex-1 p-3 relative"> 46 <view class="flex-1 p-3 relative">
47 <view class="absolute top-3 right-4" @tap.stop="() => toggleFavorite(scooter.id)"> 47 <view class="absolute top-3 right-4" @tap.stop="() => toggleFavorite(scooter.id)">
48 - <Addfollow v-if="!favoriteIds.includes(scooter.id)" size="16" color="#9ca3af" /> 48 + <Heart1 v-if="!favoriteIds.includes(scooter.id)" size="16" color="#9ca3af" />
49 <HeartFill v-else size="16" color="#ef4444" /> 49 <HeartFill v-else size="16" color="#ef4444" />
50 </view> 50 </view>
51 <text class="font-medium text-sm block">{{ scooter.name }}</text> 51 <text class="font-medium text-sm block">{{ scooter.name }}</text>
...@@ -96,7 +96,7 @@ ...@@ -96,7 +96,7 @@
96 <image :src="scooter.imageUrl" :alt="scooter.name" mode="aspectFill" 96 <image :src="scooter.imageUrl" :alt="scooter.name" mode="aspectFill"
97 class="w-full h-36 object-cover rounded-lg" /> 97 class="w-full h-36 object-cover rounded-lg" />
98 <view class="absolute top-4 right-4 p-1" @tap.stop="() => toggleFavorite(scooter.id)"> 98 <view class="absolute top-4 right-4 p-1" @tap.stop="() => toggleFavorite(scooter.id)">
99 - <Addfollow v-if="!favoriteIds.includes(scooter.id)" size="20" color="#ffffff" /> 99 + <Heart1 v-if="!favoriteIds.includes(scooter.id)" size="20" color="#ffffff" />
100 <HeartFill v-else size="20" color="#ef4444" /> 100 <HeartFill v-else size="20" color="#ef4444" />
101 </view> 101 </view>
102 <view v-if="scooter.isVerified" 102 <view v-if="scooter.isVerified"
...@@ -132,7 +132,7 @@ ...@@ -132,7 +132,7 @@
132 <script setup> 132 <script setup>
133 import { ref } from 'vue' 133 import { ref } from 'vue'
134 import Taro from '@tarojs/taro' 134 import Taro from '@tarojs/taro'
135 -import { Search2, RectRight, Check, Addfollow, HeartFill } from '@nutui/icons-vue-taro' 135 +import { Search2, RectRight, Check, Heart1, HeartFill } from '@nutui/icons-vue-taro'
136 import TabBar from '@/components/TabBar.vue' 136 import TabBar from '@/components/TabBar.vue'
137 137
138 // 响应式数据 138 // 响应式数据
......
1 +/*
2 + * @Date: 2025-07-03 09:34:12
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-07-03 09:34:43
5 + * @FilePath: /jgdl/src/pages/productDetail/index.config.js
6 + * @Description: 文件描述
7 + */
8 +export default {
9 + navigationBarTitleText: '商品详情',
10 + usingComponents: {
11 + },
12 +}
1 +// 商品详情页样式
2 +.product-detail-page {
3 + background-color: #f5f5f5;
4 + min-height: 100vh;
5 + padding-bottom: 120rpx;
6 +}
7 +
8 +// 图片轮播样式
9 +.image-carousel {
10 + .nut-swiper {
11 + .nut-swiper-pagination {
12 + bottom: 20rpx;
13 + }
14 + }
15 +}
16 +
17 +// 商品信息区域
18 +.product-info {
19 + .flex {
20 + display: flex;
21 + }
22 +
23 + .items-center {
24 + align-items: center;
25 + }
26 +
27 + .justify-between {
28 + justify-content: space-between;
29 + }
30 +
31 + .flex-col {
32 + flex-direction: column;
33 + }
34 +
35 + .space-x-4 > view:not(:first-child) {
36 + margin-left: 1rem;
37 + }
38 +
39 + .bg-gray-50 {
40 + background-color: #f9fafb;
41 + }
42 +
43 + .rounded-full {
44 + border-radius: 50%;
45 + }
46 +
47 + .p-2 {
48 + padding: 0.5rem;
49 + }
50 +
51 + .mt-1 {
52 + margin-top: 0.25rem;
53 + }
54 +
55 + .text-xs {
56 + font-size: 0.75rem;
57 + line-height: 1rem;
58 + }
59 +
60 + .text-gray-500 {
61 + color: #6b7280;
62 + }
63 +}
64 +
65 +// 基本信息网格布局
66 +.grid {
67 + display: grid;
68 +}
69 +
70 +.grid-cols-2 {
71 + grid-template-columns: repeat(2, 1fr);
72 +}
73 +
74 +.gap-4 {
75 + gap: 1rem;
76 +}
77 +
78 +// 间距工具类
79 +.space-x-3 > view:not(:first-child) {
80 + margin-left: 0.75rem;
81 +}
82 +
83 +.space-x-4 > view:not(:first-child) {
84 + margin-left: 1rem;
85 +}
86 +
87 +.space-y-3 > view:not(:first-child) {
88 + margin-top: 0.75rem;
89 +}
90 +
91 +// 富文本内容样式
92 +.rich-content {
93 + line-height: 1.6;
94 +
95 + p {
96 + margin-bottom: 12rpx;
97 + }
98 +
99 + ul {
100 + margin: 12rpx 0;
101 + padding-left: 20rpx;
102 +
103 + li {
104 + margin-bottom: 6rpx;
105 + }
106 + }
107 +
108 + strong {
109 + font-weight: bold;
110 + }
111 +
112 + span {
113 + display: inline;
114 + }
115 +}
116 +
117 +// 联系卖家弹框样式
118 +.contact-modal {
119 + .seller-card {
120 + background-color: #f9fafb;
121 + }
122 +
123 + .contact-options {
124 + .nut-button {
125 + margin-bottom: 12rpx;
126 +
127 + &:last-child {
128 + margin-bottom: 0;
129 + }
130 + }
131 + }
132 +}
133 +
134 +// 微信弹框样式
135 +.nut-dialog {
136 + .nut-dialog-content {
137 + .text-center {
138 + text-align: center;
139 + }
140 +
141 + .p-4 {
142 + padding: 1rem;
143 + }
144 +
145 + .mb-2 {
146 + margin-bottom: 0.5rem;
147 + }
148 +
149 + .mb-4 {
150 + margin-bottom: 1rem;
151 + }
152 +
153 + .text-lg {
154 + font-size: 1.125rem;
155 + line-height: 1.75rem;
156 + }
157 +
158 + .font-medium {
159 + font-weight: 500;
160 + }
161 +
162 + .text-sm {
163 + font-size: 0.875rem;
164 + line-height: 1.25rem;
165 + }
166 +
167 + .block {
168 + display: block;
169 + }
170 + }
171 +}
172 +
173 +// 底部按钮区域
174 +.fixed {
175 + position: fixed;
176 +}
177 +
178 +.bottom-0 {
179 + bottom: 0;
180 +}
181 +
182 +.left-0 {
183 + left: 0;
184 +}
185 +
186 +.right-0 {
187 + right: 0;
188 +}
189 +
190 +.bg-white {
191 + background-color: #ffffff;
192 +}
193 +
194 +.border-t {
195 + border-top-width: 1px;
196 +}
197 +
198 +.border-gray-200 {
199 + border-color: #e5e7eb;
200 +}
201 +
202 +.p-3 {
203 + padding: 0.75rem;
204 +}
205 +
206 +.flex-1 {
207 + flex: 1 1 0%;
208 +}
209 +
210 +// 响应式适配
211 +@media (max-width: 768rpx) {
212 + .product-detail-page {
213 + padding-bottom: 140rpx;
214 + }
215 +
216 + .product-info {
217 + .space-x-4 > view:not(:first-child) {
218 + margin-left: 0.5rem;
219 + }
220 +
221 + .text-xs {
222 + font-size: 0.7rem;
223 + }
224 + }
225 +
226 + .grid-cols-2 {
227 + grid-template-columns: 1fr;
228 + gap: 0.5rem;
229 + }
230 +}
231 +
232 +// 深色模式适配
233 +@media (prefers-color-scheme: dark) {
234 + .product-detail-page {
235 + background-color: #1f2937;
236 + color: #f9fafb;
237 + }
238 +
239 + .bg-white {
240 + background-color: #374151;
241 + }
242 +
243 + .text-gray-500 {
244 + color: #9ca3af;
245 + }
246 +
247 + .border-gray-200 {
248 + border-color: #4b5563;
249 + }
250 +}
251 +
252 +// 动画效果
253 +@keyframes fadeInUp {
254 + from {
255 + opacity: 0;
256 + transform: translateY(30rpx);
257 + }
258 + to {
259 + opacity: 1;
260 + transform: translateY(0);
261 + }
262 +}
263 +
264 +.product-info,
265 +.basic-info,
266 +.vehicle-condition,
267 +.vehicle-description,
268 +.seller-info {
269 + animation: fadeInUp 0.6s ease-out;
270 +}
271 +
272 +// 卡片悬停效果
273 +.product-info,
274 +.basic-info,
275 +.vehicle-condition,
276 +.vehicle-description,
277 +.seller-info {
278 + transition: all 0.3s ease;
279 +
280 + &:hover {
281 + transform: translateY(-2rpx);
282 + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
283 + }
284 +}
285 +
286 +// 按钮样式增强
287 +.nut-button {
288 + transition: all 0.3s ease;
289 +
290 + &:active {
291 + transform: scale(0.98);
292 + }
293 +}
294 +
295 +// 图标按钮样式
296 +.product-info {
297 + .bg-gray-50 {
298 + transition: all 0.3s ease;
299 +
300 + &:active {
301 + background-color: #e5e7eb;
302 + transform: scale(0.95);
303 + }
304 + }
305 +}
This diff is collapsed. Click to expand it.