爱简收旧衣按件回收前端代码仓库
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.

2426 lines
68 KiB

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