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

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