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

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