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

2366 lines
70 KiB

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