爱简收旧衣按件回收前端代码仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2816 lines
80 KiB

3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
2 months ago
2 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
2 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
  1. <template>
  2. <view class="container">
  3. <!-- 顶部banner -->
  4. <view class="banner">
  5. <swiper
  6. :indicator-dots="false"
  7. :autoplay="true"
  8. :interval="3000"
  9. :duration="500"
  10. circular
  11. style="width: 100%; height: 320rpx;"
  12. >
  13. <swiper-item v-for="(item, index) in bannerList" :key="item.id || index">
  14. <view v-if="item.type == 1" class="video-container">
  15. <!-- 预览状态显示封面图 -->
  16. <image
  17. v-if="!videoPlayingStates[index]"
  18. :src="item.image || ''"
  19. mode="aspectFill"
  20. style="width: 100%; height: 100%;"
  21. class="video-poster"
  22. @click="playVideoFullscreen(item, index)"
  23. />
  24. <!-- 播放状态显示视频 -->
  25. <video
  26. v-else
  27. :id="`recycle-video-${index}`"
  28. :src="item.voUrl"
  29. :autoplay="true"
  30. :muted="false"
  31. :loop="false"
  32. :controls="true"
  33. :show-play-btn="true"
  34. :show-center-play-btn="false"
  35. :show-fullscreen-btn="true"
  36. :show-progress="true"
  37. :show-mute-btn="true"
  38. :enable-progress-gesture="true"
  39. :enable-play-gesture="true"
  40. object-fit="cover"
  41. style="width: 100%; height: 100%;"
  42. @fullscreenchange="onFullscreenChange"
  43. @play="onVideoPlay(index)"
  44. @pause="onVideoPause(index)"
  45. @ended="onVideoEnded(index)"
  46. ></video>
  47. <!-- 播放按钮覆盖层 -->
  48. <view v-if="!videoPlayingStates[index]" class="video-overlay" @click="playVideoFullscreen(item, index)">
  49. <view class="play-button-large">
  50. <view class="play-triangle"></view>
  51. </view>
  52. </view>
  53. </view>
  54. <image v-else :src="item.image" mode="aspectFill" style="width: 100%; height: 100%;" />
  55. </swiper-item>
  56. </swiper>
  57. <!-- 备用静态图片当没有轮播图数据时显示 -->
  58. <image v-if="!bannerList || bannerList.length === 0" :src="recycle_banner" mode="aspectFill" style="width: 100%; height: 100%;" />
  59. </view>
  60. <!-- 商品列表 -->
  61. <view class="goods-list">
  62. <!-- 左侧分类导航 -->
  63. <view class="category-nav">
  64. <view
  65. v-for="(category, index) in categories"
  66. :key="category.id || index"
  67. class="category-item"
  68. :class="{ active: currentCategory === index }"
  69. @click="switchCategory(index)"
  70. >
  71. <view class="category-dot" v-if="getCategoryItemCount(index) > 0">{{ getCategoryItemCount(index) }}</view>
  72. {{ category.title }}
  73. </view>
  74. </view>
  75. <!-- 右侧商品列表 -->
  76. <scroll-view class="goods-content" scroll-y @scrolltolower="loadMoreGoods">
  77. <view class="goods-section">
  78. <view class="goods-item" v-for="(item, index) in recycleList" :key="index">
  79. <image v-if="item.image" :src="item.image" class="goods-item-img" mode="aspectFit" />
  80. <view class="goods-info-wrap">
  81. <view class="goods-header">
  82. <text class="goods-name">{{item.name}}</text>
  83. </view>
  84. <text class="goods-desc">{{item.service}}</text>
  85. <view class="rules-brand-row">
  86. <view class="rules-link" @click="showRules(item)">
  87. <view class="rules">
  88. <text>回收规则</text>
  89. <uni-icons type="right" size="14" color="#999"></uni-icons>
  90. </view>
  91. </view>
  92. <view class="brand-check-placeholder" v-if="item.isPin === 'Y'">
  93. <view class="brand-check" @click="checkBrand(index)">
  94. <text>查看品牌</text>
  95. <uni-icons type="right" size="12" color="#ff7a0e"></uni-icons>
  96. </view>
  97. </view>
  98. </view>
  99. <view class="goods-info">
  100. <view class="price-info">
  101. <text class="price-symbol">¥</text>
  102. <text class="price-value" v-if="!item.maxPrice || item.maxPrice == item.price">{{item.price}}</text>
  103. <text class="price-value" v-else>{{item.price}}-{{item.maxPrice}}</text>
  104. <text class="price-unit">/{{ item.unit || '件' }}</text>
  105. </view>
  106. <view class="quantity-control">
  107. <button class="btn-minus" @click="updateQuantity(index, -1)">-</button>
  108. <text class="quantity">{{getItemTotalQuantity(item)}}</text>
  109. <button class="btn-plus" @click="updateQuantity(index, 1)">+</button>
  110. </view>
  111. </view>
  112. </view>
  113. </view>
  114. </view>
  115. <!-- 不可回收商品 -->
  116. <view class="other-unrecycle-card">
  117. <!-- 使用商品分类数据中的icon -->
  118. <image class="other-unrecycle-img" :src="categories[currentCategory]?.icon" mode="aspectFit" />
  119. <view class="other-unrecycle-info">
  120. <!-- 主标题和副标题用接口返回的otherTitle和otherSubTitle -->
  121. <view class="other-unrecycle-title">{{ categories[currentCategory]?.otherTitle }}</view>
  122. <view class="other-unrecycle-desc">{{ categories[currentCategory]?.otherSubTitle }}</view>
  123. <view class="other-unrecycle-price-row">
  124. <text class="other-unrecycle-price">¥ /{{ categories[currentCategory]?.unit || '件' }}</text>
  125. </view>
  126. </view>
  127. <button class="other-unrecycle-btn" open-type="contact">+</button>
  128. </view>
  129. <view v-if="loadingMore" class="loading-more">加载中...</view>
  130. <view v-else-if="finished" class="loading-more">没有更多了</view>
  131. </scroll-view>
  132. </view>
  133. <!-- 固定底部区域 -->
  134. <view class="fixed-bottom-wrap" v-if="!showDetailPanel" >
  135. <view class="green-tip-bar">
  136. 回收范围仅支持回收以上品类按件回收预计比称重回收多
  137. <text class="tip-highlight"> {{extraRecycleAmount.display}}</text>
  138. </view>
  139. <view class="bottom-bar">
  140. <view class="bottom-left">
  141. <view class="summary-row">
  142. <text class="summary-label">已选 <text class="summary-count">{{totalCount}}</text> {{ totalUnitText }} 预计回收可得</text>
  143. <uni-icons type="help" size="18" color="#b2b2b2" style="margin: 0 8rpx;" @tap="showPriceInfoPopups" />
  144. </view>
  145. <view class="amount-row">
  146. <uni-icons :type="showDetailPanel ? 'up' : 'down'" size="18" color="#5e5e5e" style="margin-right: 8rpx;vertical-align: middle;" @click="toggleDetailPanel" />
  147. <text class="amount" v-if="priceRange.min === priceRange.max">¥{{priceRange.min}}</text>
  148. <text class="amount" v-else>¥{{priceRange.min}}-{{priceRange.max}}</text>
  149. </view>
  150. </view>
  151. <button class="submit-btn" @click="submitOrder">预约上门取件</button>
  152. </view>
  153. <view class="bottom-bar-divider"></view>
  154. </view>
  155. <!-- 明细弹窗遮罩和弹窗 -->
  156. <view v-if="showDetailPanel" class="detail-popup-mask" @click.self="toggleDetailPanel">
  157. <view class="detail-popup" @click.stop>
  158. <view class="detail-popup-close" @click="toggleDetailPanel">×</view>
  159. <view class="green-tip-bar popup-green-tip">
  160. 回收范围仅支持回收以上品类按件回收预计比称重回收多
  161. <text class="tip-highlight"> {{extraRecycleAmount.display}}</text>
  162. </view>
  163. <view class="panel-header">
  164. <text class="panel-title">已选商品明细</text>
  165. </view>
  166. <scroll-view class="panel-list popup-panel-list" scroll-y>
  167. <view v-for="(item, idx) in selectedProducts" :key="item.uniqueKey || idx" class="panel-item">
  168. <image v-if="item.image" :src="item.image" class="panel-item-img" mode="aspectFit" />
  169. <view class="panel-item-info">
  170. <text class="panel-item-name">{{item.name}}</text>
  171. <text class="panel-item-desc" v-if="item.brandName">品牌{{item.brandName}}</text>
  172. <text class="panel-item-desc" v-else>{{item.service}}</text>
  173. <text class="panel-item-price" v-if="!item.maxPrice || item.maxPrice == item.price">¥{{item.price}}/{{ item.unit || '' }}</text>
  174. <text class="panel-item-price" v-else>¥{{item.price}}-{{item.maxPrice}}/{{ item.unit || '' }}</text>
  175. </view>
  176. <view class="panel-quantity-control">
  177. <button class="btn-minus" @click="updateQuantityByProduct(item, -1)">-</button>
  178. <text class="quantity">{{item.quantity}}</text>
  179. <button class="btn-plus" @click="updateQuantityByProduct(item, 1)">+</button>
  180. </view>
  181. </view>
  182. </scroll-view>
  183. <view class="popup-bottom-bar">
  184. <view class="bottom-left">
  185. <view class="summary-row">
  186. <text class="summary-label">已选 <text class="summary-count">{{totalCount}}</text> {{ totalUnitText }} 预计回收可得</text>
  187. <uni-icons type="help" size="18" color="#b2b2b2" style="margin: 0 8rpx;" @tap="showPriceInfoPopups" />
  188. </view>
  189. <view class="amount-row">
  190. <uni-icons :type="showDetailPanel ? 'up' : 'down'" size="18" color="#5e5e5e" style="margin-right: 8rpx;vertical-align: middle;" @click="toggleDetailPanel" />
  191. <text class="amount" v-if="priceRange.min === priceRange.max">¥{{priceRange.min}}</text>
  192. <text class="amount" v-else>¥{{priceRange.min}}-{{priceRange.max}}</text>
  193. </view>
  194. </view>
  195. <button class="submit-btn" @click="submitOrder">预约上门取件</button>
  196. </view>
  197. </view>
  198. </view>
  199. <!-- 价格说明弹窗 -->
  200. <view v-if="showPriceInfoPopup" class="price-info-popup-mask" @click.self="closePriceInfoPopup">
  201. <view class="price-info-popup">
  202. <view class="price-info-popup-title">回收规则</view>
  203. <scroll-view class="price-info-popup-content" scroll-y>
  204. <!-- <view class="price-info-section">
  205. <view class="price-info-heading">关于旧衣质检</view>
  206. <view class="price-info-text">请确认本次回收旧衣是可以进行二次穿着的程度如回收旧衣有破损磨损开线变形起球发黄染色污渍配饰脱落或款式老旧等问题无法通过质检</view>
  207. </view>
  208. <view class="price-info-section">
  209. <view class="price-info-heading">质检报告</view>
  210. <view class="price-info-text">回收商收到衣后1-3个工作日内完成衣质检报告</view>
  211. </view>
  212. <view class="price-info-section">
  213. <view class="price-info-heading">质检结果与回收价格</view>
  214. <view class="price-info-text">若回收旧衣质检通过质检价格与用户提交订单时的预估价格一致回收商将按照预估价打款至您的小程序账户余额</view>
  215. </view> -->
  216. <uv-parse :content="huodong_text"></uv-parse>
  217. </scroll-view>
  218. <button class="price-info-popup-btn" @click="closePriceInfoPopup">我知道了</button>
  219. </view>
  220. </view>
  221. <!-- 根据角色显示不同的导航栏 -->
  222. <uv-tabbar
  223. v-if="ishow"
  224. :value="value"
  225. :fixed="true"
  226. @change="changeTo"
  227. class="uv-tabbar"
  228. >
  229. <uv-tabbar-item text="首页" >
  230. <template v-slot:active-icon>
  231. <image class="icon" src="/static/home/首页-点击.png"></image>
  232. </template>
  233. <template v-slot:inactive-icon>
  234. <image class="icon" src="/static/home/首页-未点击.png"></image>
  235. </template>
  236. </uv-tabbar-item>
  237. <uv-tabbar-item text="回收" >
  238. <template v-slot:active-icon>
  239. <image class="icon" src="/static/home/回收-点击.png"></image>
  240. </template>
  241. <template v-slot:inactive-icon>
  242. <image class="icon" src="/static/home/回收-未点击.png"></image>
  243. </template>
  244. </uv-tabbar-item>
  245. <uv-tabbar-item text="我的" >
  246. <template v-slot:active-icon>
  247. <image class="icon" src="/static/home/我的-点击.png"></image>
  248. </template>
  249. <template v-slot:inactive-icon>
  250. <image class="icon" src="/static/home/我的-未点击.png"></image>
  251. </template>
  252. </uv-tabbar-item>
  253. </uv-tabbar>
  254. <!-- 品牌索引弹窗 -->
  255. <view v-if="showBrandPopup" class="brand-popup-mask">
  256. <view class="brand-popup">
  257. <view class="brand-popup-header">
  258. <text class="brand-popup-close" @click="closeBrandPopup">关闭</text>
  259. <text class="brand-popup-title">可回收的品牌</text>
  260. </view>
  261. <view class="brand-popup-search">
  262. <input class="brand-search-input" v-model="brandSearch" placeholder="请输入要查询的内容" @input="onBrandSearchInput" />
  263. </view>
  264. <scroll-view class="brand-popup-list" scroll-y :scroll-into-view="scrollToView">
  265. <view v-for="letter in brandIndexList" :key="letter" :id="'brand-letter-' + letter">
  266. <view class="brand-letter">{{letter}}</view>
  267. <view v-for="brand in filteredBrandList.filter(b => b.letter === letter)" :key="brand.name" class="brand-item" @click="openBrandConfirm(brand)">
  268. <image :src="brand.logo" class="brand-logo" mode="aspectFit" />
  269. <text class="brand-name">{{brand.name}}</text>
  270. </view>
  271. </view>
  272. </scroll-view>
  273. <view class="brand-index-bar">
  274. <text v-for="letter in brandIndexList" :key="letter" :class="{active: currentLetter === letter}" @click="scrollToLetter(letter)">{{letter}}</text>
  275. </view>
  276. </view>
  277. </view>
  278. <!-- 回收规则弹窗 -->
  279. <view v-if="showRulePopup" class="rule-popup-mask" @click.self="handleRulePopupMaskClick">
  280. <view class="rule-popup">
  281. <view class="rule-popup-title">回收规则</view>
  282. <scroll-view class="rule-popup-content" scroll-y @scroll="onRuleContentScroll" @scrolltolower="onRuleScrollToLower">
  283. <uv-parse :content="ruleHtml" @ready="onRuleContentReady"></uv-parse>
  284. <view class="rule-content-bottom-indicator"></view>
  285. </scroll-view>
  286. <view v-if="!hasScrolledToBottom" class="scroll-tip">请滚动到底部阅读完整内容</view>
  287. <button class="rule-popup-btn" :class="{ disabled: !hasScrolledToBottom }" :disabled="!hasScrolledToBottom" @click.stop="closeRulePopup">我知道了</button>
  288. </view>
  289. </view>
  290. <!-- 预约上门取件弹窗 -->
  291. <view v-if="showPickupConfirm" class="pickup-confirm-mask">
  292. <view class="pickup-confirm-popup">
  293. <view class="pickup-confirm-title">温馨提示</view>
  294. <view class="pickup-confirm-content">
  295. <uv-parse :content="recycle_toast"></uv-parse>
  296. <!-- <view class="confirm-item confirm-item-red">1.当前回收快递免费上门由于快递成本较高为避免不必要的成本及资源二次浪费不属于可回收品类或不符合回收标准的物品请勿寄出</view>
  297. <view class="confirm-item">2.已通过的回收物品将正常结算不符合回收要求的物品可选择快递取回运费自付请在订单结算后48小时内联系在线客服安排取回逾期未联系将默认捐赠无法再次取回</view>
  298. <view class="confirm-item">3.若用户寄出大量不可回收的物品平台有权限制下次回收权限或取消下次包邮服务</view>
  299. <view class="confirm-item">4.对于合格率高的回收订单平台将根据实际情况给予额外回收奖励</view> -->
  300. </view>
  301. <view class="pickup-confirm-btn-row">
  302. <button class="pickup-confirm-btn" @click="handlePickupCancel">取消回收</button>
  303. <button class="pickup-confirm-btn agree" @click="handlePickupAgree">我同意</button>
  304. </view>
  305. </view>
  306. </view>
  307. <!-- 品牌确认弹窗 -->
  308. <view v-if="showBrandConfirm" class="brand-confirm-mask" @click.self="closeBrandConfirm">
  309. <view class="brand-confirm-popup">
  310. <view class="brand-confirm-title">品牌确认提示</view>
  311. <view class="brand-confirm-logo-wrap">
  312. <image :src="brandConfirmInfo.logo" class="brand-confirm-logo" mode="aspectFit" />
  313. </view>
  314. <view class="brand-confirm-name">{{ brandConfirmInfo.name }}</view>
  315. <view class="brand-confirm-desc">请确认所选品牌是否与实物品牌信息一致否则将无法进行回收</view>
  316. <view class="brand-confirm-btn-row">
  317. <button class="brand-confirm-btn retry" @click="closeBrandConfirm">重新选择</button>
  318. <button class="brand-confirm-btn confirm" @click="confirmBrand">确认一致</button>
  319. </view>
  320. </view>
  321. </view>
  322. <!-- 减少数量时的品牌选择弹窗 -->
  323. <view v-if="showBrandReducePopup" class="brand-reduce-popup-mask" @click.self="closeBrandReducePopup">
  324. <view class="brand-reduce-popup">
  325. <view class="brand-reduce-popup-header">
  326. <text class="brand-reduce-popup-close" @click="closeBrandReducePopup">关闭</text>
  327. <text class="brand-reduce-popup-title">选择要减少的品牌</text>
  328. </view>
  329. <scroll-view class="brand-reduce-popup-list" scroll-y>
  330. <view v-for="brand in reduceBrandList" :key="brand.brandId" class="brand-item" @click="selectReduceBrand(brand)">
  331. <image :src="brand.logo" class="brand-logo" mode="aspectFit" />
  332. <text class="brand-name">{{brand.name}}</text>
  333. </view>
  334. </scroll-view>
  335. </view>
  336. </view>
  337. </view>
  338. </template>
  339. <script>
  340. import tabBarMixin from '../mixins/tabBarMixin.js'
  341. import { pinyin } from '../../utils/pinyin.js'
  342. export default {
  343. mixins: [tabBarMixin],
  344. data() {
  345. return {
  346. value:1,
  347. ishow:true,
  348. // 动态数据
  349. allProducts: {}, // { [categoryId]: [商品数组] }
  350. allProductsPage: {}, // { [categoryId]: 当前已加载页码 }
  351. allProductsTotal: {}, // { [categoryId]: 总数 }
  352. pageSize: 10,
  353. currentCategory: 0,
  354. tabbarHeight: 0,
  355. videoPlayingStates: {}, // 记录每个视频的播放状态
  356. showDetailPanel: false,
  357. showBrandPopup: false,
  358. showRulePopup: false,
  359. ruleImgUrl: '/static/回收/回收规则.png',
  360. showPickupConfirm: false,
  361. showBrandConfirm: false,
  362. brandConfirmInfo: {
  363. logo: '',
  364. name: ''
  365. },
  366. brandList: [],
  367. brandCache: {}, // 为每个商品缓存品牌信息 { productId: [brandList] }
  368. currentLetter: 'A',
  369. scrollToView: '',
  370. brandSearch: '',
  371. ruleHtml: '', // 回收规则富文本内容
  372. loadingMore: false,
  373. finished: false,
  374. pendingBrandIndex: null, // 记录待加一的品牌商品index
  375. showPriceInfoPopup: false,
  376. isWaitingForBrandSelection: false, // 等待品牌选择的标志
  377. showBrandReducePopup: false, // 显示减少数量时的品牌选择弹窗
  378. reduceItem: null, // 待减少数量的商品
  379. reduceBrandList: [], // 可减少的品牌列表
  380. viewedRuleItems: new Set(), // 已查看过规则的商品ID集合
  381. loadOptions: null, // 保存options参数
  382. userInfo: null, // 用户信息
  383. isUserBlacklisted: false, // 用户是否被拉黑
  384. currentProductId: null, // 当前查看品牌的商品ID
  385. searchTimer: null, // 搜索防抖定时器
  386. hasScrolledToBottom: false, // 是否已滚动到底部
  387. }
  388. },
  389. computed: {
  390. recycle_banner() {
  391. const item = getApp().globalData.configData.find(i => i.keyName === 'recycle_banner')
  392. return item ? item.keyContent : ''
  393. },
  394. re_key_numer() {
  395. const item = getApp().globalData.configData.find(i => i.keyName === 're_key_numer')
  396. return item ? parseFloat(item.keyContent) : 0.066
  397. },
  398. recycle_toast() {
  399. const item = getApp().globalData.configData.find(i => i.keyName === 'recycle_toast')
  400. return item ? item.keyContent : ''
  401. },
  402. huodong_text() {
  403. const item = getApp().globalData.configData.find(i => i.keyName === 'huodong_text')
  404. return item ? item.keyContent : ''
  405. },
  406. // 当前分类的商品列表
  407. recycleList() {
  408. const currentCategoryId = this.categories[this.currentCategory]?.id
  409. return this.allProducts[currentCategoryId] || []
  410. },
  411. // 计算总数量
  412. totalCount() {
  413. return Object.values(this.allProducts).reduce((total, categoryItems) => {
  414. return total + categoryItems.reduce((sum, item) => {
  415. // 如果商品有品牌数量,汇总所有品牌的数量
  416. if (item.brandQuantities && Object.keys(item.brandQuantities).length > 0) {
  417. return sum + Object.values(item.brandQuantities).reduce((brandSum, qty) => brandSum + qty, 0)
  418. }
  419. // 否则使用原来的quantity字段
  420. return sum + (item.quantity || 0)
  421. }, 0)
  422. }, 0)
  423. },
  424. // 计算总单位文本
  425. totalUnitText() {
  426. // 获取所有已选商品的单位
  427. const units = new Set()
  428. Object.values(this.allProducts).forEach(categoryItems => {
  429. categoryItems.forEach(item => {
  430. let hasQuantity = false
  431. if (item.brandQuantities && Object.keys(item.brandQuantities).length > 0) {
  432. hasQuantity = Object.values(item.brandQuantities).some(qty => qty > 0)
  433. } else {
  434. hasQuantity = (item.quantity || 0) > 0
  435. }
  436. if (hasQuantity) {
  437. units.add(item.unit || '件')
  438. }
  439. })
  440. })
  441. // 如果只有一种单位,显示单位;如果混合多种单位,显示"项"
  442. if (units.size === 0) return '件'
  443. if (units.size === 1) return Array.from(units)[0]
  444. return '项'
  445. },
  446. // 计算总价格范围
  447. totalPriceRange() {
  448. const result = Object.values(this.allProducts).reduce((categoryTotal, categoryItems) => {
  449. return categoryItems.reduce((sum, item) => {
  450. let itemQuantity = 0
  451. // 如果商品有品牌数量,汇总所有品牌的数量
  452. if (item.brandQuantities && Object.keys(item.brandQuantities).length > 0) {
  453. itemQuantity = Object.values(item.brandQuantities).reduce((brandSum, qty) => brandSum + qty, 0)
  454. } else {
  455. itemQuantity = item.quantity || 0
  456. }
  457. if (itemQuantity > 0) {
  458. const minPrice = Number(item.price) || 0
  459. const maxPrice = Number(item.maxPrice) || Number(item.price) || 0
  460. sum.min += itemQuantity * minPrice
  461. sum.max += itemQuantity * maxPrice
  462. }
  463. return sum
  464. }, categoryTotal)
  465. }, { min: 0, max: 0 })
  466. return {
  467. min: result.min.toFixed(1),
  468. max: result.max.toFixed(1)
  469. }
  470. },
  471. // 计算总价格 (保持兼容性,使用最低价格)
  472. totalPrice() {
  473. return this.totalPriceRange.min
  474. },
  475. // 计算价格范围
  476. priceRange() {
  477. if (this.totalCount === 0) {
  478. return {
  479. min: '0.0',
  480. max: '0.0'
  481. }
  482. }
  483. return this.totalPriceRange
  484. },
  485. selectedProducts() {
  486. // 返回所有分类下所有已选商品,按品牌分组
  487. const products = []
  488. Object.values(this.allProducts).flat().forEach(item => {
  489. if (item.brandQuantities && Object.keys(item.brandQuantities).length > 0) {
  490. // 按品牌分别添加
  491. Object.entries(item.brandQuantities).forEach(([brandId, quantity]) => {
  492. if (quantity > 0) {
  493. const brandInfo = this.getBrandInfo(brandId)
  494. products.push({
  495. ...item,
  496. quantity: quantity,
  497. brandId: brandId,
  498. brandName: brandInfo ? brandInfo.name : '未知品牌',
  499. brandImage: brandInfo ? brandInfo.logo : '',
  500. uniqueKey: `${item.id}_${brandId}` // 用于区分同商品不同品牌
  501. })
  502. }
  503. })
  504. } else if (item.quantity > 0) {
  505. // 没有品牌的商品
  506. products.push({
  507. ...item,
  508. uniqueKey: item.id
  509. })
  510. }
  511. })
  512. return products
  513. },
  514. filteredBrandList() {
  515. return this.brandList
  516. },
  517. bannerList() {
  518. return getApp().globalData.bannerList || []
  519. },
  520. categories() {
  521. const list = getApp().globalData.pricePreviewList || []
  522. return list.sort((a, b) => a.sort - b.sort)
  523. // return list.filter(item => item.pid === '0').sort((a, b) => a.sort - b.sort)
  524. },
  525. minMoney() {
  526. const config = getApp().globalData.configData || [];
  527. const item = config.find(i => i.keyName === 'min_money');
  528. return item ? parseFloat(item.keyContent) : 0;
  529. },
  530. minMumber() {
  531. const config = getApp().globalData.configData || [];
  532. const item = config.find(i => i.keyName === 'min_number');
  533. return item ? parseFloat(item.keyContent) : 0;
  534. },
  535. // 计算比预计回收多的金额(商品回收价格 - (商品回收价格 * 0.066))
  536. extraRecycleAmount() {
  537. const minPrice = parseFloat(this.priceRange.min) || 0;
  538. const maxPrice = parseFloat(this.priceRange.max) || 0;
  539. if (minPrice === 0 && maxPrice === 0) {
  540. return {
  541. min: '0.00',
  542. max: '0.00',
  543. display: '0.00元'
  544. };
  545. }
  546. let re_key_numer = this.re_key_numer || 0.066
  547. // 计算减去6.6%后的金额
  548. const minExtra = minPrice - (minPrice * re_key_numer);
  549. const maxExtra = maxPrice - (maxPrice * re_key_numer);
  550. // 如果最小值和最大值相等,显示单个值
  551. if (minPrice === maxPrice) {
  552. return {
  553. min: minExtra.toFixed(2),
  554. max: maxExtra.toFixed(2),
  555. display: `${minExtra.toFixed(2)}`
  556. };
  557. }
  558. // 显示区间
  559. return {
  560. min: minExtra.toFixed(2),
  561. max: maxExtra.toFixed(2),
  562. display: `${minExtra.toFixed(2)}-${maxExtra.toFixed(2)}`
  563. };
  564. },
  565. // 动态生成品牌字母索引,只显示有品牌的字母
  566. brandIndexList() {
  567. const letters = new Set()
  568. let hasSharp = false
  569. this.brandList.forEach(b => {
  570. if (b.letter && /^[A-Z]$/.test(b.letter)) {
  571. letters.add(b.letter)
  572. } else {
  573. letters.add('#')
  574. hasSharp = true
  575. }
  576. })
  577. const arr = Array.from(letters).filter(l => l !== '#').sort()
  578. if (hasSharp) arr.push('#')
  579. return arr
  580. },
  581. },
  582. methods: {
  583. showPriceInfoPopups() {
  584. console.log('showPriceInfoPopup called');
  585. this.showPriceInfoPopup = true
  586. },
  587. closePriceInfoPopup() {
  588. console.log('closePriceInfoPopup called');
  589. this.showPriceInfoPopup = false
  590. },
  591. changeTo(e){
  592. this.value = e
  593. if(e==2){
  594. uni.reLaunch({
  595. url: '/pages/component/my'
  596. });
  597. }else if(e==0){
  598. console.log(e,'111')
  599. uni.reLaunch({
  600. url: '/pages/component/home'
  601. });
  602. }
  603. },
  604. fetchGoodsList(categoryId, page = 1, callback) {
  605. this.$api('getClassGoodsList', {
  606. classId: categoryId,
  607. pageNo: page,
  608. pageSize: this.pageSize
  609. }, res => {
  610. if (res.code === 200 && res.result && Array.isArray(res.result.records)) {
  611. const oldList = this.allProducts[categoryId] || []
  612. const newList = page === 1 ? res.result.records : oldList.concat(res.result.records)
  613. this.$set(this.allProducts, categoryId, newList)
  614. this.$set(this.allProductsPage, categoryId, page)
  615. this.$set(this.allProductsTotal, categoryId, res.result.total)
  616. }
  617. if (callback) callback()
  618. })
  619. },
  620. // 获取分类商品总数
  621. getCategoryItemCount(index) {
  622. const categoryId = this.categories[index]?.id
  623. const categoryItems = this.allProducts[categoryId] || []
  624. return categoryItems.reduce((sum, item) => {
  625. if (item.brandQuantities && Object.keys(item.brandQuantities).length > 0) {
  626. return sum + Object.values(item.brandQuantities).reduce((brandSum, qty) => brandSum + qty, 0)
  627. }
  628. return sum + (item.quantity || 0)
  629. }, 0)
  630. },
  631. // 切换分类
  632. switchCategory(index) {
  633. this.currentCategory = index
  634. this.loadingMore = false
  635. this.finished = false
  636. const categoryId = this.categories[index]?.id
  637. // console.log(categoryId,'switchCategory')
  638. if (!this.allProducts[categoryId]) {
  639. this.fetchGoodsList(categoryId, 1)
  640. }
  641. },
  642. // 更新商品数量
  643. updateQuantity(index, delta) {
  644. const categoryId = this.categories[this.currentCategory]?.id
  645. const item = this.allProducts[categoryId]?.[index]
  646. if (!item) return
  647. // 如果是减少数量且delta为负数
  648. if (delta < 0) {
  649. // 检查是否有多个品牌
  650. if (item.brandQuantities && Object.keys(item.brandQuantities).length > 1) {
  651. // 有多个品牌,显示选择弹窗
  652. this.reduceItem = { item, index, delta }
  653. this.reduceBrandList = Object.entries(item.brandQuantities)
  654. .filter(([brandId, quantity]) => quantity > 0)
  655. .map(([brandId, quantity]) => {
  656. const brandInfo = this.getBrandInfo(brandId)
  657. return {
  658. brandId,
  659. quantity,
  660. name: brandInfo ? brandInfo.name : '未知品牌',
  661. logo: brandInfo ? brandInfo.logo : ''
  662. }
  663. })
  664. this.showBrandReducePopup = true
  665. return
  666. } else if (item.brandQuantities && Object.keys(item.brandQuantities).length === 1) {
  667. // 只有一个品牌,直接减少
  668. const brandId = Object.keys(item.brandQuantities)[0]
  669. const currentQty = item.brandQuantities[brandId] || 0
  670. const newQty = Math.max(0, currentQty + delta)
  671. this.$set(item.brandQuantities, brandId, newQty)
  672. // 如果数量为0,删除该品牌
  673. if (newQty === 0) {
  674. delete item.brandQuantities[brandId]
  675. }
  676. return
  677. } else {
  678. // 没有品牌数量,使用原来的逻辑
  679. let newQuantity = (item.quantity || 0) + delta
  680. if (newQuantity < 0) newQuantity = 0
  681. this.$set(item, 'quantity', newQuantity)
  682. return
  683. }
  684. }
  685. // 品牌商品且数量为0且加一时
  686. if (item.isPin === 'Y' && (item.quantity || 0) === 0 && delta > 0) {
  687. this.pendingBrandIndex = index
  688. this.isWaitingForBrandSelection = true;
  689. this.showRules(item); // 先显示回收规则
  690. return
  691. }
  692. // 无品牌商品,数量为0且加一时,也弹规则,确认后再加数量
  693. if (item.isPin !== 'Y' && (item.quantity || 0) === 0 && delta > 0) {
  694. this.pendingBrandIndex = index
  695. this.isWaitingForBrandSelection = false; // 标记为无品牌
  696. this.showRules(item)
  697. return
  698. }
  699. // 其它情况直接加数量
  700. let newQuantity = (item.quantity || 0) + delta
  701. if (newQuantity < 0) newQuantity = 0
  702. this.$set(item, 'quantity', newQuantity)
  703. },
  704. // 选择要减少的品牌
  705. selectReduceBrand(brandInfo) {
  706. const { item, index, delta } = this.reduceItem
  707. const currentQty = item.brandQuantities[brandInfo.brandId] || 0
  708. const newQty = Math.max(0, currentQty + delta)
  709. this.$set(item.brandQuantities, brandInfo.brandId, newQty)
  710. // 如果数量为0,删除该品牌
  711. if (newQty === 0) {
  712. delete item.brandQuantities[brandInfo.brandId]
  713. }
  714. this.closeBrandReducePopup()
  715. },
  716. // 关闭减少品牌选择弹窗
  717. closeBrandReducePopup() {
  718. this.showBrandReducePopup = false
  719. this.reduceItem = null
  720. this.reduceBrandList = []
  721. },
  722. // 获取品牌信息
  723. getBrandInfo(brandId) {
  724. // 从所有商品的品牌缓存中查找品牌信息
  725. for (const productId in this.brandCache) {
  726. const brandInfo = this.brandCache[productId].find(brand => brand.id === brandId)
  727. if (brandInfo) {
  728. return brandInfo
  729. }
  730. }
  731. // 如果缓存中没有找到,也从当前brandList中查找(兼容性)
  732. return this.brandList.find(brand => brand.id === brandId)
  733. },
  734. // 获取商品的总数量(所有品牌)
  735. getItemTotalQuantity(item) {
  736. if (item.brandQuantities && Object.keys(item.brandQuantities).length > 0) {
  737. return Object.values(item.brandQuantities).reduce((sum, qty) => sum + qty, 0)
  738. }
  739. return item.quantity || 0
  740. },
  741. // 显示回收规则
  742. showRules(item) {
  743. // isPin=Y: 弹规则,读到底部后自动弹品牌;isPin=N: 只弹规则
  744. if (item.isPin === 'Y') {
  745. // 检查该商品是否已经查看过规则
  746. if (this.viewedRuleItems.has(item.id)) {
  747. // 如果已经查看过,直接跳过规则弹窗,进入品牌选择
  748. this.isWaitingForBrandSelection = false;
  749. this.getGoodsBrandList(item.id);
  750. this.showBrandPopup = true;
  751. return;
  752. }
  753. // 重置滚动状态
  754. this.hasScrolledToBottom = false;
  755. // 获取回收规则富文本
  756. this.$api('getGoodsRecycleRule', { goodsId: item.id }, res => {
  757. if (res.code === 200 && res.result) {
  758. this.ruleHtml = res.result
  759. } else {
  760. this.ruleHtml = '<p>暂无回收规则</p>'
  761. }
  762. this.showRulePopup = true
  763. // 规则弹窗关闭后自动弹品牌弹窗逻辑在closeRulePopup已实现
  764. })
  765. } else {
  766. // isPin=N 只弹规则
  767. this.hasScrolledToBottom = false;
  768. this.$api('getGoodsRecycleRule', { goodsId: item.id }, res => {
  769. if (res.code === 200 && res.result) {
  770. this.ruleHtml = res.result
  771. } else {
  772. this.ruleHtml = '<p>暂无回收规则</p>'
  773. }
  774. this.showRulePopup = true
  775. // 不弹品牌弹窗
  776. this.isWaitingForBrandSelection = false;
  777. })
  778. }
  779. },
  780. showMore() {
  781. uni.showToast({
  782. title: '更多规则请咨询客服',
  783. icon: 'none'
  784. })
  785. },
  786. submitOrder() {
  787. // 检查用户是否被拉入黑名单
  788. if (this.isUserBlacklisted) {
  789. uni.showModal({
  790. title: '提示',
  791. content: '您的账户已被限制使用回收服务,如有疑问请联系客服。',
  792. showCancel: false,
  793. confirmText: '我知道了'
  794. })
  795. return
  796. }
  797. if (this.totalCount < this.minMumber || Number(this.totalPrice) < this.minMoney) {
  798. uni.showToast({
  799. title: `各品类混合需要满${this.minMumber}${this.totalUnitText}并且各品类混合需要满${this.minMoney}元才能回收哦`,
  800. icon: 'none'
  801. })
  802. return
  803. }
  804. this.showPickupConfirm = true;
  805. },
  806. handlePickupCancel() {
  807. this.showPickupConfirm = false;
  808. },
  809. handlePickupAgree() {
  810. this.showPickupConfirm = false;
  811. uni.showLoading({
  812. title: '提交中...'
  813. })
  814. setTimeout(() => {
  815. uni.hideLoading()
  816. uni.showToast({
  817. title: '预约成功',
  818. icon: 'success'
  819. })
  820. this.goToPickup()
  821. }, 1500)
  822. },
  823. goToPickup() {
  824. // 获取所有选中的衣物(所有分类)
  825. const selectedItems = this.selectedProducts.map(item => {
  826. const baseItem = {
  827. id: item.id,
  828. name: item.name,
  829. icon: item.image,
  830. quantity: item.quantity,
  831. unitPrice: item.price,
  832. maxPrice: item.maxPrice,
  833. desc: item.brandName ? `品牌:${item.brandName}` : '允许脏破烂,160码以上'
  834. }
  835. // 如果有品牌信息,添加品牌相关字段
  836. if (item.brandId) {
  837. baseItem.brandId = item.brandId
  838. baseItem.brandName = item.brandName
  839. baseItem.brandImage = item.brandImage
  840. }
  841. return baseItem
  842. })
  843. const itemsStr = encodeURIComponent(JSON.stringify(selectedItems))
  844. uni.navigateTo({
  845. url: `/pages/subcomponent/pickup?fromRecycle=true&items=${itemsStr}`
  846. })
  847. },
  848. checkBrand(index) {
  849. const categoryId = this.categories[this.currentCategory]?.id
  850. const item = this.allProducts[categoryId]?.[index]
  851. if (item?.id) {
  852. this.pendingBrandIndex = index
  853. this.getGoodsBrandList(item.id)
  854. this.showBrandPopup = true
  855. }
  856. },
  857. closeBrandPopup() {
  858. this.showBrandPopup = false
  859. // 如果用户取消品牌选择,重置状态
  860. this.pendingBrandIndex = null;
  861. this.isWaitingForBrandSelection = false;
  862. // 清理搜索状态
  863. this.brandSearch = ''
  864. this.currentProductId = null
  865. if (this.searchTimer) {
  866. clearTimeout(this.searchTimer)
  867. this.searchTimer = null
  868. }
  869. },
  870. scrollToLetter(letter) {
  871. this.currentLetter = letter
  872. this.scrollToView = 'brand-letter-' + letter
  873. },
  874. // 添加下拉刷新方法
  875. async refreshData() {
  876. try {
  877. // 这里可以添加刷新数据的逻辑,比如重新获取商品列表等
  878. // 示例:重新初始化数据
  879. this.currentCategory = 0
  880. Object.values(this.allProducts).forEach(categoryItems => {
  881. categoryItems.forEach(item => {
  882. item.quantity = 0
  883. if (item.brandQuantities) {
  884. item.brandQuantities = {}
  885. }
  886. })
  887. })
  888. // 清空已查看规则的记录
  889. this.viewedRuleItems.clear()
  890. // 清空品牌缓存
  891. this.brandCache = {}
  892. // 模拟网络请求延迟
  893. await new Promise(resolve => setTimeout(resolve, 1000))
  894. uni.showToast({
  895. title: '刷新成功',
  896. icon: 'success'
  897. })
  898. } catch (error) {
  899. uni.showToast({
  900. title: '刷新失败',
  901. icon: 'none'
  902. })
  903. } finally {
  904. // 停止下拉刷新动画
  905. uni.stopPullDownRefresh()
  906. }
  907. },
  908. toggleDetailPanel() {
  909. this.showDetailPanel = !this.showDetailPanel
  910. },
  911. fetchUserInfo() {
  912. if(uni.getStorageSync('token')){
  913. this.login_status = getApp().globalData.login_status;
  914. this.$api("getUserByToken",{},(res)=>{
  915. if(res.code == 200){
  916. this.userInfo = res.result
  917. // 检查用户是否被拉入黑名单
  918. this.isUserBlacklisted = res.result.isBlack === 'Y'
  919. // isTuiType 为0用户,1推广达人,2推广大使
  920. }
  921. })
  922. } else {
  923. this.login_status = false;
  924. }
  925. },
  926. updateQuantityByProduct(item, delta) {
  927. // 在明细弹窗中更新数量
  928. if (item.brandId) {
  929. // 有品牌ID的商品
  930. const originalItem = this.findOriginalItem(item.id)
  931. if (originalItem && originalItem.brandQuantities) {
  932. const currentQty = originalItem.brandQuantities[item.brandId] || 0
  933. const newQty = Math.max(0, currentQty + delta)
  934. this.$set(originalItem.brandQuantities, item.brandId, newQty)
  935. // 如果数量为0,删除该品牌
  936. if (newQty === 0) {
  937. delete originalItem.brandQuantities[item.brandId]
  938. }
  939. // 同步更新显示的数量
  940. item.quantity = newQty
  941. }
  942. } else {
  943. // 没有品牌的商品
  944. if (!item.quantity) item.quantity = 0
  945. item.quantity += delta
  946. if (item.quantity < 0) item.quantity = 0
  947. // 同步到原商品
  948. const originalItem = this.findOriginalItem(item.id)
  949. if (originalItem) {
  950. this.$set(originalItem, 'quantity', item.quantity)
  951. }
  952. }
  953. },
  954. // 查找原始商品对象
  955. findOriginalItem(itemId) {
  956. for (const categoryItems of Object.values(this.allProducts)) {
  957. const item = categoryItems.find(i => i.id === itemId)
  958. if (item) return item
  959. }
  960. return null
  961. },
  962. openRulePopup() {
  963. this.showRulePopup = true
  964. // 弹窗显示后主动判断内容是否需要滚动
  965. this.$nextTick(() => {
  966. const query = uni.createSelectorQuery().in(this)
  967. query.select('.rule-popup-content').boundingClientRect(rect => {
  968. if (rect && rect.height && rect.scrollHeight && rect.scrollHeight <= rect.height + 10) {
  969. this.hasScrolledToBottom = true
  970. }
  971. }).exec()
  972. })
  973. },
  974. closeRulePopup() {
  975. if (!this.hasScrolledToBottom) {
  976. uni.showToast({
  977. title: '请阅读完整回收规则',
  978. icon: 'none'
  979. })
  980. return
  981. }
  982. this.showRulePopup = false;
  983. this.hasScrolledToBottom = false; // 重置滚动状态
  984. // 如果是在等待品牌选择的状态下关闭规则弹窗,则接着打开品牌选择
  985. if (this.isWaitingForBrandSelection) {
  986. this.isWaitingForBrandSelection = false; // 清除等待状态
  987. const categoryId = this.categories[this.currentCategory]?.id;
  988. const item = this.allProducts[categoryId]?.[this.pendingBrandIndex];
  989. // 记录该商品的规则已被查看
  990. this.viewedRuleItems.add(item.id);
  991. this.getGoodsBrandList(item.id);
  992. this.showBrandPopup = true; // 打开品牌索引弹窗
  993. } else if (this.pendingBrandIndex !== null) {
  994. // 无品牌商品,规则弹窗关闭后加数量
  995. const categoryId = this.categories[this.currentCategory]?.id;
  996. const item = this.allProducts[categoryId]?.[this.pendingBrandIndex];
  997. if (item) {
  998. let newQuantity = (item.quantity || 0) + 1
  999. this.$set(item, 'quantity', newQuantity)
  1000. }
  1001. this.pendingBrandIndex = null
  1002. }
  1003. },
  1004. // 处理规则弹窗遮罩点击
  1005. handleRulePopupMaskClick() {
  1006. if (!this.hasScrolledToBottom) {
  1007. uni.showToast({
  1008. title: '请阅读完整回收规则',
  1009. icon: 'none'
  1010. })
  1011. return
  1012. }
  1013. this.closeRulePopup()
  1014. },
  1015. // 监听规则内容滚动
  1016. onRuleContentScroll(e) {
  1017. const { scrollTop, scrollHeight, clientHeight, height } = e.detail
  1018. const h = clientHeight || height
  1019. // 内容高度不够,无需滚动,直接允许
  1020. if (scrollHeight <= h + 10) {
  1021. this.hasScrolledToBottom = true
  1022. return
  1023. }
  1024. if (scrollTop + h >= scrollHeight - 20) {
  1025. this.hasScrolledToBottom = true
  1026. }
  1027. },
  1028. loadMoreGoods() {
  1029. const categoryId = this.categories[this.currentCategory]?.id
  1030. const page = (this.allProductsPage[categoryId] || 1) + 1
  1031. const total = this.allProductsTotal[categoryId] || 0
  1032. const loaded = (this.allProducts[categoryId] || []).length
  1033. if (this.loadingMore || this.finished) return
  1034. if (loaded < total) {
  1035. this.loadingMore = true
  1036. this.fetchGoodsList(categoryId, page, () => {
  1037. this.loadingMore = false
  1038. // 判断是否加载完
  1039. const newLoaded = (this.allProducts[categoryId] || []).length
  1040. this.finished = newLoaded >= (this.allProductsTotal[categoryId] || 0)
  1041. })
  1042. } else {
  1043. this.finished = true
  1044. }
  1045. },
  1046. openBrandConfirm(brand) {
  1047. this.brandConfirmInfo = {
  1048. id: brand.id,
  1049. logo: brand.logo,
  1050. name: brand.name
  1051. }
  1052. this.showBrandConfirm = true
  1053. },
  1054. closeBrandConfirm() {
  1055. this.showBrandConfirm = false
  1056. },
  1057. confirmBrand() {
  1058. this.showBrandConfirm = false
  1059. this.showBrandPopup = false
  1060. // 确认后将待加一的商品数量+1,并记录品牌ID
  1061. if (this.pendingBrandIndex !== null) {
  1062. const categoryId = this.categories[this.currentCategory]?.id
  1063. const item = this.allProducts[categoryId]?.[this.pendingBrandIndex]
  1064. if (item) {
  1065. // 初始化品牌数量对象
  1066. if (!item.brandQuantities) {
  1067. this.$set(item, 'brandQuantities', {})
  1068. }
  1069. // 增加该品牌的数量
  1070. const currentQty = item.brandQuantities[this.brandConfirmInfo.id] || 0
  1071. this.$set(item.brandQuantities, this.brandConfirmInfo.id, currentQty + 1)
  1072. // 清除原来的quantity(如果存在)
  1073. if (item.quantity) {
  1074. this.$set(item, 'quantity', 0)
  1075. }
  1076. }
  1077. this.pendingBrandIndex = null
  1078. }
  1079. },
  1080. getGoodsBrandList(productId, searchName = '') {
  1081. this.currentProductId = productId
  1082. const params = { productId }
  1083. if (searchName.trim()) {
  1084. params.name = searchName.trim()
  1085. }
  1086. this.$api('getGoodsBrandList', params, res => {
  1087. // console.log(res,'res')
  1088. if (res && res.success && res.result && res.result.records) {
  1089. const brandList = res.result.records.map(item => {
  1090. // 获取品牌名称的拼音首字母
  1091. const firstChar = this.getPinyinFirstLetter(item.name)
  1092. return {
  1093. id: item.id,
  1094. logo: item.image || '/static/brand/alexander.png',
  1095. name: item.name,
  1096. letter: firstChar,
  1097. isPin: item.isPin
  1098. }
  1099. })
  1100. // 缓存到对应商品的品牌列表中
  1101. this.$set(this.brandCache, productId, brandList)
  1102. // 同时更新当前显示的brandList(用于弹窗显示)
  1103. this.brandList = [...brandList]
  1104. }
  1105. })
  1106. },
  1107. // 获取中文拼音首字母
  1108. getPinyinFirstLetter(str) {
  1109. if (!str) return '#'
  1110. const firstChar = str.charAt(0)
  1111. // 遍历pinyin对象,查找包含该汉字的拼音
  1112. for (let key in pinyin) {
  1113. const chars = pinyin[key]
  1114. if (chars && chars.indexOf(firstChar) !== -1) {
  1115. return key.charAt(0).toUpperCase()
  1116. }
  1117. }
  1118. // 英文首字母
  1119. if (/^[A-Za-z]$/.test(firstChar)) {
  1120. return firstChar.toUpperCase()
  1121. }
  1122. return '#'
  1123. },
  1124. // 品牌搜索输入事件处理
  1125. onBrandSearchInput(e) {
  1126. const searchValue = e.detail.value
  1127. // 清除之前的定时器
  1128. if (this.searchTimer) {
  1129. clearTimeout(this.searchTimer)
  1130. }
  1131. // 设置防抖,500ms后执行搜索
  1132. this.searchTimer = setTimeout(() => {
  1133. if (this.currentProductId) {
  1134. this.getGoodsBrandList(this.currentProductId, searchValue)
  1135. }
  1136. }, 500)
  1137. },
  1138. // 视频相关方法
  1139. playVideoFullscreen(item, index) {
  1140. if (!this.videoPlayingStates[index]) {
  1141. // 第一次点击:显示视频并开始播放
  1142. this.$set(this.videoPlayingStates, index, true);
  1143. // 等待视频元素渲染后再进行操作
  1144. this.$nextTick(() => {
  1145. setTimeout(() => {
  1146. const videoContext = uni.createVideoContext(`recycle-video-${index}`, this);
  1147. if (videoContext) {
  1148. // 直接进入全屏播放,强制竖屏
  1149. videoContext.requestFullScreen({
  1150. direction: 0 // 竖屏方向,0代表竖屏,1代表横屏,-1自动
  1151. });
  1152. }
  1153. }, 200);
  1154. });
  1155. }
  1156. },
  1157. onVideoPlay(index) {
  1158. this.$set(this.videoPlayingStates, index, true);
  1159. },
  1160. onVideoPause(index) {
  1161. this.$set(this.videoPlayingStates, index, false);
  1162. },
  1163. onVideoEnded(index) {
  1164. // 视频播放结束,回到预览状态
  1165. this.$set(this.videoPlayingStates, index, false);
  1166. },
  1167. onFullscreenChange(e) {
  1168. console.log('全屏状态改变:', e.detail);
  1169. const videoIndex = e.target.id.replace('recycle-video-', '');
  1170. if (e.detail.fullScreen) {
  1171. // 进入全屏时,不做任何操作
  1172. console.log('进入全屏模式,方向:', e.detail.direction);
  1173. } else {
  1174. // 退出全屏时,回到预览状态
  1175. console.log('退出全屏模式,回到预览状态');
  1176. this.$set(this.videoPlayingStates, videoIndex, false);
  1177. }
  1178. },
  1179. // 初始化页面数据的方法
  1180. initializePageData() {
  1181. const options = this.loadOptions || {}
  1182. if (options && options.categoryId) {
  1183. const idx = this.categories.findIndex(c => c.id == options.categoryId)
  1184. if (idx !== -1) this.currentCategory = idx
  1185. }
  1186. if (this.categories.length > 0) {
  1187. this.fetchGoodsList(this.categories[this.currentCategory].id, 1)
  1188. }
  1189. uni.$on('bannerListUpdated', () => {
  1190. this.$forceUpdate && this.$forceUpdate()
  1191. })
  1192. if (getApp().globalData.bannerList && getApp().globalData.bannerList.length > 0) {
  1193. this.$forceUpdate && this.$forceUpdate()
  1194. }
  1195. // 检查全局清空标志(兼容 reLaunch)
  1196. if (getApp().globalData.shouldClearRecycle) {
  1197. Object.values(this.allProducts).forEach(categoryItems => {
  1198. categoryItems.forEach(item => {
  1199. this.$set(item, 'quantity', 0)
  1200. if (item.brandQuantities) {
  1201. this.$set(item, 'brandQuantities', {})
  1202. }
  1203. })
  1204. })
  1205. // 清空已查看规则的记录
  1206. this.viewedRuleItems.clear()
  1207. this.showDetailPanel = false
  1208. this.brandCache = {} // 清空品牌缓存
  1209. this.$forceUpdate()
  1210. getApp().globalData.shouldClearRecycle = false
  1211. }
  1212. },
  1213. onRuleContentReady() {
  1214. this.$nextTick(() => {
  1215. const query = uni.createSelectorQuery().in(this)
  1216. query.select('.rule-popup-content').boundingClientRect(rect => {
  1217. query.select('.rule-popup-content').scrollOffset(scroll => {
  1218. // 只有内容高度小于等于可视高度时才点亮
  1219. if (scroll.scrollHeight <= rect.height + 1) {
  1220. this.hasScrolledToBottom = true
  1221. }
  1222. })
  1223. }).exec()
  1224. })
  1225. },
  1226. onRuleScrollToLower() {
  1227. this.hasScrolledToBottom = true
  1228. },
  1229. },
  1230. created() {
  1231. this.currentCategory = 0
  1232. this.$nextTick(() => {
  1233. if (this.categories.length > 0) {
  1234. const firstCategoryId = this.categories[0]?.id
  1235. if (firstCategoryId) {
  1236. this.fetchGoodsList(firstCategoryId, 1)
  1237. }
  1238. }
  1239. })
  1240. },
  1241. mounted() {
  1242. this.$nextTick(() => {
  1243. const query = uni.createSelectorQuery().in(this)
  1244. query.select('.uv-tabbar').boundingClientRect(rect => {
  1245. if (rect && rect.height) {
  1246. this.tabbarHeight = rect.height
  1247. } else {
  1248. this.tabbarHeight = uni.upx2px ? uni.upx2px(95) : 45
  1249. }
  1250. // console.log(this.tabbarHeight,'tabbarHeight')
  1251. }).exec()
  1252. })
  1253. },
  1254. onLoad(options) {
  1255. // 保存options参数
  1256. this.loadOptions = options
  1257. // 检查App数据是否已经准备好
  1258. if (!getApp().globalData.isAppDataReady) {
  1259. // 显示加载状态
  1260. uni.showLoading({
  1261. title: '加载中...',
  1262. mask: true
  1263. })
  1264. // 监听App数据准备完成事件
  1265. uni.$on('appDataReady', () => {
  1266. uni.hideLoading()
  1267. this.initializePageData()
  1268. })
  1269. } else {
  1270. // App数据已经准备好,直接初始化页面数据
  1271. this.initializePageData()
  1272. }
  1273. },
  1274. onUnload() {
  1275. uni.$off('bannerListUpdated')
  1276. // 移除事件监听
  1277. uni.$off('clearRecycleOrderData')
  1278. },
  1279. onShow() {
  1280. // 获取用户信息,检查黑名单状态
  1281. this.fetchUserInfo()
  1282. const id = getApp().globalData.targetRecycleCategoryId
  1283. if (id) {
  1284. const trySwitch = () => {
  1285. if (this.categories.length > 0) {
  1286. const idx = this.categories.findIndex(c => String(c.id) === String(id))
  1287. if (idx !== -1) {
  1288. this.currentCategory = idx
  1289. const categoryId = this.categories[idx]?.id
  1290. if (categoryId && !this.allProducts[categoryId]) {
  1291. this.loadingMore = false
  1292. this.finished = false
  1293. this.fetchGoodsList(categoryId, 1)
  1294. }
  1295. }
  1296. getApp().globalData.targetRecycleCategoryId = null
  1297. } else {
  1298. setTimeout(trySwitch, 100)
  1299. }
  1300. }
  1301. trySwitch()
  1302. }
  1303. // 检查全局清空标志
  1304. if (getApp().globalData.shouldClearRecycle) {
  1305. Object.values(this.allProducts).forEach(categoryItems => {
  1306. categoryItems.forEach(item => {
  1307. this.$set(item, 'quantity', 0)
  1308. if (item.brandQuantities) {
  1309. this.$set(item, 'brandQuantities', {})
  1310. }
  1311. })
  1312. })
  1313. // 清空已查看规则的记录
  1314. this.viewedRuleItems.clear()
  1315. this.showDetailPanel = false
  1316. this.brandCache = {} // 清空品牌缓存
  1317. this.$forceUpdate()
  1318. getApp().globalData.shouldClearRecycle = false
  1319. }
  1320. // 监听清除订单数据的事件
  1321. uni.$on('clearRecycleOrderData', () => {
  1322. // 清除所有商品的选中数量,保证响应式
  1323. Object.values(this.allProducts).forEach(categoryItems => {
  1324. categoryItems.forEach(item => {
  1325. this.$set(item, 'quantity', 0)
  1326. if (item.brandQuantities) {
  1327. this.$set(item, 'brandQuantities', {})
  1328. }
  1329. })
  1330. })
  1331. // 清空已查看规则的记录
  1332. this.viewedRuleItems.clear()
  1333. // 清空品牌缓存
  1334. this.brandCache = {}
  1335. // 重置其他相关数据
  1336. this.showDetailPanel = false
  1337. this.$forceUpdate()
  1338. })
  1339. },
  1340. watch: {
  1341. categories(newVal) {
  1342. const id = getApp().globalData.targetRecycleCategoryId
  1343. const idx = newVal.findIndex(c => String(c.id) === String(id))
  1344. if (id && newVal.length > 0 && idx !== -1) {
  1345. this.currentCategory = idx
  1346. getApp().globalData.targetRecycleCategoryId = null
  1347. // 自动加载右侧商品
  1348. const categoryId = newVal[idx]?.id
  1349. if (categoryId && !this.allProducts[categoryId]) {
  1350. this.loadingMore = false
  1351. this.finished = false
  1352. this.fetchGoodsList(categoryId, 1)
  1353. }
  1354. }
  1355. }
  1356. },
  1357. }
  1358. </script>
  1359. <style lang="scss" scoped>
  1360. .container {
  1361. display: flex;
  1362. flex-direction: column;
  1363. height: 100vh;
  1364. background-color: #f5f5f5;
  1365. overflow: hidden;
  1366. }
  1367. .banner {
  1368. background: linear-gradient(135deg, #ff9500,#ff5e00);
  1369. position: relative;
  1370. height: 350rpx;
  1371. z-index: 1;
  1372. image {
  1373. width: 100%;
  1374. height: 90%;
  1375. }
  1376. .video-container {
  1377. position: relative;
  1378. width: 100%;
  1379. height: 100%;
  1380. .video-poster {
  1381. cursor: pointer;
  1382. transition: transform 0.2s ease;
  1383. &:active {
  1384. transform: scale(0.98);
  1385. }
  1386. }
  1387. .video-overlay {
  1388. position: absolute;
  1389. top: 0;
  1390. left: 0;
  1391. right: 0;
  1392. bottom: 0;
  1393. background: rgba(0, 0, 0, 0.4);
  1394. z-index: 10;
  1395. cursor: pointer;
  1396. display: flex;
  1397. justify-content: center;
  1398. align-items: center;
  1399. .play-button-large {
  1400. width: 120rpx;
  1401. height: 120rpx;
  1402. background: transparent;
  1403. border-radius: 50%;
  1404. display: flex;
  1405. justify-content: center;
  1406. align-items: center;
  1407. backdrop-filter: blur(10rpx);
  1408. transition: all 0.3s ease;
  1409. box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.3);
  1410. &:active {
  1411. transform: scale(0.9);
  1412. background: rgba(255, 255, 255, 0.1);
  1413. }
  1414. .play-triangle {
  1415. width: 0;
  1416. height: 0;
  1417. border-left: 24rpx solid #fff;
  1418. border-top: 18rpx solid transparent;
  1419. border-bottom: 18rpx solid transparent;
  1420. margin-left: 8rpx;
  1421. transition: all 0.3s ease;
  1422. filter: drop-shadow(0 2rpx 4rpx rgba(0, 0, 0, 0.3));
  1423. }
  1424. }
  1425. &:hover .play-button-large {
  1426. transform: scale(1.1);
  1427. box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.4);
  1428. .play-triangle {
  1429. border-left-color: #ff8917;
  1430. }
  1431. }
  1432. }
  1433. }
  1434. }
  1435. .goods-list {
  1436. // flex: 1;
  1437. display: flex;
  1438. position: relative;
  1439. height: calc(110vh - 320rpx - 160rpx - env(safe-area-inset-bottom)); /* 减去banner、底部栏和绿色提示条的高度 */
  1440. margin-top: -10rpx;
  1441. z-index: 2;
  1442. border-radius: 20rpx 20rpx 0 0;
  1443. overflow: hidden;
  1444. padding: 30rpx;
  1445. box-shadow: 0 -4rpx 8rpx rgba(0, 0, 0, 0.05);
  1446. background: linear-gradient(to bottom, #fff7e8, 20%,#ffffff);
  1447. .category-nav {
  1448. width: 20%;
  1449. background: #ffffff;
  1450. height: 100%;
  1451. border-right: 1rpx solid rgba(255, 126, 14, 0.1);
  1452. margin: 1rpx;
  1453. border-radius: 20rpx 0 0 0;
  1454. margin-right: 20rpx;
  1455. overflow-y: auto;
  1456. scrollbar-width: none; /* Firefox */
  1457. -ms-overflow-style: none; /* IE and Edge */
  1458. &::-webkit-scrollbar {
  1459. width: 0 !important;
  1460. display: none; /* Chrome, Safari, Opera */
  1461. }
  1462. .category-item {
  1463. position: relative;
  1464. padding: 28rpx 20rpx;
  1465. text-align: center;
  1466. font-family: PingFang SC;
  1467. font-weight: 600;
  1468. font-size: 15px;
  1469. line-height: 100%;
  1470. letter-spacing: 0px;
  1471. color: #666;
  1472. .category-dot {
  1473. position: absolute;
  1474. top: 15rpx;
  1475. right: 15rpx;
  1476. min-width: 32rpx;
  1477. height: 32rpx;
  1478. padding: 0 6rpx;
  1479. background: #ff7a0e;
  1480. border-radius: 16rpx;
  1481. color: #fff;
  1482. font-size: 20rpx;
  1483. text-align: center;
  1484. line-height: 32rpx;
  1485. box-sizing: border-box;
  1486. }
  1487. &.active {
  1488. color: #ff7a0e;
  1489. font-weight: bold;
  1490. background: #fff7e8;
  1491. position: relative;
  1492. &::before {
  1493. content: '';
  1494. position: absolute;
  1495. left: 0;
  1496. top: 50%;
  1497. transform: translateY(-50%);
  1498. width: 6rpx;
  1499. height: 36rpx;
  1500. background: #ff7a0e;
  1501. border-radius: 3rpx;
  1502. }
  1503. }
  1504. }
  1505. }
  1506. .goods-content {
  1507. flex: 1;
  1508. height: 100%;
  1509. padding: 0 0 180rpx 0; /* 添加底部padding,为固定底部栏预留空间 */
  1510. background: #ffffff;
  1511. width: 70%;
  1512. margin: 1rpx;
  1513. margin-left: 0;
  1514. border-radius: 0 20rpx 0 0;
  1515. overflow-y: auto;
  1516. box-sizing: border-box;
  1517. scrollbar-width: none; /* Firefox */
  1518. -ms-overflow-style: none; /* IE and Edge */
  1519. &::-webkit-scrollbar {
  1520. width: 0 !important;
  1521. display: none; /* Chrome, Safari, Opera */
  1522. }
  1523. }
  1524. }
  1525. .goods-item {
  1526. display: flex;
  1527. align-items: flex-start;
  1528. padding: 30rpx 0;
  1529. border-bottom: 1rpx solid #f5f5f5;
  1530. .goods-item-img {
  1531. width: 180rpx;
  1532. height: 180rpx;
  1533. border-radius: 24rpx;
  1534. background: #f8f8f8;
  1535. margin-right: 28rpx;
  1536. object-fit: contain;
  1537. flex-shrink: 0;
  1538. }
  1539. .goods-info-wrap {
  1540. flex: 1;
  1541. display: flex;
  1542. flex-direction: column;
  1543. justify-content: center;
  1544. min-width: 0;
  1545. }
  1546. .goods-header {
  1547. display: flex;
  1548. justify-content: space-between;
  1549. align-items: center;
  1550. margin-bottom: 10rpx;
  1551. }
  1552. .goods-name {
  1553. font-family: PingFang SC;
  1554. font-weight: 500;
  1555. font-size: 14px;
  1556. line-height: 140%;
  1557. letter-spacing: 0%;
  1558. vertical-align: middle;
  1559. color: #333;
  1560. font-weight: bold;
  1561. flex-shrink: 1;
  1562. flex-grow: 1;
  1563. }
  1564. .brand-check-placeholder {
  1565. flex-shrink: 0;
  1566. margin-left: 10rpx;
  1567. }
  1568. .brand-check {
  1569. display: flex;
  1570. flex-direction: row;
  1571. align-items: center;
  1572. justify-content: center;
  1573. border: 1px solid #f8a01d;
  1574. border-radius: 8rpx;
  1575. color: #ff7a0e;
  1576. font-family: PingFang SC;
  1577. font-weight: 400;
  1578. font-size: 12px;
  1579. padding: 4rpx 10rpx;
  1580. line-height: 1;
  1581. white-space: nowrap;
  1582. text {
  1583. margin-right: 4rpx;
  1584. }
  1585. }
  1586. .goods-desc {
  1587. font-size: 20rpx;
  1588. color: #999;
  1589. display: block;
  1590. // margin-bottom: 20rpx;
  1591. white-space: nowrap;
  1592. overflow: hidden;
  1593. text-overflow: ellipsis;
  1594. }
  1595. .goods-info {
  1596. display: flex;
  1597. justify-content: space-between;
  1598. align-items: center;
  1599. flex-wrap: nowrap;
  1600. gap: 5rpx;
  1601. margin-top: 10rpx;
  1602. }
  1603. .price-info {
  1604. display: flex;
  1605. align-items: baseline;
  1606. white-space: nowrap;
  1607. flex-shrink: 0;
  1608. .price-symbol {
  1609. font-size: 24rpx;
  1610. color: #ff7a0e;
  1611. }
  1612. .price-value {
  1613. font-size: 36rpx;
  1614. color: #ff7a0e;
  1615. font-weight: bold;
  1616. margin: 0 0rpx;
  1617. white-space: nowrap;
  1618. }
  1619. .price-unit {
  1620. font-size: 24rpx;
  1621. color: #999;
  1622. white-space: nowrap;
  1623. }
  1624. }
  1625. .quantity-control {
  1626. display: flex;
  1627. align-items: center;
  1628. flex-shrink: 0;
  1629. white-space: nowrap;
  1630. button {
  1631. width: 60rpx;
  1632. height: 60rpx;
  1633. padding: 0;
  1634. margin: 0;
  1635. display: flex;
  1636. align-items: center;
  1637. justify-content: center;
  1638. font-size: 28rpx;
  1639. color: #666;
  1640. background: #ffffff;
  1641. border: none;
  1642. border-radius: 50%;
  1643. &::after {
  1644. border: none;
  1645. }
  1646. &:active {
  1647. opacity: 0.8;
  1648. }
  1649. }
  1650. .quantity {
  1651. width: 50rpx;
  1652. text-align: center;
  1653. font-size: 32rpx;
  1654. color: #333;
  1655. }
  1656. }
  1657. .rules-brand-row {
  1658. display: flex;
  1659. align-items: center;
  1660. margin-top: 20rpx;
  1661. gap: 16rpx;
  1662. .rules-link {
  1663. margin-top: 0;
  1664. .rules {
  1665. display: inline-flex;
  1666. align-items: center;
  1667. font-family: PingFang SC;
  1668. font-weight: 400;
  1669. font-size: 12px;
  1670. color: #666;
  1671. white-space: nowrap;
  1672. }
  1673. }
  1674. .brand-check-placeholder {
  1675. margin-left: 12rpx;
  1676. }
  1677. }
  1678. }
  1679. .other-unrecycle-card {
  1680. display: flex;
  1681. align-items: center;
  1682. background: #fff;
  1683. border-radius: 24rpx;
  1684. box-shadow: 0 4rpx 24rpx rgba(0,0,0,0.06);
  1685. padding: 30rpx 30rpx 30rpx 30rpx;
  1686. margin: 30rpx 0 0 0;
  1687. }
  1688. .other-unrecycle-img {
  1689. width: 120rpx;
  1690. height: 120rpx;
  1691. border-radius: 24rpx;
  1692. background: #f8f8f8;
  1693. margin-right: 28rpx;
  1694. object-fit: contain;
  1695. flex-shrink: 0;
  1696. }
  1697. .other-unrecycle-info {
  1698. flex: 1;
  1699. display: flex;
  1700. flex-direction: column;
  1701. justify-content: center;
  1702. min-width: 0;
  1703. overflow: hidden;
  1704. }
  1705. .other-unrecycle-title {
  1706. font-size: 30rpx;
  1707. color: #222;
  1708. font-weight: bold;
  1709. margin-bottom: 8rpx;
  1710. white-space: nowrap;
  1711. overflow: hidden;
  1712. text-overflow: ellipsis;
  1713. }
  1714. .other-unrecycle-desc {
  1715. font-size: 24rpx;
  1716. color: #999;
  1717. margin-bottom: 12rpx;
  1718. text-overflow: ellipsis;
  1719. overflow: hidden;
  1720. white-space: nowrap;
  1721. }
  1722. .other-unrecycle-price-row {
  1723. display: flex;
  1724. align-items: center;
  1725. white-space: nowrap;
  1726. }
  1727. .other-unrecycle-price {
  1728. font-size: 28rpx;
  1729. color: #ff9c00;
  1730. font-weight: bold;
  1731. white-space: nowrap;
  1732. }
  1733. .other-unrecycle-btn {
  1734. width: 60rpx;
  1735. height: 60rpx;
  1736. margin-left: 24rpx;
  1737. border-radius: 50%;
  1738. background: #fff;
  1739. color: #666;
  1740. font-size: 36rpx;
  1741. border: none;
  1742. display: flex;
  1743. align-items: center;
  1744. justify-content: center;
  1745. }
  1746. .fixed-bottom-wrap {
  1747. position: fixed;
  1748. left: 0;
  1749. right: 0;
  1750. bottom: calc(v-bind('tabbarHeight + "px"') + env(safe-area-inset-bottom));
  1751. width: 100vw;
  1752. z-index: 100;
  1753. background: transparent;
  1754. box-sizing: border-box;
  1755. pointer-events: auto;
  1756. }
  1757. .bottom-bar-divider {
  1758. width: 100%;
  1759. height: 1px;
  1760. background: #f0f0f0;
  1761. position: absolute;
  1762. left: 0;
  1763. bottom: 0;
  1764. z-index: 1;
  1765. }
  1766. .green-tip-bar {
  1767. width: 100%;
  1768. background: #eaffea;
  1769. color: #13ac47;
  1770. font-size: 20rpx;
  1771. padding: 8rpx 30rpx;
  1772. box-sizing: border-box;
  1773. text-align: center;
  1774. display: flex;
  1775. align-items: center;
  1776. justify-content: center;
  1777. line-height: 1.4;
  1778. min-height: 40rpx;
  1779. .tip-highlight {
  1780. color: #ff9c00;
  1781. font-weight: bold;
  1782. font-size: 20rpx;
  1783. }
  1784. }
  1785. .bottom-bar {
  1786. width: 100%;
  1787. background-color: #fff;
  1788. display: flex;
  1789. align-items: center;
  1790. justify-content: space-between;
  1791. padding: 0 30rpx;
  1792. box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
  1793. height: 120rpx;
  1794. border-top-left-radius: 0;
  1795. border-top-right-radius: 0;
  1796. border-bottom-left-radius: env(safe-area-inset-bottom);
  1797. border-bottom-right-radius: env(safe-area-inset-bottom);
  1798. .bottom-left {
  1799. // flex: 1;
  1800. display: flex;
  1801. flex-direction: column;
  1802. justify-content: center;
  1803. .summary-row {
  1804. display: flex;
  1805. align-items: center;
  1806. font-size: 26rpx;
  1807. color: #333;
  1808. .summary-label {
  1809. color: #333;
  1810. }
  1811. .summary-count {
  1812. color: #ff9c00;
  1813. font-weight: bold;
  1814. font-size: 28rpx;
  1815. }
  1816. }
  1817. .amount-row {
  1818. display: flex;
  1819. align-items: center;
  1820. margin-top: 4rpx;
  1821. .amount {
  1822. color: #ff9c00;
  1823. font-size: 44rpx;
  1824. font-weight: bold;
  1825. vertical-align: middle;
  1826. }
  1827. }
  1828. }
  1829. .submit-btn {
  1830. width: 300rpx;
  1831. height: 88rpx;
  1832. background: linear-gradient(to right, #ffd01e, #ff8917);
  1833. border-radius: 44rpx;
  1834. color: #fff;
  1835. font-size: 32rpx;
  1836. font-weight: bold;
  1837. display: flex;
  1838. align-items: center;
  1839. justify-content: center;
  1840. border: none;
  1841. // margin-left: 0rpx;
  1842. box-shadow: 0 4rpx 16rpx rgba(255, 156, 0, 0.08);
  1843. &::after {
  1844. border: none;
  1845. }
  1846. &:active {
  1847. opacity: 0.9;
  1848. }
  1849. }
  1850. }
  1851. .detail-popup-mask {
  1852. position: fixed;
  1853. left: 0;
  1854. right: 0;
  1855. top: 0;
  1856. bottom: calc(90rpx + env(safe-area-inset-bottom)); /* tabbar高度+安全区 */
  1857. background: rgba(0,0,0,0.35);
  1858. z-index: 8;
  1859. display: flex;
  1860. align-items: flex-end;
  1861. justify-content: center;
  1862. }
  1863. .detail-popup {
  1864. width: 100vw;
  1865. max-width: none;
  1866. background: #fff;
  1867. border-radius: 48rpx 48rpx 0 0;
  1868. box-shadow: 0 8rpx 48rpx rgba(0,0,0,0.18);
  1869. display: flex;
  1870. flex-direction: column;
  1871. align-items: stretch;
  1872. position: relative;
  1873. padding: 0;
  1874. overflow: hidden;
  1875. min-height: 520rpx;
  1876. max-height: 80vh;
  1877. bottom: 0;
  1878. }
  1879. .detail-popup-close {
  1880. position: absolute;
  1881. right: 36rpx;
  1882. top: 36rpx;
  1883. font-size: 36rpx;
  1884. color: #bbb;
  1885. z-index: 2;
  1886. }
  1887. .popup-green-tip {
  1888. border-radius: 48rpx 48rpx 0 0;
  1889. font-size: 20rpx;
  1890. padding: 24rpx 30rpx 0 30rpx;
  1891. background: #eaffea;
  1892. color: #13ac47;
  1893. text-align: left;
  1894. }
  1895. .panel-header {
  1896. display: flex;
  1897. align-items: center;
  1898. justify-content: center;
  1899. font-size: 32rpx;
  1900. font-weight: bold;
  1901. padding: 40rpx 36rpx 0 36rpx;
  1902. background: #fff;
  1903. position: relative;
  1904. }
  1905. .panel-title {
  1906. font-size: 32rpx;
  1907. color: #222;
  1908. font-weight: bold;
  1909. text-align: center;
  1910. flex: 1;
  1911. }
  1912. .popup-panel-list {
  1913. flex: 1;
  1914. overflow-y: auto;
  1915. max-height: 36vh;
  1916. padding: 0 24rpx;
  1917. scrollbar-width: none; /* Firefox */
  1918. -ms-overflow-style: none; /* IE and Edge */
  1919. &::-webkit-scrollbar {
  1920. width: 0 !important;
  1921. display: none; /* Chrome, Safari, Opera */
  1922. }
  1923. }
  1924. .panel-item {
  1925. display: flex;
  1926. align-items: center;
  1927. justify-content: flex-start;
  1928. padding: 24rpx 0;
  1929. border-bottom: 1px solid #f0f0f0;
  1930. }
  1931. .panel-item-img {
  1932. width: 100rpx;
  1933. height: 100rpx;
  1934. margin-right: 20rpx;
  1935. border-radius: 16rpx;
  1936. background: #f8f8f8;
  1937. }
  1938. .panel-item-info {
  1939. flex: 1;
  1940. display: flex;
  1941. flex-direction: column;
  1942. justify-content: center;
  1943. min-width: 0;
  1944. }
  1945. .panel-item-name {
  1946. font-size: 30rpx;
  1947. color: #222;
  1948. font-weight: bold;
  1949. margin-bottom: 4rpx;
  1950. text-overflow: ellipsis;
  1951. overflow: hidden;
  1952. white-space: nowrap;
  1953. }
  1954. .panel-item-desc {
  1955. font-size: 24rpx;
  1956. color: #999;
  1957. margin-bottom: 4rpx;
  1958. text-overflow: ellipsis;
  1959. overflow: hidden;
  1960. white-space: nowrap;
  1961. }
  1962. .panel-item-price {
  1963. font-size: 26rpx;
  1964. color: #ff9c00;
  1965. margin-top: 2rpx;
  1966. }
  1967. .panel-quantity-control {
  1968. display: flex;
  1969. align-items: center;
  1970. margin-left: 20rpx;
  1971. // margin-right: 30rpx;
  1972. flex-shrink: 0;
  1973. }
  1974. .panel-quantity-control button {
  1975. width: 48rpx;
  1976. height: 48rpx;
  1977. padding: 0;
  1978. margin: 0 8rpx;
  1979. display: flex;
  1980. align-items: center;
  1981. justify-content: center;
  1982. font-size: 32rpx;
  1983. color: #666;
  1984. background: #ffffff;
  1985. border: none;
  1986. border-radius: 50%;
  1987. &::after {
  1988. border: none;
  1989. }
  1990. &:active {
  1991. opacity: 0.8;
  1992. }
  1993. }
  1994. .panel-quantity-control .quantity {
  1995. width: 30rpx;
  1996. text-align: center;
  1997. font-size: 28rpx;
  1998. color: #333;
  1999. }
  2000. .popup-bottom-bar {
  2001. width: 100%;
  2002. background-color: #fff;
  2003. display: flex;
  2004. align-items: center;
  2005. justify-content: space-between;
  2006. padding: 0 30rpx;
  2007. box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
  2008. height: 120rpx;
  2009. border-top: 1px solid #f0f0f0;
  2010. border-bottom-left-radius: 48rpx;
  2011. border-bottom-right-radius: 48rpx;
  2012. // padding-bottom: env(safe-area-inset-bottom);
  2013. .bottom-left {
  2014. display: flex;
  2015. flex-direction: column;
  2016. justify-content: center;
  2017. .summary-row {
  2018. display: flex;
  2019. align-items: center;
  2020. font-size: 26rpx;
  2021. color: #333;
  2022. .summary-label {
  2023. color: #333;
  2024. }
  2025. .summary-count {
  2026. color: #ff9c00;
  2027. font-weight: bold;
  2028. font-size: 28rpx;
  2029. }
  2030. }
  2031. .amount-row {
  2032. display: flex;
  2033. align-items: center;
  2034. margin-top: 4rpx;
  2035. .amount {
  2036. color: #ff9c00;
  2037. font-size: 44rpx;
  2038. font-weight: bold;
  2039. vertical-align: middle;
  2040. }
  2041. }
  2042. }
  2043. .submit-btn {
  2044. width: 300rpx;
  2045. height: 88rpx;
  2046. background: linear-gradient(to right, #ffd01e, #ff8917);
  2047. border-radius: 44rpx;
  2048. color: #fff;
  2049. font-size: 32rpx;
  2050. font-weight: bold;
  2051. display: flex;
  2052. align-items: center;
  2053. justify-content: center;
  2054. border: none;
  2055. box-shadow: 0 4rpx 16rpx rgba(255, 156, 0, 0.08);
  2056. &::after {
  2057. border: none;
  2058. }
  2059. &:active {
  2060. opacity: 0.9;
  2061. }
  2062. }
  2063. }
  2064. .brand-popup-mask {
  2065. position: fixed;
  2066. left: 0;
  2067. right: 0;
  2068. top: 0;
  2069. bottom: 0;
  2070. background: rgba(0,0,0,0.35);
  2071. z-index: 3000;
  2072. display: flex;
  2073. align-items: flex-end;
  2074. justify-content: center;
  2075. }
  2076. .brand-popup {
  2077. position: relative;
  2078. width: 100%;
  2079. max-width: 750px;
  2080. background: #fff;
  2081. border-radius: 32rpx 32rpx 0 0;
  2082. box-shadow: 0 -4rpx 24rpx rgba(0,0,0,0.08);
  2083. padding-bottom: 40rpx;
  2084. max-height: 90vh;
  2085. display: flex;
  2086. flex-direction: column;
  2087. overflow: hidden;
  2088. }
  2089. .brand-popup-header {
  2090. display: flex;
  2091. align-items: center;
  2092. justify-content: center;
  2093. padding: 32rpx 24rpx 0 24rpx;
  2094. font-size: 32rpx;
  2095. font-weight: bold;
  2096. position: relative;
  2097. }
  2098. .brand-popup-close {
  2099. position: absolute;
  2100. left: 24rpx;
  2101. font-size: 28rpx;
  2102. color: #888;
  2103. }
  2104. .brand-popup-title {
  2105. font-size: 32rpx;
  2106. color: #222;
  2107. font-weight: bold;
  2108. }
  2109. .brand-popup-search {
  2110. padding: 20rpx 24rpx 0 24rpx;
  2111. }
  2112. .brand-search-input {
  2113. width: 100%;
  2114. height: 60rpx;
  2115. border-radius: 30rpx;
  2116. background: #f5f5f5;
  2117. border: none;
  2118. padding-left: 40rpx;
  2119. font-size: 28rpx;
  2120. color: #888;
  2121. }
  2122. .brand-popup-list {
  2123. flex: 1;
  2124. overflow-y: auto;
  2125. max-height: 60vh;
  2126. padding: 0 24rpx;
  2127. scrollbar-width: none; /* Firefox */
  2128. -ms-overflow-style: none; /* IE and Edge */
  2129. &::-webkit-scrollbar {
  2130. width: 0 !important;
  2131. display: none; /* Chrome, Safari, Opera */
  2132. }
  2133. }
  2134. .brand-letter {
  2135. font-size: 28rpx;
  2136. color: #888;
  2137. margin: 24rpx 0 8rpx 0;
  2138. font-weight: bold;
  2139. }
  2140. .brand-item {
  2141. display: flex;
  2142. align-items: center;
  2143. padding: 16rpx 0;
  2144. border-bottom: 1px solid #f0f0f0;
  2145. }
  2146. .brand-logo {
  2147. width: 60rpx;
  2148. height: 60rpx;
  2149. margin-right: 20rpx;
  2150. border-radius: 8rpx;
  2151. background: #f8f8f8;
  2152. }
  2153. .brand-name {
  2154. font-size: 28rpx;
  2155. color: #222;
  2156. }
  2157. .brand-index-bar {
  2158. position: absolute;
  2159. right: 12rpx;
  2160. top: 120rpx;
  2161. width: 32rpx;
  2162. display: flex;
  2163. flex-direction: column;
  2164. align-items: center;
  2165. z-index: 10;
  2166. }
  2167. .brand-index-bar text {
  2168. font-size: 22rpx;
  2169. color: #bbb;
  2170. margin: 4rpx 0;
  2171. font-weight: bold;
  2172. &.active {
  2173. color: #ff9c00;
  2174. }
  2175. }
  2176. .rule-popup-mask {
  2177. position: fixed;
  2178. left: 0;
  2179. right: 0;
  2180. top: 0;
  2181. bottom: 0;
  2182. background: rgba(0,0,0,0.35);
  2183. z-index: 4000;
  2184. display: flex;
  2185. align-items: center;
  2186. justify-content: center;
  2187. }
  2188. .rule-popup {
  2189. width: 95vw;
  2190. max-width: 750rpx;
  2191. max-height: 85vh;
  2192. background: #fff;
  2193. border-radius: 48rpx;
  2194. box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.12);
  2195. display: flex;
  2196. flex-direction: column;
  2197. align-items: center;
  2198. position: relative;
  2199. padding-bottom: 40rpx;
  2200. }
  2201. .rule-popup-title {
  2202. font-size: 36rpx;
  2203. color: #222;
  2204. font-weight: bold;
  2205. text-align: center;
  2206. margin-top: 48rpx;
  2207. margin-bottom: 16rpx;
  2208. }
  2209. .rule-popup-content {
  2210. width: 100%;
  2211. max-height: 60vh;
  2212. min-height: 400rpx;
  2213. padding: 0 40rpx;
  2214. box-sizing: border-box;
  2215. overflow-y: auto;
  2216. scrollbar-width: none; /* Firefox */
  2217. -ms-overflow-style: none; /* IE and Edge */
  2218. &::-webkit-scrollbar {
  2219. width: 0 !important;
  2220. display: none; /* Chrome, Safari, Opera */
  2221. }
  2222. }
  2223. .rule-content-bottom-indicator {
  2224. height: 20rpx;
  2225. width: 100%;
  2226. }
  2227. .scroll-tip {
  2228. font-size: 24rpx;
  2229. color: #ff6b35;
  2230. text-align: center;
  2231. margin: 16rpx 0 8rpx 0;
  2232. animation: tipPulse 2s infinite;
  2233. }
  2234. @keyframes tipPulse {
  2235. 0%, 100% { opacity: 1; }
  2236. 50% { opacity: 0.6; }
  2237. }
  2238. .rule-popup-desc {
  2239. font-size: 26rpx;
  2240. color: #888;
  2241. text-align: center;
  2242. margin-bottom: 24rpx;
  2243. margin-top: 0;
  2244. }
  2245. .rule-popup-warning {
  2246. width: 100%;
  2247. border: 2rpx solid #ffb800;
  2248. color: #ffb800;
  2249. background: #fffbe6;
  2250. border-radius: 32rpx;
  2251. font-size: 28rpx;
  2252. text-align: center;
  2253. padding: 16rpx 0;
  2254. margin-bottom: 24rpx;
  2255. font-weight: bold;
  2256. }
  2257. .rule-popup-img {
  2258. display: block;
  2259. margin: 0 auto 24rpx auto;
  2260. max-width: 80%;
  2261. max-height: 300rpx;
  2262. border-radius: 16rpx;
  2263. object-fit: contain;
  2264. }
  2265. .rule-popup-btn {
  2266. width: 80%;
  2267. height: 88rpx;
  2268. background: linear-gradient(to right, #ffd01e, #ff8917);
  2269. border-radius: 44rpx;
  2270. color: #fff;
  2271. font-size: 32rpx;
  2272. font-weight: bold;
  2273. display: flex;
  2274. align-items: center;
  2275. justify-content: center;
  2276. border: none;
  2277. margin: 0 auto;
  2278. margin-top: 16rpx;
  2279. box-shadow: 0 4rpx 16rpx rgba(255, 156, 0, 0.08);
  2280. transition: all 0.3s ease;
  2281. &::after {
  2282. border: none;
  2283. }
  2284. &:active {
  2285. opacity: 0.9;
  2286. }
  2287. &.disabled {
  2288. background: #ccc;
  2289. color: #999;
  2290. box-shadow: none;
  2291. opacity: 0.6;
  2292. }
  2293. }
  2294. .rule-popup-close {
  2295. position: absolute;
  2296. right: 32rpx;
  2297. bottom: 32rpx;
  2298. width: 72rpx;
  2299. height: 72rpx;
  2300. background: #ff5a5f;
  2301. border-radius: 50%;
  2302. display: flex;
  2303. align-items: center;
  2304. justify-content: center;
  2305. z-index: 10;
  2306. box-shadow: 0 2rpx 8rpx rgba(255,90,95,0.12);
  2307. }
  2308. /* 预约上门取件弹窗样式 */
  2309. .pickup-confirm-mask {
  2310. position: fixed;
  2311. left: 0;
  2312. right: 0;
  2313. top: 0;
  2314. bottom: 0;
  2315. background: rgba(0,0,0,0.35);
  2316. z-index: 5000;
  2317. display: flex;
  2318. align-items: center;
  2319. justify-content: center;
  2320. }
  2321. .pickup-confirm-popup {
  2322. width: 90vw;
  2323. max-width: 600rpx;
  2324. background: #fff;
  2325. border-radius: 48rpx;
  2326. box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.12);
  2327. display: flex;
  2328. flex-direction: column;
  2329. align-items: center;
  2330. position: relative;
  2331. padding: 48rpx 36rpx 40rpx 36rpx;
  2332. }
  2333. .pickup-confirm-title {
  2334. font-size: 36rpx;
  2335. color: #222;
  2336. font-weight: bold;
  2337. text-align: center;
  2338. margin-bottom: 24rpx;
  2339. }
  2340. .pickup-confirm-content {
  2341. width: 100%;
  2342. font-size: 26rpx;
  2343. color: #333;
  2344. text-align: left;
  2345. line-height: 1.7;
  2346. margin-bottom: 36rpx;
  2347. .confirm-item {
  2348. margin-bottom: 24rpx;
  2349. &:last-child {
  2350. margin-bottom: 0;
  2351. }
  2352. }
  2353. .confirm-item-red {
  2354. color: red;
  2355. }
  2356. }
  2357. .pickup-confirm-btn-row {
  2358. width: 100%;
  2359. display: flex;
  2360. justify-content: space-between;
  2361. gap: 32rpx;
  2362. }
  2363. .pickup-confirm-btn {
  2364. flex: 1;
  2365. height: 88rpx;
  2366. border-radius: 44rpx;
  2367. font-size: 32rpx;
  2368. font-weight: bold;
  2369. display: flex;
  2370. align-items: center;
  2371. justify-content: center;
  2372. border: 2rpx solid #ffd01e;
  2373. background: #fff;
  2374. color: #ff9c00;
  2375. box-shadow: 0 4rpx 16rpx rgba(255, 156, 0, 0.08);
  2376. }
  2377. .pickup-confirm-btn:not(.agree) {
  2378. background: #fff0d2;
  2379. }
  2380. .pickup-confirm-btn.agree {
  2381. background: linear-gradient(to right, #ffd01e, #ff8917);
  2382. color: #fff;
  2383. border: none;
  2384. }
  2385. .uv-tabbar {
  2386. z-index: 1000;
  2387. }
  2388. .loading-more {
  2389. text-align: center;
  2390. color: #999;
  2391. padding: 20rpx 0;
  2392. font-size: 26rpx;
  2393. }
  2394. .brand-confirm-mask {
  2395. position: fixed;
  2396. left: 0;
  2397. right: 0;
  2398. top: 0;
  2399. bottom: 0;
  2400. background: rgba(0,0,0,0.25);
  2401. z-index: 5001;
  2402. display: flex;
  2403. align-items: center;
  2404. justify-content: center;
  2405. }
  2406. .brand-confirm-popup {
  2407. width: 70vw;
  2408. max-width: 270px;
  2409. background: #fff;
  2410. border-radius: 32rpx;
  2411. box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.12);
  2412. display: flex;
  2413. flex-direction: column;
  2414. align-items: center;
  2415. padding: 48rpx 20rpx 36rpx 20rpx;
  2416. position: relative;
  2417. }
  2418. .brand-confirm-title {
  2419. font-size: 36rpx;
  2420. color: #222;
  2421. font-weight: bold;
  2422. text-align: center;
  2423. margin-bottom: 24rpx;
  2424. }
  2425. .brand-confirm-logo-wrap {
  2426. width: 120rpx;
  2427. height: 120rpx;
  2428. background: #f8f8f8;
  2429. border-radius: 50%;
  2430. display: flex;
  2431. align-items: center;
  2432. justify-content: center;
  2433. margin-bottom: 18rpx;
  2434. }
  2435. .brand-confirm-logo {
  2436. width: 80rpx;
  2437. height: 80rpx;
  2438. border-radius: 50%;
  2439. }
  2440. .brand-confirm-name {
  2441. font-size: 28rpx;
  2442. color: #222;
  2443. font-weight: bold;
  2444. text-align: center;
  2445. margin-bottom: 16rpx;
  2446. }
  2447. .brand-confirm-desc {
  2448. font-size: 24rpx;
  2449. color: #999;
  2450. text-align: center;
  2451. margin-bottom: 32rpx;
  2452. line-height: 1.6;
  2453. }
  2454. .brand-confirm-btn-row {
  2455. width: 100%;
  2456. display: flex;
  2457. justify-content: space-between;
  2458. gap: 24rpx;
  2459. }
  2460. .brand-confirm-btn {
  2461. flex: 1;
  2462. height: 72rpx;
  2463. border-radius: 36rpx;
  2464. font-size: 28rpx;
  2465. font-weight: bold;
  2466. display: flex;
  2467. align-items: center;
  2468. justify-content: center;
  2469. border: none;
  2470. margin: 0 0;
  2471. }
  2472. .brand-confirm-btn.retry {
  2473. background: #fff;
  2474. color: #ff9c00;
  2475. border: 2rpx solid #ff9c00;
  2476. }
  2477. .brand-confirm-btn.confirm {
  2478. background: linear-gradient(to right, #ffd01e, #ff8917);
  2479. color: #fff;
  2480. border: none;
  2481. }
  2482. .price-info-popup-mask {
  2483. position: fixed;
  2484. left: 0;
  2485. right: 0;
  2486. top: 0;
  2487. bottom: 0;
  2488. background: rgba(0,0,0,0.4);
  2489. z-index: 6000;
  2490. display: flex;
  2491. align-items: center;
  2492. justify-content: center;
  2493. }
  2494. .price-info-popup {
  2495. width: 85vw;
  2496. max-width: 600rpx;
  2497. background: #fff;
  2498. border-radius: 24rpx;
  2499. box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.12);
  2500. display: flex;
  2501. flex-direction: column;
  2502. align-items: center;
  2503. position: relative;
  2504. padding: 40rpx 40rpx;
  2505. .price-info-popup-title {
  2506. font-size: 34rpx;
  2507. color: #333;
  2508. font-weight: bold;
  2509. text-align: center;
  2510. margin-bottom: 30rpx;
  2511. }
  2512. .price-info-popup-content {
  2513. width: 100%;
  2514. max-height: 50vh;
  2515. .price-info-section {
  2516. margin-bottom: 30rpx;
  2517. &:last-child {
  2518. margin-bottom: 0;
  2519. }
  2520. .price-info-heading {
  2521. font-size: 28rpx;
  2522. color: #333;
  2523. font-weight: 500;
  2524. margin-bottom: 15rpx;
  2525. }
  2526. .price-info-text {
  2527. font-size: 26rpx;
  2528. color: #666;
  2529. line-height: 1.6;
  2530. }
  2531. }
  2532. }
  2533. .price-info-popup-btn {
  2534. width: 100%;
  2535. height: 88rpx;
  2536. background: linear-gradient(to right, #ffd01e, #ff8917);
  2537. border-radius: 44rpx;
  2538. color: #fff;
  2539. font-size: 32rpx;
  2540. font-weight: bold;
  2541. display: flex;
  2542. align-items: center;
  2543. justify-content: center;
  2544. border: none;
  2545. margin-top: 40rpx;
  2546. box-shadow: 0 4rpx 16rpx rgba(255, 156, 0, 0.08);
  2547. &::after {
  2548. border: none;
  2549. }
  2550. &:active {
  2551. opacity: 0.9;
  2552. }
  2553. }
  2554. }
  2555. /* 减少数量时的品牌选择弹窗 */
  2556. .brand-reduce-popup-mask {
  2557. position: fixed;
  2558. left: 0;
  2559. right: 0;
  2560. top: 0;
  2561. bottom: 0;
  2562. background: rgba(0,0,0,0.35);
  2563. z-index: 5001;
  2564. display: flex;
  2565. align-items: center;
  2566. justify-content: center;
  2567. }
  2568. .brand-reduce-popup {
  2569. width: 70vw;
  2570. max-width: 270px;
  2571. background: #fff;
  2572. border-radius: 32rpx;
  2573. box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.12);
  2574. display: flex;
  2575. flex-direction: column;
  2576. align-items: center;
  2577. padding: 48rpx 20rpx 36rpx 20rpx;
  2578. position: relative;
  2579. }
  2580. .brand-reduce-popup-header {
  2581. display: flex;
  2582. align-items: center;
  2583. justify-content: center;
  2584. padding: 32rpx 24rpx 0 24rpx;
  2585. font-size: 32rpx;
  2586. font-weight: bold;
  2587. position: relative;
  2588. }
  2589. .brand-reduce-popup-close {
  2590. position: absolute;
  2591. left: 24rpx;
  2592. font-size: 28rpx;
  2593. color: #888;
  2594. }
  2595. .brand-reduce-popup-title {
  2596. font-size: 32rpx;
  2597. color: #222;
  2598. font-weight: bold;
  2599. }
  2600. .brand-reduce-popup-list {
  2601. flex: 1;
  2602. overflow-y: auto;
  2603. max-height: 60vh;
  2604. padding: 0 24rpx;
  2605. scrollbar-width: none; /* Firefox */
  2606. -ms-overflow-style: none; /* IE and Edge */
  2607. &::-webkit-scrollbar {
  2608. width: 0 !important;
  2609. display: none; /* Chrome, Safari, Opera */
  2610. }
  2611. }
  2612. .brand-item {
  2613. display: flex;
  2614. align-items: center;
  2615. padding: 16rpx 0;
  2616. border-bottom: 1px solid #f0f0f0;
  2617. }
  2618. .brand-logo {
  2619. width: 60rpx;
  2620. height: 60rpx;
  2621. margin-right: 20rpx;
  2622. border-radius: 8rpx;
  2623. background: #f8f8f8;
  2624. }
  2625. .brand-name {
  2626. font-size: 28rpx;
  2627. color: #222;
  2628. }
  2629. .rules-link {
  2630. min-width: 90rpx;
  2631. padding: 0 12rpx;
  2632. .rules {
  2633. font-size: 15px;
  2634. white-space: normal;
  2635. overflow: visible;
  2636. }
  2637. }
  2638. // ... existing code ...
  2639. .rules-brand-row {
  2640. display: flex;
  2641. align-items: center;
  2642. margin-top: 20rpx;
  2643. gap: 24rpx; // 增大间距
  2644. .rules-link {
  2645. margin-top: 0;
  2646. }
  2647. .brand-check-placeholder {
  2648. margin-left: 16rpx;
  2649. }
  2650. }
  2651. // ... existing code ...
  2652. .price-info {
  2653. display: flex;
  2654. align-items: baseline;
  2655. white-space: nowrap;
  2656. flex-shrink: 0;
  2657. gap: 12rpx; // 增大间距
  2658. .price-symbol {
  2659. font-size: 32rpx; // 增大符号
  2660. color: #ff7a0e;
  2661. }
  2662. .price-value {
  2663. font-size: 44rpx; // 增大数字
  2664. color: #ff7a0e;
  2665. font-weight: bold;
  2666. margin: 0 6rpx;
  2667. white-space: nowrap;
  2668. }
  2669. .price-unit {
  2670. font-size: 28rpx;
  2671. color: #999;
  2672. white-space: nowrap;
  2673. }
  2674. }
  2675. .quantity-control {
  2676. display: flex;
  2677. align-items: center;
  2678. flex-shrink: 0;
  2679. white-space: nowrap;
  2680. gap: 0rpx; // 增大间距
  2681. button {
  2682. width: 30rpx; // 增大按钮
  2683. height: 72rpx;
  2684. padding: 0;
  2685. margin: 0;
  2686. display: flex;
  2687. align-items: center;
  2688. justify-content: center;
  2689. font-size: 36rpx; // 增大符号
  2690. color: #666;
  2691. background: #ffffff;
  2692. border: none;
  2693. border-radius: 50%;
  2694. &::after {
  2695. border: none;
  2696. }
  2697. &:active {
  2698. opacity: 0.8;
  2699. }
  2700. }
  2701. .quantity {
  2702. width: 60rpx;
  2703. text-align: center;
  2704. font-size: 36rpx; // 增大数字
  2705. color: #333;
  2706. }
  2707. }
  2708. </style>