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

2438 lines
72 KiB

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