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

597 lines
14 KiB

2 weeks ago
2 weeks ago
2 weeks ago
  1. <template>
  2. <!-- 品牌索引弹窗 -->
  3. <view v-if="showBrandPopup" class="brand-popup-mask">
  4. <view class="brand-popup">
  5. <view class="brand-popup-header">
  6. <text class="brand-popup-close" @click="close">关闭</text>
  7. <text class="brand-popup-title">可回收的品牌</text>
  8. </view>
  9. <view class="brand-popup-search">
  10. <input class="brand-search-input" v-model="brandSearch" placeholder="请输入要查询的内容" @input="onBrandSearchInput" />
  11. </view>
  12. <scroll-view class="brand-popup-list" scroll-y :scroll-into-view="scrollToView">
  13. <!-- 热门品牌区域 -->
  14. <view v-if="hotBrandList.length > 0 && !brandSearch" class="hot-brands-section">
  15. <view class="hot-brands-title">热门品牌</view>
  16. <view class="hot-brands-grid">
  17. <view v-for="brand in hotBrandList" :key="brand.id" class="hot-brand-item" @click="openBrandConfirm(brand)">
  18. <image :src="brand.logo" class="hot-brand-logo" mode="aspectFit" />
  19. <text class="hot-brand-name">{{brand.name}}</text>
  20. </view>
  21. </view>
  22. </view>
  23. <view v-for="letter in brandIndexList" :key="letter" :id="'brand-letter-' + letter">
  24. <view class="brand-letter">{{letter}}</view>
  25. <view v-for="brand in filteredBrandList.filter(b => b.letter === letter)" :key="brand.name" class="brand-item" @click="openBrandConfirm(brand)">
  26. <image :src="brand.logo" class="brand-logo" mode="aspectFit" />
  27. <text class="brand-name">{{brand.name}}</text>
  28. </view>
  29. </view>
  30. </scroll-view>
  31. <view class="brand-index-bar">
  32. <text v-for="letter in brandIndexList" :key="letter" :class="{active: currentLetter === letter}" @click="scrollToLetter(letter)">{{letter}}</text>
  33. </view>
  34. </view>
  35. </view>
  36. <!-- 品牌确认弹窗 -->
  37. <view v-if="showBrandConfirm" class="brand-confirm-mask" @click.self="closeBrandConfirm">
  38. <view class="brand-confirm-popup">
  39. <view class="brand-confirm-title">品牌确认提示</view>
  40. <view class="brand-confirm-logo-wrap">
  41. <image :src="brandConfirmInfo.logo" class="brand-confirm-logo" mode="aspectFit" />
  42. </view>
  43. <view class="brand-confirm-name">{{ brandConfirmInfo.name }}</view>
  44. <view class="brand-confirm-desc">请确认所选品牌是否与实物品牌信息一致否则将无法进行回收</view>
  45. <view class="brand-confirm-btn-row">
  46. <button class="brand-confirm-btn retry" @click="closeBrandConfirm">重新选择</button>
  47. <button class="brand-confirm-btn confirm" @click="confirmBrand">确认一致</button>
  48. </view>
  49. </view>
  50. </view>
  51. <!-- 减少数量时的品牌选择弹窗 -->
  52. <view v-if="showBrandReducePopup" class="brand-reduce-popup-mask" @click.self="closeBrandReducePopup">
  53. <view class="brand-reduce-popup">
  54. <view class="brand-reduce-popup-header">
  55. <text class="brand-reduce-popup-close" @click="closeBrandReducePopup">关闭</text>
  56. <text class="brand-reduce-popup-title">选择要减少的品牌</text>
  57. </view>
  58. <scroll-view class="brand-reduce-popup-list" scroll-y>
  59. <view v-for="brand in reduceBrandList" :key="brand.brandId" class="brand-item" @click="selectReduceBrand(brand)">
  60. <image :src="brand.logo" class="brand-logo" mode="aspectFit" />
  61. <text class="brand-name">{{brand.name}}</text>
  62. </view>
  63. </scroll-view>
  64. </view>
  65. </view>
  66. <!-- 商品款式选择组件 -->
  67. <product-style-selector ref="styleSelector" @style-confirm="onStyleConfirm" @close="onStyleSelectorClose"></product-style-selector>
  68. </template>
  69. <script>
  70. import { pinyin } from '../../utils/pinyin.js'
  71. import ProductStyleSelector from './product-style-selector.vue'
  72. export default {
  73. name: 'BrandSelector',
  74. components: {
  75. ProductStyleSelector
  76. },
  77. data() {
  78. return {
  79. showBrandPopup: false,
  80. showBrandConfirm: false,
  81. showBrandReducePopup: false,
  82. brandConfirmInfo: {
  83. logo: '',
  84. name: '',
  85. id: ''
  86. },
  87. brandList: [],
  88. hotBrandList: [], // 热门品牌列表
  89. currentLetter: 'A',
  90. scrollToView: '',
  91. brandSearch: '',
  92. reduceBrandList: [],
  93. currentProductId: null,
  94. searchTimer: null
  95. }
  96. },
  97. computed: {
  98. filteredBrandList() {
  99. return this.brandList
  100. },
  101. // 动态生成品牌字母索引,只显示有品牌的字母
  102. brandIndexList() {
  103. const letters = new Set()
  104. let hasSharp = false
  105. this.brandList.forEach(b => {
  106. if (b.letter && /^[A-Z]$/.test(b.letter)) {
  107. letters.add(b.letter)
  108. } else {
  109. letters.add('#')
  110. hasSharp = true
  111. }
  112. })
  113. const arr = Array.from(letters).filter(l => l !== '#').sort()
  114. if (hasSharp) arr.push('#')
  115. return arr
  116. }
  117. },
  118. methods: {
  119. // 打开品牌选择弹窗
  120. open(productId) {
  121. if (!productId) {
  122. console.error('productId is required')
  123. return
  124. }
  125. this.currentProductId = productId
  126. this.getGoodsBrandList(productId)
  127. this.showBrandPopup = true
  128. },
  129. // 关闭品牌选择弹窗
  130. close() {
  131. this.showBrandPopup = false
  132. this.showBrandConfirm = false
  133. this.showBrandReducePopup = false
  134. // 清理搜索状态
  135. this.brandSearch = ''
  136. this.currentProductId = null
  137. // 清空品牌列表
  138. this.brandList = []
  139. this.hotBrandList = []
  140. if (this.searchTimer) {
  141. clearTimeout(this.searchTimer)
  142. this.searchTimer = null
  143. }
  144. this.$emit('close')
  145. },
  146. // 打开减少品牌选择弹窗
  147. openReducePopup(brandList) {
  148. this.reduceBrandList = brandList || []
  149. this.showBrandReducePopup = true
  150. },
  151. // 关闭减少品牌选择弹窗
  152. closeBrandReducePopup() {
  153. this.showBrandReducePopup = false
  154. this.reduceBrandList = []
  155. this.$emit('reduce-close')
  156. },
  157. // 选择要减少的品牌
  158. selectReduceBrand(brandInfo) {
  159. this.closeBrandReducePopup()
  160. this.$emit('reduce-select', brandInfo)
  161. },
  162. // 滚动到指定字母
  163. scrollToLetter(letter) {
  164. this.currentLetter = letter
  165. this.scrollToView = 'brand-letter-' + letter
  166. },
  167. // 打开品牌确认弹窗
  168. openBrandConfirm(brand) {
  169. this.brandConfirmInfo = {
  170. id: brand.id,
  171. logo: brand.logo,
  172. name: brand.name
  173. }
  174. // this.showBrandConfirm = true
  175. this.confirmBrand()
  176. },
  177. // 关闭品牌确认弹窗
  178. closeBrandConfirm() {
  179. this.showBrandConfirm = false
  180. },
  181. // 确认品牌
  182. confirmBrand() {
  183. this.showBrandConfirm = false
  184. // 打开商品款式选择弹窗,传递已有数量
  185. this.$emit('get-existing-quantities', this.brandConfirmInfo.id, (existingQuantities) => {
  186. this.$refs.styleSelector.open(this.brandConfirmInfo, this.currentProductId, existingQuantities)
  187. })
  188. },
  189. // 处理款式确认事件
  190. onStyleConfirm(data) {
  191. this.showBrandPopup = false
  192. this.$emit('brand-confirm', {
  193. brandInfo: data.brandInfo,
  194. selectedStyles: data.selectedStyles
  195. })
  196. },
  197. // 处理款式选择器关闭事件
  198. onStyleSelectorClose() {
  199. // 款式选择器关闭时的处理逻辑
  200. },
  201. // 获取商品品牌列表
  202. getGoodsBrandList(productId, searchName = '') {
  203. this.currentProductId = productId
  204. const params = { productId }
  205. if (searchName.trim()) {
  206. params.name = searchName.trim()
  207. }
  208. this.$api('getGoodsBrandList', params, res => {
  209. if (res && res.success && res.result && res.result.records) {
  210. const allBrands = res.result.records.map(item => {
  211. // 获取品牌名称的拼音首字母
  212. const firstChar = this.getPinyinFirstLetter(item.name)
  213. return {
  214. id: item.id,
  215. logo: item.image || '/static/brand/alexander.png',
  216. name: item.name,
  217. letter: firstChar,
  218. isPin: item.isPin,
  219. hot: item.hot || 0,
  220. ...item
  221. }
  222. })
  223. // 分离热门品牌和普通品牌
  224. this.hotBrandList = allBrands.filter(brand => brand.hot == 1)
  225. this.brandList = allBrands
  226. }
  227. })
  228. },
  229. // 获取中文拼音首字母
  230. getPinyinFirstLetter(str) {
  231. if (!str) return '#'
  232. const firstChar = str.charAt(0)
  233. // 遍历pinyin对象,查找包含该汉字的拼音
  234. for (let key in pinyin) {
  235. const chars = pinyin[key]
  236. if (chars && chars.indexOf(firstChar) !== -1) {
  237. return key.charAt(0).toUpperCase()
  238. }
  239. }
  240. // 英文首字母
  241. if (/^[A-Za-z]$/.test(firstChar)) {
  242. return firstChar.toUpperCase()
  243. }
  244. return '#'
  245. },
  246. // 品牌搜索输入事件处理
  247. onBrandSearchInput(e) {
  248. const searchValue = e.detail.value
  249. // 清除之前的定时器
  250. if (this.searchTimer) {
  251. clearTimeout(this.searchTimer)
  252. }
  253. // 设置防抖,500ms后执行搜索
  254. this.searchTimer = setTimeout(() => {
  255. if (this.currentProductId) {
  256. this.getGoodsBrandList(this.currentProductId, searchValue)
  257. }
  258. }, 500)
  259. }
  260. }
  261. }
  262. </script>
  263. <style lang="scss" scoped>
  264. .brand-popup-mask {
  265. position: fixed;
  266. left: 0;
  267. right: 0;
  268. top: 0;
  269. bottom: 0;
  270. background: rgba(0,0,0,0.35);
  271. z-index: 3000;
  272. display: flex;
  273. align-items: flex-end;
  274. justify-content: center;
  275. }
  276. .brand-popup {
  277. position: relative;
  278. width: 100%;
  279. max-width: 750px;
  280. background: #fff;
  281. border-radius: 32rpx 32rpx 0 0;
  282. box-shadow: 0 -4rpx 24rpx rgba(0,0,0,0.08);
  283. padding-bottom: 40rpx;
  284. height: 94vh;
  285. display: flex;
  286. flex-direction: column;
  287. overflow: hidden;
  288. }
  289. .brand-popup-header {
  290. display: flex;
  291. align-items: center;
  292. justify-content: center;
  293. padding: 32rpx 24rpx 0 24rpx;
  294. font-size: 32rpx;
  295. font-weight: bold;
  296. position: relative;
  297. }
  298. .brand-popup-close {
  299. position: absolute;
  300. left: 24rpx;
  301. font-size: 28rpx;
  302. color: #888;
  303. }
  304. .brand-popup-title {
  305. font-size: 32rpx;
  306. color: #222;
  307. font-weight: bold;
  308. }
  309. .brand-popup-search {
  310. padding: 20rpx 24rpx 0 24rpx;
  311. }
  312. .brand-search-input {
  313. height: 60rpx;
  314. border-radius: 30rpx;
  315. background: #f5f5f5;
  316. border: none;
  317. padding-left: 40rpx;
  318. font-size: 28rpx;
  319. color: #888;
  320. }
  321. .brand-popup-list {
  322. flex: 1;
  323. overflow-y: auto;
  324. max-height: calc(94vh - 160rpx);
  325. padding: 0 24rpx;
  326. scrollbar-width: none; /* Firefox */
  327. -ms-overflow-style: none; /* IE and Edge */
  328. &::-webkit-scrollbar {
  329. width: 0 !important;
  330. display: none; /* Chrome, Safari, Opera */
  331. }
  332. }
  333. .brand-letter {
  334. font-size: 28rpx;
  335. color: #888;
  336. background-color: #f8f8f8;
  337. margin: 24rpx 0 0 0;
  338. padding: 8rpx 8rpx;
  339. font-weight: bold;
  340. }
  341. .brand-item {
  342. display: flex;
  343. align-items: center;
  344. padding: 16rpx 0;
  345. border-bottom: 1px solid #f0f0f0;
  346. }
  347. .brand-logo {
  348. width: 60rpx;
  349. height: 60rpx;
  350. margin-right: 20rpx;
  351. border-radius: 8rpx;
  352. background: #f8f8f8;
  353. }
  354. .brand-name {
  355. font-size: 28rpx;
  356. color: #222;
  357. }
  358. .brand-index-bar {
  359. position: absolute;
  360. right: 12rpx;
  361. top: 120rpx;
  362. width: 32rpx;
  363. display: flex;
  364. flex-direction: column;
  365. align-items: center;
  366. z-index: 10;
  367. }
  368. .brand-index-bar text {
  369. font-size: 22rpx;
  370. color: #bbb;
  371. margin: 4rpx 0;
  372. font-weight: bold;
  373. &.active {
  374. color: #ff9c00;
  375. }
  376. }
  377. /* 热门品牌样式 */
  378. .hot-brands-section {
  379. padding: 20rpx 0;
  380. border-bottom: 1px solid #f0f0f0;
  381. }
  382. .hot-brands-title {
  383. font-size: 28rpx;
  384. color: #222;
  385. font-weight: bold;
  386. margin-bottom: 20rpx;
  387. }
  388. .hot-brands-grid {
  389. display: flex;
  390. flex-wrap: wrap;
  391. gap: 10rpx;
  392. }
  393. .hot-brand-item {
  394. width: calc((100% - 120rpx) / 3);
  395. display: flex;
  396. // flex-direction: column;
  397. align-items: center;
  398. padding: 2rpx 4rpx;
  399. border-radius: 40rpx;
  400. background: #f3f3f3;
  401. border: 1px solid #eee;
  402. &:active {
  403. background: #eee;
  404. }
  405. }
  406. .hot-brand-logo {
  407. width: 48rpx;
  408. height: 48rpx;
  409. border-radius: 8rpx;
  410. margin-bottom: 8rpx;
  411. background: #fff;
  412. flex-shrink: 0;
  413. }
  414. .hot-brand-name {
  415. font-size: 22rpx;
  416. color: #333;
  417. text-align: center;
  418. line-height: 1.2;
  419. max-width: 100%;
  420. overflow: hidden;
  421. text-overflow: ellipsis;
  422. white-space: nowrap;
  423. }
  424. .brand-confirm-mask {
  425. position: fixed;
  426. left: 0;
  427. right: 0;
  428. top: 0;
  429. bottom: 0;
  430. background: rgba(0,0,0,0.25);
  431. z-index: 5001;
  432. display: flex;
  433. align-items: center;
  434. justify-content: center;
  435. }
  436. .brand-confirm-popup {
  437. width: 70vw;
  438. max-width: 270px;
  439. background: #fff;
  440. border-radius: 32rpx;
  441. box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.12);
  442. display: flex;
  443. flex-direction: column;
  444. align-items: center;
  445. padding: 48rpx 20rpx 36rpx 20rpx;
  446. position: relative;
  447. }
  448. .brand-confirm-title {
  449. font-size: 36rpx;
  450. color: #222;
  451. font-weight: bold;
  452. text-align: center;
  453. margin-bottom: 24rpx;
  454. }
  455. .brand-confirm-logo-wrap {
  456. width: 120rpx;
  457. height: 120rpx;
  458. background: #f8f8f8;
  459. border-radius: 50%;
  460. display: flex;
  461. align-items: center;
  462. justify-content: center;
  463. margin-bottom: 18rpx;
  464. }
  465. .brand-confirm-logo {
  466. width: 80rpx;
  467. height: 80rpx;
  468. border-radius: 50%;
  469. }
  470. .brand-confirm-name {
  471. font-size: 28rpx;
  472. color: #222;
  473. font-weight: bold;
  474. text-align: center;
  475. margin-bottom: 16rpx;
  476. }
  477. .brand-confirm-desc {
  478. font-size: 24rpx;
  479. color: #999;
  480. text-align: center;
  481. margin-bottom: 32rpx;
  482. line-height: 1.6;
  483. }
  484. .brand-confirm-btn-row {
  485. width: 100%;
  486. display: flex;
  487. justify-content: space-between;
  488. gap: 24rpx;
  489. }
  490. .brand-confirm-btn {
  491. flex: 1;
  492. height: 72rpx;
  493. border-radius: 36rpx;
  494. font-size: 28rpx;
  495. font-weight: bold;
  496. display: flex;
  497. align-items: center;
  498. justify-content: center;
  499. border: none;
  500. margin: 0 0;
  501. }
  502. .brand-confirm-btn.retry {
  503. background: #fff;
  504. color: #ff9c00;
  505. border: 2rpx solid #ff9c00;
  506. }
  507. .brand-confirm-btn.confirm {
  508. background: linear-gradient(to right, #ffd01e, #ff8917);
  509. color: #fff;
  510. border: none;
  511. }
  512. /* 减少数量时的品牌选择弹窗 */
  513. .brand-reduce-popup-mask {
  514. position: fixed;
  515. left: 0;
  516. right: 0;
  517. top: 0;
  518. bottom: 0;
  519. background: rgba(0,0,0,0.35);
  520. z-index: 5001;
  521. display: flex;
  522. align-items: center;
  523. justify-content: center;
  524. }
  525. .brand-reduce-popup {
  526. width: 70vw;
  527. max-width: 270px;
  528. background: #fff;
  529. border-radius: 32rpx;
  530. box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.12);
  531. display: flex;
  532. flex-direction: column;
  533. align-items: center;
  534. padding: 48rpx 20rpx 36rpx 20rpx;
  535. position: relative;
  536. }
  537. .brand-reduce-popup-header {
  538. display: flex;
  539. align-items: center;
  540. justify-content: center;
  541. padding: 32rpx 24rpx 0 24rpx;
  542. font-size: 32rpx;
  543. font-weight: bold;
  544. position: relative;
  545. }
  546. .brand-reduce-popup-close {
  547. position: absolute;
  548. left: 24rpx;
  549. font-size: 28rpx;
  550. color: #888;
  551. }
  552. .brand-reduce-popup-title {
  553. font-size: 32rpx;
  554. color: #222;
  555. font-weight: bold;
  556. }
  557. .brand-reduce-popup-list {
  558. flex: 1;
  559. overflow-y: auto;
  560. max-height: 60vh;
  561. padding: 0 24rpx;
  562. scrollbar-width: none; /* Firefox */
  563. -ms-overflow-style: none; /* IE and Edge */
  564. &::-webkit-scrollbar {
  565. width: 0 !important;
  566. display: none; /* Chrome, Safari, Opera */
  567. }
  568. }
  569. </style>