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

1322 lines
37 KiB

1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
  1. <template>
  2. <view class="inspect-container">
  3. <!-- 顶部导航栏 -->
  4. <view class="nav-bar">
  5. <view class="back" @tap="goBack">
  6. <uni-icons type="left" size="20" color="#222" />
  7. </view>
  8. <text class="nav-title">步骤一数量确认</text>
  9. <view class="nav-icons">
  10. <uni-icons type="scan" size="24" color="#222" />
  11. </view>
  12. </view>
  13. <view class="main-content">
  14. <!-- 左侧分类导航 -->
  15. <view class="category-nav">
  16. <view v-for="(cat, idx) in categories" :key="cat.title"
  17. :class="['category-item', { active: idx === currentCategory }]" @tap="switchCategory(idx)">
  18. <text>{{ cat.title }}</text>
  19. <view v-if="cat.badge" class="category-badge">{{ cat.badge }}</view>
  20. </view>
  21. </view>
  22. <!-- 右侧商品卡片区 -->
  23. <scroll-view class="goods-list" scroll-y @scrolltolower="loadMoreGoods">
  24. <view v-for="(item, idx) in currentGoods" :key="item.id" class="goods-card">
  25. <view class="goods-header">
  26. <image :src="item.image" class="goods-img" />
  27. <view class="goods-info">
  28. <view class="goods-title-row">
  29. <text class="goods-name">{{ item.name }}</text>
  30. <text class="goods-price">¥ {{ item.price }} <text class="goods-unit">/</text></text>
  31. </view>
  32. <text class="goods-desc">{{ item.desc }}</text>
  33. </view>
  34. </view>
  35. <view class="goods-row">
  36. <text class="row-label">合格数量</text>
  37. <view class="num-ctrl">
  38. <button class="num-btn" @tap="changeNum(item, 'qualified', -1)">-</button>
  39. <text class="num">{{ item.qualified }}</text>
  40. <button class="num-btn" @tap="changeNum(item, 'qualified', 1)">+</button>
  41. </view>
  42. </view>
  43. <view class="goods-row">
  44. <text class="row-label">总金额</text>
  45. <input class="amount-input" :value="getInspectPrice(item)" @input="updateInspectPrice(item, $event)" placeholder="请输入金额" />
  46. </view>
  47. </view>
  48. <view v-if="loadingMore" class="loading-more">加载中...</view>
  49. <view v-else-if="finished" class="loading-more">没有更多了</view>
  50. </scroll-view>
  51. </view>
  52. <!-- 底部操作按钮 -->
  53. <view class="footer-btns">
  54. <button class="btn-outline" @tap="goBack">返回订单详情</button>
  55. <button class="btn-main" @tap="goNext">下一步</button>
  56. </view>
  57. <!-- 品牌索引弹窗 -->
  58. <view v-if="showBrandPopup" class="brand-popup-mask">
  59. <view class="brand-popup">
  60. <view class="brand-popup-header">
  61. <text class="brand-popup-close" @click="closeBrandPopup">关闭</text>
  62. <text class="brand-popup-title">可回收的品牌</text>
  63. </view>
  64. <view class="brand-popup-search">
  65. <input class="brand-search-input" v-model="brandSearch" placeholder="请输入要查询的内容" @input="onBrandSearchInput" />
  66. </view>
  67. <scroll-view class="brand-popup-list" scroll-y>
  68. <view v-for="letter in brandIndexList" :key="letter" :id="'brand-letter-' + letter">
  69. <view class="brand-letter">{{letter}}</view>
  70. <view v-for="brand in brandList.filter(b => b.letter === letter)" :key="brand.name" class="brand-item" @click="openBrandConfirm(brand)">
  71. <image :src="brand.logo" class="brand-logo" mode="aspectFit" />
  72. <text class="brand-name">{{brand.name}}</text>
  73. </view>
  74. </view>
  75. </scroll-view>
  76. </view>
  77. </view>
  78. <!-- 品牌确认弹窗 -->
  79. <view v-if="showBrandConfirm" class="brand-confirm-mask" @click.self="closeBrandConfirm">
  80. <view class="brand-confirm-popup">
  81. <view class="brand-confirm-title">品牌确认提示</view>
  82. <view class="brand-confirm-logo-wrap">
  83. <image :src="brandConfirmInfo.logo" class="brand-confirm-logo" mode="aspectFit" />
  84. </view>
  85. <view class="brand-confirm-name">{{ brandConfirmInfo.name }}</view>
  86. <view class="brand-confirm-desc">请确认所选品牌是否与实物品牌信息一致否则将无法进行回收</view>
  87. <view class="brand-confirm-btn-row">
  88. <button class="brand-confirm-btn retry" @click="closeBrandConfirm">重新选择</button>
  89. <button class="brand-confirm-btn confirm" @click="confirmBrand">确认一致</button>
  90. </view>
  91. </view>
  92. </view>
  93. </view>
  94. </template>
  95. <script>
  96. export default {
  97. data() {
  98. return {
  99. statusBarHeight: 0,
  100. currentCategory: 0,
  101. orderId: '',
  102. order: null, // 订单数据
  103. currentGoods: [], // 当前显示的商品列表
  104. inspectResult: {}, // 质检结果对象,按照新的数据格式
  105. allProducts: {}, // { [categoryId]: [商品数组] }
  106. allProductsPage: {}, // { [categoryId]: 当前已加载页码 }
  107. allProductsTotal: {}, // { [categoryId]: 总数 }
  108. pageSize: 10,
  109. loadingMore: false,
  110. finished: false,
  111. // 品牌选择相关
  112. showBrandPopup: false,
  113. brandList: [],
  114. brandSearch: '',
  115. currentProductId: null,
  116. pendingBrandIndex: null,
  117. brandConfirmInfo: {},
  118. showBrandConfirm: false,
  119. searchTimer: null,
  120. brandIndexList: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
  121. isPresetDataSync: false, // 标记是否为预置数据同步
  122. }
  123. },
  124. computed: {
  125. categories() {
  126. const list = getApp().globalData.pricePreviewList || []
  127. console.log('categories计算 - pricePreviewList:', list.length)
  128. // 显示所有分类,不再根据订单数据筛选
  129. const allCategories = list.filter(item => item.pid === '0').sort((a, b) => a.sort - b.sort)
  130. console.log('categories计算 - allCategories:', allCategories.length)
  131. // 为每个分类计算数量
  132. const categoriesWithCount = allCategories.map(category => {
  133. const count = this.getCategoryItemCountDirect(category.id)
  134. return {
  135. ...category,
  136. badge: count > 0 ? count : null
  137. }
  138. })
  139. // 新增不可回收和质量问题分类
  140. const extra = [
  141. { id: 'unrecyclable', title: '不可回收', badge: this.getUnrecyclableCount() },
  142. { id: 'quality_issue', title: '质量问题', badge: this.getQualityIssueCount() }
  143. ]
  144. const result = [...categoriesWithCount, ...extra]
  145. console.log('categories计算 - 最终结果:', result.map(c => ({ id: c.id, title: c.title, badge: c.badge })))
  146. return result
  147. }
  148. },
  149. methods: {
  150. initInspectResult() {
  151. // 初始化一个空的质检结果结构
  152. this.inspectResult = {
  153. id: this.order ? this.order.id : '',
  154. list: []
  155. }
  156. // 如果有订单数据,基于订单创建质检结果结构
  157. if (this.order && this.order.commonOrderList) {
  158. const groupedGoods = {}
  159. this.order.commonOrderList.forEach(item => {
  160. if (!groupedGoods[item.id]) {
  161. groupedGoods[item.id] = {
  162. id: item.id,
  163. price: '',
  164. qualifiedNum: item.num || 0, // 初始化为订单中的数量
  165. noQualifiedNum: 0,
  166. unrecyclable: 0,
  167. commonOrderList: []
  168. }
  169. }
  170. // 每个商品只需要一个质检项
  171. groupedGoods[item.id].commonOrderList.push({
  172. id: item.id,
  173. testingInstructions: '',
  174. testingImages: '',
  175. testingStatus: 0 // 初始化为合格状态
  176. })
  177. })
  178. this.inspectResult.list = Object.values(groupedGoods)
  179. console.log('初始化inspectResult:', JSON.stringify(this.inspectResult, null, 2))
  180. }
  181. },
  182. // 获取分类商品数量(直接方法,避免递归)
  183. getCategoryItemCountDirect(categoryId) {
  184. if (categoryId === 'unrecyclable') {
  185. return this.getUnrecyclableCount()
  186. }
  187. if (categoryId === 'quality_issue') {
  188. return this.getQualityIssueCount()
  189. }
  190. // 对于普通分类,从inspectResult中获取该分类下所有商品的合格数量总和
  191. let totalCount = 0
  192. if (this.inspectResult.list && this.inspectResult.list.length > 0) {
  193. // 遍历inspectResult中的所有商品
  194. this.inspectResult.list.forEach(inspectItem => {
  195. // 跳过特殊分类
  196. if (inspectItem.id === 'unrecyclable' || inspectItem.id === 'quality_issue') {
  197. return
  198. }
  199. // 通过商品ID查找对应的订单项,获取分类信息
  200. const orderItem = this.order?.commonOrderList?.find(item => item.id == inspectItem.id)
  201. if (orderItem && orderItem.shopClass === categoryId) {
  202. // 累加该商品的合格数量
  203. totalCount += inspectItem.qualifiedNum || 0
  204. }
  205. })
  206. }
  207. return totalCount
  208. },
  209. // 获取当前分类ID(避免递归)
  210. getCurrentCategoryId() {
  211. const list = getApp().globalData.pricePreviewList || []
  212. const allCategories = list.filter(item => item.pid === '0').sort((a, b) => a.sort - b.sort)
  213. const extra = [
  214. { id: 'unrecyclable', title: '不可回收' },
  215. { id: 'quality_issue', title: '质量问题' }
  216. ]
  217. const allCategoriesWithExtra = [...allCategories, ...extra]
  218. return allCategoriesWithExtra[this.currentCategory]?.id
  219. },
  220. // 获取分类商品数量(保持原有方法名兼容性)
  221. getCategoryItemCount(categoryId) {
  222. return this.getCategoryItemCountDirect(categoryId)
  223. },
  224. // 获取不可回收数量
  225. getUnrecyclableCount() {
  226. // 从inspectResult中获取不可回收的数量
  227. const unrecyclableItem = this.inspectResult.list?.find(item => item.id === 'unrecyclable')
  228. return unrecyclableItem ? unrecyclableItem.unrecyclable : 0
  229. },
  230. // 获取质量问题数量
  231. getQualityIssueCount() {
  232. // 从inspectResult中获取质量问题的数量
  233. const qualityIssueItem = this.inspectResult.list?.find(item => item.id === 'quality_issue')
  234. return qualityIssueItem ? qualityIssueItem.noQualifiedNum : 0
  235. },
  236. fetchGoodsList(categoryId, page = 1, callback) {
  237. this.$api('getClassGoodsList', {
  238. classId: categoryId,
  239. pageNo: page,
  240. pageSize: this.pageSize
  241. }, res => {
  242. if (res.code === 200 && res.result && Array.isArray(res.result.records)) {
  243. const oldList = this.allProducts[categoryId] || []
  244. const newList = page === 1 ? res.result.records : oldList.concat(res.result.records)
  245. this.$set(this.allProducts, categoryId, newList)
  246. this.$set(this.allProductsPage, categoryId, page)
  247. this.$set(this.allProductsTotal, categoryId, res.result.total)
  248. this.updateCurrentGoods()
  249. }
  250. if (callback) callback()
  251. })
  252. },
  253. updateCurrentGoods() {
  254. const currentCategoryId = this.categories[this.currentCategory]?.id
  255. // 不可回收分类内容
  256. if (currentCategoryId === 'unrecyclable') {
  257. this.currentGoods = [{
  258. id: 'unrecyclable-1',
  259. image: '/static/回收/衣物.png',
  260. name: '不可回收品类',
  261. price: '—',
  262. desc: '允许脏破烂,160码以上',
  263. qualified: 0,
  264. amount: '',
  265. originalNum: 0 // 不设置最大数量限制
  266. }]
  267. return
  268. }
  269. // 质量问题分类内容
  270. if (currentCategoryId === 'quality_issue') {
  271. this.currentGoods = [{
  272. id: 'quality-issue-1',
  273. image: '/static/回收/衣物.png',
  274. name: '质量问题品类',
  275. price: '—',
  276. desc: '存在质量问题,无法正常回收',
  277. qualified: 0,
  278. amount: '',
  279. originalNum: 0 // 不设置最大数量限制
  280. }]
  281. return
  282. }
  283. // 从API获取的商品数据
  284. const categoryGoods = this.allProducts[currentCategoryId] || []
  285. // 将API商品数据转换为质检页面格式
  286. const goodsList = categoryGoods.map((item, index) => {
  287. // 从订单数据中查找对应的商品,获取预置数量
  288. const orderItem = this.getOrderItemByProductId(item.id)
  289. const presetQuantity = orderItem ? (orderItem.num || 0) : 0
  290. return {
  291. id: item.id,
  292. image: item.image || '/static/回收/衣物.png',
  293. name: item.name,
  294. price: item.price || 0,
  295. desc: item.service || '允许脏破烂,160码以上',
  296. qualified: presetQuantity, // 使用订单中的预置数量
  297. amount: orderItem ? (orderItem.estimatedPrice || '') : '',
  298. originalNum: presetQuantity, // 设置原始数量
  299. estimatedPrice: orderItem ? orderItem.estimatedPrice : 0,
  300. originalId: item.id,
  301. isPin: item.isPin || 'N', // 添加品牌标识
  302. orderItem: orderItem // 保存订单项引用,方便后续使用
  303. }
  304. })
  305. this.currentGoods = goodsList
  306. },
  307. // 根据商品ID从订单数据中查找对应商品
  308. getOrderItemByProductId(productId) {
  309. if (!this.order || !this.order.commonOrderList) {
  310. return null
  311. }
  312. // 先通过商品ID直接匹配
  313. let orderItem = this.order.commonOrderList.find(item => item.id == productId)
  314. // 如果没找到,尝试通过shopId匹配
  315. if (!orderItem) {
  316. orderItem = this.order.commonOrderList.find(item => item.shopId == productId)
  317. }
  318. return orderItem
  319. },
  320. goBack() {
  321. uni.navigateBack()
  322. },
  323. goNext() {
  324. // 检测是否所有商品都已完成质检和填写价格
  325. const validationResult = this.validateInspectData()
  326. if (!validationResult.isValid) {
  327. uni.showToast({
  328. title: validationResult.message,
  329. icon: 'none',
  330. duration: 2000
  331. })
  332. return
  333. }
  334. // 构造传递给步骤二的完整数据
  335. const resultData = {
  336. inspectResult: this.inspectResult,
  337. order: this.order // 同时传递订单信息
  338. }
  339. const resultDataStr = encodeURIComponent(JSON.stringify(resultData))
  340. uni.navigateTo({
  341. url: `/pages/manager/inspect-result?resultData=${resultDataStr}`
  342. })
  343. },
  344. validateInspectData() {
  345. if (!this.inspectResult.list || this.inspectResult.list.length === 0) {
  346. return {
  347. isValid: false,
  348. message: '没有质检数据'
  349. }
  350. }
  351. for (const item of this.inspectResult.list) {
  352. // 跳过不可回收和质量问题分类的检查
  353. if (item.id === 'unrecyclable' || item.id === 'quality_issue') {
  354. continue
  355. }
  356. // 获取商品信息用于显示错误消息
  357. const orderItem = this.order.commonOrderList.find(orderGoods => orderGoods.id == item.id)
  358. const brandName = orderItem ? (orderItem.title+' '+orderItem.pinName || orderItem.title || '未知商品') : '未知商品'
  359. // 检查是否有空的testingStatus
  360. const hasEmptyStatus = item.commonOrderList.some(commonItem =>
  361. commonItem.testingStatus === '' || commonItem.testingStatus === null || commonItem.testingStatus === undefined
  362. )
  363. if (hasEmptyStatus) {
  364. return {
  365. isValid: false,
  366. message: `${brandName} 还未完成质检选择`
  367. }
  368. }
  369. // 检查价格是否为空
  370. if (!item.price || item.price === 0 || item.price === '') {
  371. return {
  372. isValid: false,
  373. message: `${brandName} 还未填写总金额`
  374. }
  375. }
  376. }
  377. return {
  378. isValid: true,
  379. message: ''
  380. }
  381. },
  382. switchCategory(idx) {
  383. this.currentCategory = idx
  384. this.loadingMore = false
  385. this.finished = false
  386. const categoryId = this.categories[idx]?.id
  387. if (categoryId === 'unrecyclable' || categoryId === 'quality_issue') {
  388. // 不可回收和质量问题分类直接更新商品列表
  389. this.updateCurrentGoods()
  390. return
  391. }
  392. // 如果该分类的商品还没有加载,调用API获取
  393. if (!this.allProducts[categoryId]) {
  394. this.fetchGoodsList(categoryId, 1)
  395. } else {
  396. // 已有数据,直接更新显示
  397. this.updateCurrentGoods()
  398. }
  399. },
  400. changeNum(item, key, delta) {
  401. if (key === 'qualified') {
  402. // 如果是增加数量
  403. if (delta > 0) {
  404. // 对于不可回收和质量问题分类,直接增加数量,不弹出品牌选择
  405. if (item.id === 'unrecyclable-1' || item.id === 'quality-issue-1') {
  406. const newQualified = Math.max(0, (item.qualified || 0) + delta)
  407. this.$set(item, 'qualified', newQualified)
  408. this.updateInspectResult(item, 'qualified', delta)
  409. this.$forceUpdate()
  410. return
  411. }
  412. // 其他商品弹出品牌选择弹窗
  413. this.pendingBrandIndex = this.currentGoods.findIndex(goods => goods.id === item.id)
  414. this.getGoodsBrandList(item.id)
  415. this.showBrandPopup = true
  416. return
  417. }
  418. const newQualified = Math.max(0, (item.qualified || 0) + delta)
  419. this.$set(item, 'qualified', newQualified)
  420. // 更新inspectResult对象
  421. this.updateInspectResult(item, 'qualified', delta)
  422. // 强制更新categories计算属性,使左侧分类数字变化
  423. this.$forceUpdate()
  424. console.log('更新后的inspectResult:', JSON.stringify(this.inspectResult, null, 2))
  425. }
  426. },
  427. updateInspectResult(item, type, delta) {
  428. // 处理不可回收分类
  429. if (item.id === 'unrecyclable-1') {
  430. // 找到或创建不可回收的inspectResult项
  431. let inspectItem = this.inspectResult.list.find(listItem => listItem.id === 'unrecyclable')
  432. if (!inspectItem) {
  433. inspectItem = {
  434. id: 'unrecyclable',
  435. price: '',
  436. qualifiedNum: 0,
  437. noQualifiedNum: 0,
  438. unrecyclable: 0,
  439. commonOrderList: []
  440. }
  441. this.inspectResult.list.push(inspectItem)
  442. }
  443. if (type === 'qualified' && delta > 0) {
  444. inspectItem.unrecyclable++
  445. } else if (type === 'qualified' && delta < 0) {
  446. inspectItem.unrecyclable = Math.max(0, inspectItem.unrecyclable - 1)
  447. }
  448. console.log('不可回收数量更新:', inspectItem.unrecyclable)
  449. return
  450. }
  451. // 处理质量问题分类
  452. if (item.id === 'quality-issue-1') {
  453. // 找到或创建质量问题的inspectResult项
  454. let inspectItem = this.inspectResult.list.find(listItem => listItem.id === 'quality_issue')
  455. if (!inspectItem) {
  456. inspectItem = {
  457. id: 'quality_issue',
  458. price: '',
  459. qualifiedNum: 0,
  460. noQualifiedNum: 0,
  461. unrecyclable: 0,
  462. commonOrderList: []
  463. }
  464. this.inspectResult.list.push(inspectItem)
  465. }
  466. if (type === 'qualified' && delta > 0) {
  467. inspectItem.noQualifiedNum++
  468. } else if (type === 'qualified' && delta < 0) {
  469. inspectItem.noQualifiedNum = Math.max(0, inspectItem.noQualifiedNum - 1)
  470. }
  471. console.log('质量问题数量更新:', inspectItem.noQualifiedNum)
  472. return
  473. }
  474. // 找到或创建对应的inspectResult项
  475. const itemId = item.originalId || item.id
  476. let inspectItem = this.inspectResult.list.find(listItem => listItem.id == itemId)
  477. // 如果不存在,动态创建
  478. if (!inspectItem) {
  479. inspectItem = {
  480. id: itemId,
  481. price: '',
  482. qualifiedNum: 0,
  483. noQualifiedNum: 0,
  484. unrecyclable: 0,
  485. commonOrderList: []
  486. }
  487. this.inspectResult.list.push(inspectItem)
  488. console.log('新增商品到inspectResult:', itemId)
  489. }
  490. // 如果是预置数据同步,直接设置数量
  491. if (type === 'qualified' && delta > 0 && this.isPresetDataSync) {
  492. inspectItem.qualifiedNum = delta
  493. return
  494. }
  495. if (type === 'qualified' && delta > 0) {
  496. // 增加合格数量:直接更新数量
  497. inspectItem.qualifiedNum++
  498. console.log(`为商品 ${itemId} 增加数量,当前合格数量: ${inspectItem.qualifiedNum}`)
  499. } else if (type === 'qualified' && delta < 0) {
  500. // 减少合格数量:直接更新数量
  501. inspectItem.qualifiedNum = Math.max(0, inspectItem.qualifiedNum - 1)
  502. console.log(`为商品 ${itemId} 减少数量,当前合格数量: ${inspectItem.qualifiedNum}`)
  503. }
  504. console.log('更新后的inspectResult:', JSON.stringify(this.inspectResult, null, 2))
  505. },
  506. getInspectPrice(item) {
  507. // 获取inspectResult中对应商品的price
  508. if (item.id === 'unrecyclable-1' || item.id === 'quality-issue-1') {
  509. return ''
  510. }
  511. const itemId = item.originalId || item.id
  512. const inspectItem = this.inspectResult.list?.find(listItem => listItem.id == itemId)
  513. return inspectItem ? inspectItem.price : ''
  514. },
  515. updateInspectPrice(item, event) {
  516. // 更新inspectResult中对应商品的price
  517. if (item.id === 'unrecyclable-1' || item.id === 'quality-issue-1') {
  518. return
  519. }
  520. const itemId = item.originalId || item.id
  521. let inspectItem = this.inspectResult.list?.find(listItem => listItem.id == itemId)
  522. // 如果不存在,动态创建
  523. if (!inspectItem) {
  524. inspectItem = {
  525. id: itemId,
  526. price: '',
  527. qualifiedNum: 0,
  528. noQualifiedNum: 0,
  529. unrecyclable: 0,
  530. commonOrderList: []
  531. }
  532. this.inspectResult.list.push(inspectItem)
  533. }
  534. const newPrice = parseFloat(event.detail.value) || 0
  535. inspectItem.price = newPrice
  536. console.log('更新价格:', itemId, newPrice)
  537. },
  538. loadMoreGoods() {
  539. const categoryId = this.categories[this.currentCategory]?.id
  540. // 不可回收和质量问题分类不支持加载更多
  541. if (categoryId === 'unrecyclable' || categoryId === 'quality_issue') {
  542. return
  543. }
  544. const page = (this.allProductsPage[categoryId] || 1) + 1
  545. const total = this.allProductsTotal[categoryId] || 0
  546. const loaded = (this.allProducts[categoryId] || []).length
  547. if (this.loadingMore || this.finished) return
  548. if (loaded < total) {
  549. this.loadingMore = true
  550. this.fetchGoodsList(categoryId, page, () => {
  551. this.loadingMore = false
  552. // 判断是否加载完
  553. const newLoaded = (this.allProducts[categoryId] || []).length
  554. this.finished = newLoaded >= (this.allProductsTotal[categoryId] || 0)
  555. })
  556. } else {
  557. this.finished = true
  558. }
  559. },
  560. // 获取品牌列表
  561. getGoodsBrandList(productId, searchName = '') {
  562. this.currentProductId = productId
  563. const params = { productId }
  564. if (searchName.trim()) {
  565. params.name = searchName.trim()
  566. }
  567. this.$api('getGoodsBrandList', params, res => {
  568. if (res && res.success && res.result && res.result.records) {
  569. this.brandList = res.result.records.map(item => {
  570. // 获取品牌名称的拼音首字母
  571. const firstChar = this.getPinyinFirstLetter(item.name)
  572. return {
  573. id: item.id,
  574. logo: item.image || '/static/brand/alexander.png',
  575. name: item.name,
  576. letter: firstChar
  577. }
  578. })
  579. }
  580. })
  581. },
  582. // 获取中文拼音首字母
  583. getPinyinFirstLetter(str) {
  584. if (!str) return '#'
  585. const firstChar = str.charAt(0)
  586. let index = this.brandIndexList.indexOf(firstChar.toUpperCase())
  587. if (index != -1) {
  588. return this.brandIndexList[index]
  589. }
  590. return '#'
  591. },
  592. // 品牌搜索输入事件处理
  593. onBrandSearchInput(e) {
  594. const searchValue = e.detail.value
  595. // 清除之前的定时器
  596. if (this.searchTimer) {
  597. clearTimeout(this.searchTimer)
  598. }
  599. // 设置防抖,500ms后执行搜索
  600. this.searchTimer = setTimeout(() => {
  601. if (this.currentProductId) {
  602. this.getGoodsBrandList(this.currentProductId, searchValue)
  603. }
  604. }, 500)
  605. },
  606. // 打开品牌确认弹窗
  607. openBrandConfirm(brand) {
  608. this.brandConfirmInfo = {
  609. id: brand.id,
  610. logo: brand.logo,
  611. name: brand.name
  612. }
  613. this.showBrandConfirm = true
  614. },
  615. // 关闭品牌确认弹窗
  616. closeBrandConfirm() {
  617. this.showBrandConfirm = false
  618. },
  619. // 确认品牌选择
  620. confirmBrand() {
  621. this.showBrandConfirm = false
  622. this.showBrandPopup = false
  623. // 确认后增加商品数量
  624. if (this.pendingBrandIndex !== null) {
  625. const item = this.currentGoods[this.pendingBrandIndex]
  626. if (item) {
  627. const newQualified = Math.max(0, (item.qualified || 0) + 1)
  628. this.$set(item, 'qualified', newQualified)
  629. this.updateInspectResult(item, 'qualified', 1)
  630. // 强制更新categories计算属性,使左侧分类数字变化
  631. this.$forceUpdate()
  632. }
  633. this.pendingBrandIndex = null
  634. }
  635. },
  636. // 关闭品牌弹窗
  637. closeBrandPopup() {
  638. this.showBrandPopup = false
  639. this.pendingBrandIndex = null
  640. this.brandSearch = ''
  641. this.currentProductId = null
  642. },
  643. },
  644. created() {
  645. this.currentCategory = 0
  646. },
  647. onLoad(options) {
  648. // 接收订单数据
  649. if (options && options.orderData) {
  650. try {
  651. this.order = JSON.parse(decodeURIComponent(options.orderData))
  652. console.log('接收到的订单数据:', this.order)
  653. // 订单数据加载完成后初始化质检结果
  654. this.$nextTick(() => {
  655. this.initInspectResult()
  656. })
  657. } catch (error) {
  658. console.error('解析订单数据失败:', error)
  659. }
  660. }
  661. if (options && options.orderId) {
  662. this.orderId = options.orderId
  663. }
  664. // 初始化加载第一个分类的商品
  665. this.$nextTick(() => {
  666. // 确保categories已经计算完成
  667. if (this.categories.length > 0) {
  668. const firstCategoryId = this.categories[0]?.id
  669. console.log('第一个分类ID:', firstCategoryId, '所有分类:', this.categories.map(c => c.id))
  670. if (firstCategoryId && firstCategoryId !== 'unrecyclable' && firstCategoryId !== 'quality_issue') {
  671. this.fetchGoodsList(firstCategoryId, 1)
  672. } else if (firstCategoryId === 'unrecyclable' || firstCategoryId === 'quality_issue') {
  673. this.updateCurrentGoods()
  674. }
  675. } else {
  676. console.log('categories为空,等待数据加载')
  677. // 如果categories为空,等待一下再尝试
  678. setTimeout(() => {
  679. if (this.categories.length > 0) {
  680. const firstCategoryId = this.categories[0]?.id
  681. if (firstCategoryId && firstCategoryId !== 'unrecyclable' && firstCategoryId !== 'quality_issue') {
  682. this.fetchGoodsList(firstCategoryId, 1)
  683. } else if (firstCategoryId === 'unrecyclable' || firstCategoryId === 'quality_issue') {
  684. this.updateCurrentGoods()
  685. }
  686. }
  687. }, 500)
  688. }
  689. })
  690. console.log(this.orderId, 'orderId')
  691. },
  692. onShow() {
  693. // 确保在页面显示时categories已经正确计算
  694. console.log('onShow - categories:', this.categories.map(c => ({ id: c.id, title: c.title, badge: c.badge })))
  695. // 强制更新视图,确保所有分类都显示
  696. this.$nextTick(() => {
  697. console.log('分类导航应该显示的分类数量:', this.categories.length)
  698. this.categories.forEach((cat, index) => {
  699. console.log(`分类 ${index}: ${cat.title} (${cat.id})`)
  700. })
  701. })
  702. },
  703. }
  704. </script>
  705. <style lang="scss" scoped>
  706. .inspect-container {
  707. min-height: 100vh;
  708. background: #f8f8f8;
  709. display: flex;
  710. flex-direction: column;
  711. }
  712. .nav-bar {
  713. display: flex;
  714. align-items: center;
  715. height: calc(150rpx + var(--status-bar-height));
  716. padding: 0 32rpx;
  717. padding-top: var(--status-bar-height);
  718. background: #fff;
  719. position: fixed;
  720. top: 0;
  721. left: 0;
  722. right: 0;
  723. z-index: 999;
  724. box-sizing: border-box;
  725. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
  726. .back {
  727. padding: 20rpx;
  728. margin-left: -20rpx;
  729. }
  730. .nav-title {
  731. flex: 1;
  732. text-align: center;
  733. font-size: 32rpx;
  734. font-weight: 500;
  735. color: #222;
  736. }
  737. .nav-icons {
  738. display: flex;
  739. align-items: center;
  740. gap: 12px;
  741. }
  742. }
  743. .main-content {
  744. margin-top: calc(200rpx + var(--status-bar-height));
  745. display: flex;
  746. background: none;
  747. height: calc(100vh - 200rpx - var(--status-bar-height));
  748. min-height: calc(100vh - 200rpx - var(--status-bar-height));
  749. }
  750. .category-nav {
  751. width: 80px;
  752. background: #fff;
  753. border-radius: 24px 0 0 24px;
  754. // padding: 24px 0;
  755. display: flex;
  756. flex-direction: column;
  757. align-items: center;
  758. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
  759. height: calc(100vh - 200rpx - var(--status-bar-height) - 80px);
  760. max-height: calc(100vh - 200rpx - var(--status-bar-height) - 80px);
  761. overflow-y: scroll;
  762. overflow-x: hidden;
  763. position: relative;
  764. z-index: 2;
  765. -webkit-overflow-scrolling: touch;
  766. scrollbar-width: thin;
  767. scrollbar-color: #ddd transparent;
  768. &::-webkit-scrollbar {
  769. width: 4px;
  770. }
  771. &::-webkit-scrollbar-track {
  772. background: transparent;
  773. }
  774. &::-webkit-scrollbar-thumb {
  775. background: #ddd;
  776. border-radius: 2px;
  777. }
  778. &::-webkit-scrollbar-thumb:hover {
  779. background: #ccc;
  780. }
  781. .category-item {
  782. width: 64px;
  783. height: 44px;
  784. border-radius: 16px 0 0 16px;
  785. display: flex;
  786. align-items: center;
  787. justify-content: flex-start;
  788. font-size: 16px;
  789. color: #222;
  790. margin-bottom: 12px;
  791. background: #fff;
  792. position: relative;
  793. transition: background 0.2s, color 0.2s, font-weight 0.2s;
  794. padding-left: 12px;
  795. &.active {
  796. background: linear-gradient(90deg, #fff7e6 80%, #fff 100%);
  797. color: #ffb400;
  798. font-weight: bold;
  799. &::before {
  800. content: '';
  801. position: absolute;
  802. left: 0;
  803. top: 30%;
  804. height: 40%;
  805. width: 2px;
  806. border-radius: 4px;
  807. background: #ffb400;
  808. bottom: auto;
  809. }
  810. }
  811. .category-badge {
  812. position: absolute;
  813. top: 6px;
  814. right: 10px;
  815. background: #ff4d4f;
  816. color: #fff;
  817. font-size: 12px;
  818. border-radius: 50%;
  819. width: 18px;
  820. height: 18px;
  821. display: flex;
  822. align-items: center;
  823. justify-content: center;
  824. }
  825. }
  826. }
  827. .goods-list {
  828. flex: 1;
  829. height: calc(100vh - 200rpx - var(--status-bar-height) - 80px);
  830. padding: 0 0 0 16px;
  831. overflow-y: auto;
  832. background: none;
  833. }
  834. .goods-card {
  835. background: #fff;
  836. border-radius: 24px;
  837. box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
  838. margin-bottom: 18px;
  839. padding: 18px 18px 9px 18px;
  840. }
  841. .goods-header {
  842. display: flex;
  843. align-items: center;
  844. margin-bottom: 12px;
  845. .goods-img {
  846. width: 56px;
  847. height: 56px;
  848. border-radius: 16px;
  849. margin-right: 12px;
  850. background: #f8f8f8;
  851. object-fit: contain;
  852. }
  853. .goods-info {
  854. flex: 1;
  855. display: flex;
  856. flex-direction: column;
  857. justify-content: center;
  858. min-width: 0;
  859. .goods-title-row {
  860. display: flex;
  861. align-items: baseline;
  862. .goods-name {
  863. font-size: 16px;
  864. font-weight: bold;
  865. color: #222;
  866. margin-right: 8px;
  867. }
  868. .goods-price {
  869. font-size: 15px;
  870. color: #ffb400;
  871. font-weight: bold;
  872. .goods-unit {
  873. font-size: 13px;
  874. color: #bbb;
  875. }
  876. }
  877. }
  878. .goods-desc {
  879. font-size: 13px;
  880. color: #999;
  881. margin-top: 4px;
  882. }
  883. }
  884. }
  885. .goods-row {
  886. display: flex;
  887. align-items: center;
  888. margin-bottom: 12px;
  889. .row-label {
  890. font-size: 14px;
  891. color: #888;
  892. width: 80px;
  893. flex-shrink: 0;
  894. }
  895. .num-ctrl {
  896. display: flex;
  897. align-items: center;
  898. .num-btn {
  899. width: 60rpx;
  900. height: 60rpx;
  901. padding: 0;
  902. margin: 0;
  903. display: flex;
  904. align-items: center;
  905. justify-content: center;
  906. font-size: 28rpx;
  907. color: #666;
  908. background: #ffffff;
  909. border: none;
  910. border-radius: 50%;
  911. &::after {
  912. border: none;
  913. }
  914. &:active {
  915. opacity: 0.8;
  916. }
  917. }
  918. .num {
  919. width: 80rpx;
  920. text-align: center;
  921. font-size: 32rpx;
  922. color: #333;
  923. }
  924. }
  925. .amount-input {
  926. flex: 1;
  927. height: 32px;
  928. border-radius: 12px;
  929. background: #f6f6f6;
  930. border: none;
  931. font-size: 15px;
  932. color: #222;
  933. padding-left: 10px;
  934. margin-left: 8px;
  935. }
  936. }
  937. .footer-btns {
  938. position: fixed;
  939. left: 0;
  940. right: 0;
  941. bottom: 0;
  942. background: #fff;
  943. display: flex;
  944. gap: 16px;
  945. padding: 12px 16px 24px 16px;
  946. z-index: 101;
  947. .btn-outline {
  948. flex: 1;
  949. height: 40px;
  950. border-radius: 16px;
  951. border: 1px solid #ffe09a;
  952. color: #ffb400;
  953. background: #fff0d2;
  954. font-size: 15px;
  955. font-weight: 500;
  956. box-shadow: none;
  957. padding: 0 18px;
  958. }
  959. .btn-main {
  960. flex: 1;
  961. height: 40px;
  962. border-radius: 16px;
  963. background: linear-gradient(90deg, #ffd01e 0%, #ffac04 100%);
  964. color: #fff;
  965. border: none;
  966. font-size: 15px;
  967. font-weight: 500;
  968. box-shadow: none;
  969. padding: 0 18px;
  970. }
  971. }
  972. .loading-more {
  973. text-align: center;
  974. color: #999;
  975. padding: 20rpx 0;
  976. font-size: 26rpx;
  977. }
  978. // 品牌选择弹窗样式
  979. .brand-popup-mask {
  980. position: fixed;
  981. left: 0;
  982. right: 0;
  983. top: 0;
  984. bottom: 0;
  985. background: rgba(0,0,0,0.35);
  986. z-index: 3000;
  987. display: flex;
  988. align-items: flex-end;
  989. justify-content: center;
  990. }
  991. .brand-popup {
  992. position: relative;
  993. width: 100%;
  994. max-width: 750px;
  995. background: #fff;
  996. border-radius: 32rpx 32rpx 0 0;
  997. box-shadow: 0 -4rpx 24rpx rgba(0,0,0,0.08);
  998. padding-bottom: 40rpx;
  999. max-height: 90vh;
  1000. display: flex;
  1001. flex-direction: column;
  1002. overflow: hidden;
  1003. }
  1004. .brand-popup-header {
  1005. display: flex;
  1006. align-items: center;
  1007. justify-content: center;
  1008. padding: 32rpx 24rpx 0 24rpx;
  1009. font-size: 32rpx;
  1010. font-weight: bold;
  1011. position: relative;
  1012. }
  1013. .brand-popup-close {
  1014. position: absolute;
  1015. left: 24rpx;
  1016. font-size: 28rpx;
  1017. color: #888;
  1018. }
  1019. .brand-popup-title {
  1020. font-size: 32rpx;
  1021. color: #222;
  1022. font-weight: bold;
  1023. }
  1024. .brand-popup-search {
  1025. padding: 20rpx 24rpx 0 24rpx;
  1026. }
  1027. .brand-search-input {
  1028. width: 100%;
  1029. height: 60rpx;
  1030. border-radius: 30rpx;
  1031. background: #f5f5f5;
  1032. border: none;
  1033. padding-left: 40rpx;
  1034. font-size: 28rpx;
  1035. color: #888;
  1036. }
  1037. .brand-popup-list {
  1038. flex: 1;
  1039. overflow-y: auto;
  1040. max-height: 60vh;
  1041. padding: 0 24rpx;
  1042. scrollbar-width: none; /* Firefox */
  1043. -ms-overflow-style: none; /* IE and Edge */
  1044. &::-webkit-scrollbar {
  1045. width: 0 !important;
  1046. display: none; /* Chrome, Safari, Opera */
  1047. }
  1048. }
  1049. .brand-letter {
  1050. font-size: 28rpx;
  1051. color: #888;
  1052. margin: 24rpx 0 8rpx 0;
  1053. font-weight: bold;
  1054. }
  1055. .brand-item {
  1056. display: flex;
  1057. align-items: center;
  1058. padding: 16rpx 0;
  1059. border-bottom: 1px solid #f0f0f0;
  1060. }
  1061. .brand-logo {
  1062. width: 60rpx;
  1063. height: 60rpx;
  1064. margin-right: 20rpx;
  1065. border-radius: 8rpx;
  1066. background: #f8f8f8;
  1067. }
  1068. .brand-name {
  1069. font-size: 28rpx;
  1070. color: #222;
  1071. }
  1072. // 品牌确认弹窗样式
  1073. .brand-confirm-mask {
  1074. position: fixed;
  1075. left: 0;
  1076. right: 0;
  1077. top: 0;
  1078. bottom: 0;
  1079. background: rgba(0,0,0,0.25);
  1080. z-index: 5001;
  1081. display: flex;
  1082. align-items: center;
  1083. justify-content: center;
  1084. }
  1085. .brand-confirm-popup {
  1086. width: 70vw;
  1087. max-width: 270px;
  1088. background: #fff;
  1089. border-radius: 32rpx;
  1090. box-shadow: 0 8rpx 32rpx rgba(0,0,0,0.12);
  1091. display: flex;
  1092. flex-direction: column;
  1093. align-items: center;
  1094. padding: 48rpx 20rpx 36rpx 20rpx;
  1095. position: relative;
  1096. }
  1097. .brand-confirm-title {
  1098. font-size: 36rpx;
  1099. color: #222;
  1100. font-weight: bold;
  1101. text-align: center;
  1102. margin-bottom: 24rpx;
  1103. }
  1104. .brand-confirm-logo-wrap {
  1105. width: 120rpx;
  1106. height: 120rpx;
  1107. background: #f8f8f8;
  1108. border-radius: 50%;
  1109. display: flex;
  1110. align-items: center;
  1111. justify-content: center;
  1112. margin-bottom: 18rpx;
  1113. }
  1114. .brand-confirm-logo {
  1115. width: 80rpx;
  1116. height: 80rpx;
  1117. border-radius: 50%;
  1118. }
  1119. .brand-confirm-name {
  1120. font-size: 28rpx;
  1121. color: #222;
  1122. font-weight: bold;
  1123. text-align: center;
  1124. margin-bottom: 16rpx;
  1125. }
  1126. .brand-confirm-desc {
  1127. font-size: 24rpx;
  1128. color: #999;
  1129. text-align: center;
  1130. margin-bottom: 32rpx;
  1131. line-height: 1.6;
  1132. }
  1133. .brand-confirm-btn-row {
  1134. width: 100%;
  1135. display: flex;
  1136. justify-content: space-between;
  1137. gap: 24rpx;
  1138. }
  1139. .brand-confirm-btn {
  1140. flex: 1;
  1141. height: 72rpx;
  1142. border-radius: 36rpx;
  1143. font-size: 28rpx;
  1144. font-weight: bold;
  1145. display: flex;
  1146. align-items: center;
  1147. justify-content: center;
  1148. border: none;
  1149. margin: 0 0;
  1150. }
  1151. .brand-confirm-btn.retry {
  1152. background: #fff;
  1153. color: #ff9c00;
  1154. border: 2rpx solid #ff9c00;
  1155. }
  1156. .brand-confirm-btn.confirm {
  1157. background: linear-gradient(to right, #ffd01e, #ff8917);
  1158. color: #fff;
  1159. border: none;
  1160. }
  1161. </style>