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

1142 lines
33 KiB

10 months ago
10 months ago
9 months ago
10 months ago
10 months ago
7 months ago
10 months ago
7 months ago
7 months ago
10 months ago
7 months ago
10 months ago
10 months ago
10 months ago
7 months ago
10 months ago
10 months ago
10 months ago
10 months ago
7 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
9 months ago
9 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
9 months ago
10 months ago
10 months ago
7 months ago
7 months ago
7 months ago
7 months ago
10 months ago
7 months ago
7 months ago
9 months ago
9 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
7 months ago
10 months ago
10 months ago
10 months ago
9 months ago
10 months ago
9 months ago
9 months ago
7 months ago
9 months ago
10 months ago
9 months ago
10 months ago
10 months ago
10 months ago
9 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
7 months ago
10 months ago
10 months ago
10 months ago
7 months ago
7 months ago
10 months ago
7 months ago
10 months ago
10 months ago
10 months ago
10 months ago
7 months ago
10 months ago
7 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
7 months ago
10 months ago
10 months ago
10 months ago
7 months ago
10 months ago
10 months ago
10 months ago
7 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
9 months ago
10 months ago
10 months ago
9 months ago
10 months ago
9 months ago
10 months ago
10 months ago
9 months ago
10 months ago
9 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
  1. <template>
  2. <view class="inspect-result-container" :class="{ 'popup-open': isPopupOpen }">
  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="more" size="24" color="#222" />
  11. </view>
  12. </view>
  13. <view class="main-content">
  14. <!-- 合格产品卡片 -->
  15. <view v-if="hasQualifiedItems" class="result-card">
  16. <view class="card-title">合格产品</view>
  17. <view v-for="(item, index) in qualifiedList" :key="item.uniqueKey || index" class="result-group">
  18. <view class="row-main">
  19. <view class="goods-name-section">
  20. <text class="goods-name">{{ getGoodsName(item.shopId) }}</text>
  21. <text class="goods-brand">{{ getBrandStyleInfo(item) }}</text>
  22. </view>
  23. <text class="row-count">x{{ item.count }}</text>
  24. </view>
  25. <view class="price-input-row">
  26. <text class="price-label">总金额</text>
  27. <input class="price-input" v-model="item.total" type="digit" placeholder="请输入金额" />
  28. </view>
  29. <!-- 选择理由模块 -->
  30. <view v-if="item.items && item.items.length > 0">
  31. <view v-for="(commonItem, itemIndex) in item.items" :key="commonItem.id || itemIndex" class="row-reason">
  32. <text class="reason-label">{{ getQualifiedItemLabel(item, itemIndex) }}</text>
  33. <view class="reason-select" @click="selectReasonForQualified(commonItem, item)">
  34. <text class="reason-placeholder" :class="{ 'selected': hasSelectedReason(commonItem) }">
  35. {{ hasSelectedReason(commonItem) ? '已选择' : '请选择理由(选填)' }}
  36. </text>
  37. <uni-icons type="right" size="18" color="#bbb" />
  38. </view>
  39. </view>
  40. </view>
  41. </view>
  42. </view>
  43. <!-- 不合格产品卡片 -->
  44. <view v-if="hasUnqualifiedItems" class="result-card">
  45. <view class="card-title">不合格产品</view>
  46. <view v-for="group in unqualifiedGroups" :key="group.shopId" class="result-group">
  47. <view class="row-main">
  48. <view class="goods-name-section">
  49. <text class="goods-name">质量问题</text>
  50. <text class="goods-brand"></text>
  51. </view>
  52. <text class="row-count">x{{ group.count }}</text>
  53. </view>
  54. <view class="price-input-row">
  55. <text class="price-label">总金额</text>
  56. <input class="price-input" v-model="group.total" type="digit" placeholder="请输入金额" />
  57. </view>
  58. <!-- 选择理由模块 -->
  59. <view v-if="inspectData && inspectData.list">
  60. <view v-for="(item, index) in (inspectData.list.find(i => i.id === 'quality_issue')?.commonOrderList || [])"
  61. :key="item.id || item.testingStatus" class="row-reason">
  62. <text class="reason-label">质量问题{{ index + 1 }}</text>
  63. <view class="reason-select" @click="selectReason(item)">
  64. <text class="reason-placeholder" :class="{ 'selected': hasSelectedReason(item) }">
  65. {{ hasSelectedReason(item) ? '已选择' : '请选择理由' }}
  66. </text>
  67. <uni-icons type="right" size="18" color="#bbb" />
  68. </view>
  69. </view>
  70. </view>
  71. </view>
  72. </view>
  73. <!-- 不可回收产品卡片 -->
  74. <view v-if="hasUnrecyclableItems" class="result-card">
  75. <view class="card-title">不可回收产品</view>
  76. <view v-for="item in unrecyclableList" :key="item.id" class="result-group">
  77. <view class="row-main">
  78. <view class="goods-name-section">
  79. <text class="goods-name">不可回收</text>
  80. <text class="goods-brand"></text>
  81. </view>
  82. <text class="row-count">x{{ item.count }}</text>
  83. </view>
  84. <view class="price-input-row">
  85. <text class="price-label">总金额</text>
  86. <input class="price-input" v-model="item.total" type="digit" placeholder="请输入金额" />
  87. </view>
  88. <!-- 选择理由模块 -->
  89. <view v-if="inspectData && inspectData.list">
  90. <view v-for="(commonItem,index) in (inspectData.list.find(i => i.id === 'unrecyclable')?.commonOrderList || [])"
  91. :key="commonItem.id || commonItem.testingStatus" class="row-reason">
  92. <text class="reason-label">不可回收{{ index +1 }}</text>
  93. <view class="reason-select" @click="selectReason(commonItem)">
  94. <text class="reason-placeholder" :class="{ 'selected': hasSelectedReason(commonItem) }">
  95. {{ hasSelectedReason(commonItem) ? '已选择' : '请选择理由' }}
  96. </text>
  97. <uni-icons type="right" size="18" color="#bbb" />
  98. </view>
  99. </view>
  100. </view>
  101. </view>
  102. </view>
  103. <!-- 回收信息卡片 -->
  104. <view class="result-card info-card">
  105. <view class="card-title-row">
  106. <text class="card-title">回收信息</text>
  107. <view class="status-tag">待质检</view>
  108. </view>
  109. <view class="info-row">
  110. <text class="info-label">订单编号</text>
  111. <text class="info-value copy-btn"
  112. @click="$utils.copyText(order?.ordeNo)"
  113. >{{ order?.ordeNo }} 复制</text>
  114. </view>
  115. <view class="info-row">
  116. <text class="info-label">取件时间</text>
  117. <text class="info-value">{{ order?.goTime || '2025-03-20 11:00' }}</text>
  118. </view>
  119. </view>
  120. </view>
  121. <!-- 底部操作按钮 -->
  122. <view class="footer-btns">
  123. <button class="btn-outline" @tap="goPrev">上一步</button>
  124. <button class="btn-main" @tap="finishInspect">完成质检</button>
  125. </view>
  126. <!-- 理由弹窗 -->
  127. <uni-popup ref="reasonPopup" type="bottom" :mask-click="false" :safe-area="false" class="reason-popup-wrapper">
  128. <view class="reason-popup">
  129. <view class="popup-header">
  130. <text class="popup-close" @tap="closeReasonPopup">关闭</text>
  131. <text class="popup-title">{{ currentReasonTitle }}</text>
  132. </view>
  133. <scroll-view class="popup-content" scroll-y="true">
  134. <view class="popup-section">
  135. <text class="section-label">上传图片</text>
  136. <view class="img-list">
  137. <view class="img-item add" @tap="addReasonImg">
  138. <uni-icons type="plusempty" size="32" color="#bbb" />
  139. </view>
  140. <view v-for="(img, idx) in reasonImages" :key="idx" class="img-item">
  141. <image :src="img" class="img" />
  142. <view class="img-del" @tap="removeReasonImg(idx)">×</view>
  143. </view>
  144. </view>
  145. </view>
  146. <view class="popup-section">
  147. <text class="section-label">选择理由</text>
  148. <view v-for="(item, index) in reasonOptions" :key="item.id" class="reason-row" @tap="toggleReason(index)">
  149. <view :class="['checkbox', { checked: reasonChecked.includes(item.reason) }]" />
  150. <text class="reason-text">{{ item.reason }}</text>
  151. </view>
  152. </view>
  153. </scroll-view>
  154. <button class="popup-save-btn" @tap="saveReason">保存</button>
  155. </view>
  156. </uni-popup>
  157. </view>
  158. </template>
  159. <script>
  160. import pullRefreshMixin from '../mixins/pullRefreshMixin.js'
  161. import OSS from '@/utils/oss-upload/oss/index.js'
  162. export default {
  163. mixins: [pullRefreshMixin],
  164. data() {
  165. return {
  166. inspectData: null, // 从步骤一传过来的质检数据
  167. order: null, // 订单数据
  168. qualifiedList: [],
  169. unqualifiedGroups: [],
  170. unrecyclableList: [],
  171. goodsNameMap: {}, // 商品id => 名称
  172. reasonPopupVisible: false,
  173. currentReasonTitle: '',
  174. reasonImages: [],
  175. reasonOptions: [],
  176. reasonChecked: [],
  177. currentReasonItem: null,
  178. isPopupOpen: false, // 控制弹窗状态
  179. scrollTop: 0 // 记录滚动位置
  180. }
  181. },
  182. computed: {
  183. hasQualifiedItems() {
  184. return this.qualifiedList && this.qualifiedList.length > 0
  185. },
  186. hasUnqualifiedItems() {
  187. return this.unqualifiedGroups && this.unqualifiedGroups.length > 0
  188. },
  189. hasUnrecyclableItems() {
  190. return this.unrecyclableList && this.unrecyclableList.length > 0
  191. }
  192. },
  193. onLoad(options) {
  194. // 接收质检数据和订单数据
  195. if (options && options.resultData) {
  196. try {
  197. const resultData = JSON.parse(decodeURIComponent(options.resultData))
  198. this.inspectData = resultData.inspectResult
  199. this.order = resultData.order
  200. this.processInspectData()
  201. } catch (error) {
  202. console.error('解析数据失败:', error)
  203. }
  204. }
  205. if (options && options.inspectData && !options.resultData) {
  206. try {
  207. this.inspectData = JSON.parse(decodeURIComponent(options.inspectData))
  208. this.processInspectData()
  209. } catch (error) {
  210. console.error('解析质检数据失败:', error)
  211. }
  212. }
  213. },
  214. methods: {
  215. async processInspectData() {
  216. if (!this.inspectData || !this.inspectData["list"]) return
  217. this.qualifiedList = []
  218. this.unqualifiedGroups = []
  219. this.unrecyclableList = []
  220. // 只收集普通商品的 shopId 作为 goodsId
  221. const allIds = []
  222. this.inspectData["list"].forEach(item => {
  223. if (item.shopId && item.id !== 'unrecyclable' && item.id !== 'quality_issue') {
  224. allIds.push(item.shopId)
  225. }
  226. })
  227. console.log('allIds', allIds)
  228. const uniqueIds = Array.from(new Set(allIds.filter(Boolean)))
  229. console.log('uniqueIds', uniqueIds)
  230. await this.fetchGoodsNames(uniqueIds)
  231. // 合格商品 - 按照商品、品牌、款式作为唯一标识生成独立数据
  232. this.inspectData["list"].forEach(item => {
  233. if (item.qualifiedNum > 0 && item.id !== 'unrecyclable' && item.id !== 'quality_issue') {
  234. // 为每个品牌款式组合创建独立的条目
  235. const uniqueKey = item.uniqueKey || `${item.shopId}_${item.pinId || 'no-brand'}_${item.styleId || 'no-style'}`
  236. this.qualifiedList.push({
  237. shopId: item.shopId,
  238. count: item.qualifiedNum,
  239. total: item.price || 0,
  240. classId: item.categoryId,
  241. brandName: item.brandName || '',
  242. styleName: item.styleName || '',
  243. pinId: item.pinId || '',
  244. styleId: item.styleId || '',
  245. uniqueKey: uniqueKey,
  246. originalItem: item, // 保存原始数据引用
  247. items: item.commonOrderList ? [...item.commonOrderList] : [{
  248. testingInstructions: '',
  249. testingImages: '',
  250. testingStatus: 0
  251. }]
  252. })
  253. console.log('qualifiedList', this.qualifiedList)
  254. }
  255. })
  256. // 不合格商品(只展示质量问题)
  257. this.inspectData["list"].forEach(item => {
  258. if (item.id === 'quality_issue' && item.noQualifiedNum > 0) {
  259. this.unqualifiedGroups.push({
  260. shopId: item.id,
  261. count: item.noQualifiedNum,
  262. total: item.price,
  263. items: item.commonOrderList ? [...item.commonOrderList] : []
  264. })
  265. }
  266. })
  267. // 不可回收商品
  268. this.inspectData["list"].forEach(item => {
  269. if (item.id === 'unrecyclable' && item.unrecyclable > 0) {
  270. this.unrecyclableList.push({
  271. id: item.id,
  272. count: item.unrecyclable,
  273. total: item.price,
  274. items: item.commonOrderList ? [...item.commonOrderList] : []
  275. })
  276. }
  277. })
  278. },
  279. async fetchGoodsNames(ids) {
  280. // 批量获取商品名称
  281. const nameMap = {}
  282. for (const id of ids) {
  283. try {
  284. const res = await this.$api('getGoodsDetail', { goodsId: id })
  285. if (res && res.code === 200 && res.result) {
  286. nameMap[id] = res.result.name
  287. }
  288. } catch (e) {
  289. nameMap[id] = '未知商品'
  290. }
  291. }
  292. this.goodsNameMap = nameMap
  293. },
  294. getGoodsName(id) {
  295. return this.goodsNameMap[id] || '未知商品'
  296. },
  297. goBack() {
  298. uni.navigateBack()
  299. },
  300. goPrev() {
  301. uni.navigateBack()
  302. },
  303. finishInspect() {
  304. // 校验金额必填性
  305. // 检查合格产品金额
  306. for (const item of this.qualifiedList) {
  307. if (!item.total || item.total === '' || parseFloat(item.total) <= 0) {
  308. uni.showToast({ title: '请填写合格产品金额', icon: 'none' })
  309. return
  310. }
  311. }
  312. // 检查不合格产品金额
  313. for (const group of this.unqualifiedGroups) {
  314. if (!group.total || group.total === '' || parseFloat(group.total) <= 0) {
  315. uni.showToast({ title: '请填写不合格产品金额', icon: 'none' })
  316. return
  317. }
  318. }
  319. // 检查不可回收产品金额
  320. for (const item of this.unrecyclableList) {
  321. if (!item.total || item.total === '' || parseFloat(item.total) <= 0) {
  322. uni.showToast({ title: '请填写不可回收产品金额', icon: 'none' })
  323. return
  324. }
  325. }
  326. // 校验理由必填性
  327. // 1. 合格产品理由可选填
  328. // 2. 质量问题和不可回收理由必填
  329. // 检查质量问题
  330. const qualityItem = this.inspectData["list"].find(item => item.id === 'quality_issue')
  331. if (qualityItem && qualityItem.noQualifiedNum > 0) {
  332. const hasReason = qualityItem.commonOrderList && qualityItem.commonOrderList.some(c => {
  333. // 只要填写了理由或图片即可
  334. return (c.testingInstructions && c.testingInstructions.trim() !== '') ||
  335. (c.testingImages && c.testingImages.trim() !== '')
  336. })
  337. if (!hasReason) {
  338. uni.showToast({ title: '质量问题理由必填', icon: 'none' })
  339. return
  340. }
  341. }
  342. // 检查不可回收
  343. const unrecyclableItem = this.inspectData["list"].find(item => item.id === 'unrecyclable')
  344. if (unrecyclableItem && unrecyclableItem.unrecyclable > 0) {
  345. const hasReason = unrecyclableItem.commonOrderList && unrecyclableItem.commonOrderList.some(c => {
  346. return (c.testingInstructions && c.testingInstructions.trim() !== '') ||
  347. (c.testingImages && c.testingImages.trim() !== '')
  348. })
  349. if (!hasReason) {
  350. uni.showToast({ title: '不可回收理由必填', icon: 'none' })
  351. return
  352. }
  353. }
  354. // 同步用户输入的金额到inspectData
  355. this.syncPriceToInspectData()
  356. // 在提交前将质量问题和不可回收中的id值置为空
  357. const processedList = this.inspectData["list"].map(item => {
  358. if (item.id === 'quality_issue' || item.id === 'unrecyclable') {
  359. // 对于质量问题和不可回收,将id置为空
  360. return { ...item, id: '' }
  361. }
  362. return item
  363. })
  364. const inspectDatas = {...this.inspectData, list: JSON.stringify(processedList, null, 2)}
  365. console.log('最终的质检数据:', inspectDatas);
  366. this.$api('submitQualityInfo', inspectDatas , res => {
  367. if (res && res.code === 200) {
  368. uni.showToast({ title: '质检完成', icon: 'success' })
  369. uni.reLaunch({ url: '/pages/component/home' })
  370. }
  371. })
  372. uni.showToast({ title: '完成质检', icon: 'success' })
  373. },
  374. selectReason(item) {
  375. // 直接使用传入的item,因为它已经是commonOrderList中的具体项
  376. let popupItem = item
  377. let type = 0 // 默认类型
  378. // 根据testingStatus确定类型
  379. if (item.testingStatus === 1) {
  380. type = 1 // 质量问题
  381. } else if (item.testingStatus === 2) {
  382. type = 2 // 不可回收
  383. }
  384. this.currentReasonItem = popupItem
  385. // 判断类型,动态设置标题
  386. if (popupItem.testingStatus === 1) {
  387. this.currentReasonTitle = '质量问题'
  388. } else if (popupItem.testingStatus === 2) {
  389. this.currentReasonTitle = '不可回收'
  390. } else {
  391. this.currentReasonTitle = this.getGoodsName(popupItem.shopId || popupItem.id)
  392. }
  393. // 只使用testingImages字段
  394. if (popupItem.testingImages && popupItem.testingImages.trim() !== '') {
  395. this.reasonImages = popupItem.testingImages.split(',').filter(img => img.trim() !== '')
  396. } else {
  397. this.reasonImages = []
  398. }
  399. // 不在这里设置reasonChecked
  400. this.selectReasonForUnqualified(popupItem, type)
  401. this.lockScroll()
  402. this.$refs.reasonPopup.open()
  403. },
  404. selectReasonForQualified(commonItem, parentItem) {
  405. this.currentReasonItem = commonItem
  406. this.currentReasonTitle = this.getGoodsName(parentItem.shopId || parentItem.id)
  407. // 只使用testingImages字段
  408. if (commonItem.testingImages && commonItem.testingImages.trim() !== '') {
  409. this.reasonImages = commonItem.testingImages.split(',').filter(img => img.trim() !== '')
  410. } else {
  411. this.reasonImages = []
  412. }
  413. // 使用从上一个页面传过来的categoryId
  414. this.selectReasonForUnqualified(commonItem, 0, parentItem.classId)
  415. this.lockScroll()
  416. this.$refs.reasonPopup.open()
  417. },
  418. selectReasonForUnqualified(item, type, classId = '') {
  419. console.log(item)
  420. // 根据类型确定classId
  421. if (!classId) {
  422. if (type === 1) {
  423. // 质量问题
  424. classId = ''
  425. } else if (type === 2) {
  426. // 不可回收
  427. classId = ''
  428. } else {
  429. // classId
  430. classId = item.classId || ''
  431. }
  432. }
  433. console.log('[selectReasonForUnqualified] classId:', classId, 'type:', type)
  434. this.$api('getcheckoutReasons', {
  435. classId: classId,
  436. type: type
  437. }, res => {
  438. if (res.code == 200) {
  439. this.reasonOptions = res.result;
  440. if (item.testingInstructions && item.testingInstructions.trim() !== '') {
  441. this.reasonChecked = item.testingInstructions.split(',').map(s => s.trim()).filter(Boolean)
  442. } else {
  443. this.reasonChecked = []
  444. }
  445. this.$nextTick(() => {
  446. this.reasonChecked = [...this.reasonChecked]
  447. console.log('[selectReasonForUnqualified] reasonOptions:', this.reasonOptions)
  448. console.log('[selectReasonForUnqualified] reasonChecked:', this.reasonChecked)
  449. })
  450. } else {
  451. console.error('[selectReasonForUnqualified] API返回错误:', res)
  452. uni.showToast({
  453. title: '获取理由列表失败',
  454. icon: 'none'
  455. })
  456. }
  457. }, err => {
  458. console.error('[selectReasonForUnqualified] API调用失败:', err)
  459. uni.showToast({
  460. title: '获取理由列表失败',
  461. icon: 'none'
  462. })
  463. })
  464. },
  465. closeReasonPopup() {
  466. this.unlockScroll()
  467. this.$refs.reasonPopup.close()
  468. },
  469. addReasonImg() {
  470. // 检查是否已达到最大数量限制
  471. if (this.reasonImages.length >= 9) {
  472. uni.showToast({
  473. title: '最多只能上传9张图片',
  474. icon: 'none'
  475. })
  476. return
  477. }
  478. uni.chooseMedia({
  479. count: 3 - this.reasonImages.length, // 剩余可选择数量
  480. mediaType: ['image'], // 只选择图片
  481. sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机
  482. maxDuration: 30,
  483. sizeType: [ 'compressed'],
  484. camera: 'back',
  485. success: (res) => {
  486. console.log('选择的图片:', res.tempFiles)
  487. // 显示上传进度
  488. uni.showLoading({
  489. title: '上传中...'
  490. })
  491. // 处理选择的图片
  492. const uploadPromises = res.tempFiles.map(file => {
  493. return this.uploadImageToOSS(file.tempFilePath)
  494. })
  495. // 批量上传
  496. Promise.all(uploadPromises).then(urls => {
  497. this.reasonImages = this.reasonImages.concat(urls)
  498. uni.hideLoading()
  499. uni.showToast({
  500. title: '上传成功',
  501. icon: 'success'
  502. })
  503. }).catch(error => {
  504. console.error('上传失败:', error)
  505. uni.hideLoading()
  506. uni.showToast({
  507. title: '上传失败,请重试',
  508. icon: 'none'
  509. })
  510. })
  511. },
  512. fail: (error) => {
  513. console.error('选择图片失败:', error)
  514. uni.showToast({
  515. title: '选择图片失败',
  516. icon: 'none'
  517. })
  518. }
  519. })
  520. },
  521. async uploadImageToOSS(filePath) {
  522. try {
  523. // 使用项目中的OSS上传工具
  524. const url = await OSS.ossUpload(filePath)
  525. return url
  526. } catch (error) {
  527. console.error('OSS上传失败:', error)
  528. throw error
  529. }
  530. },
  531. removeReasonImg(idx) {
  532. this.reasonImages.splice(idx, 1)
  533. },
  534. toggleReason(idx) {
  535. const val = this.reasonOptions[idx].reason.toString()
  536. const i = this.reasonChecked.indexOf(val)
  537. if (i > -1) {
  538. this.reasonChecked.splice(i, 1)
  539. } else {
  540. this.reasonChecked.push(val)
  541. }
  542. this.reasonChecked = [...this.reasonChecked]
  543. console.log('[toggleReason] reasonChecked:', this.reasonChecked)
  544. },
  545. saveReason() {
  546. if (this.currentReasonItem) {
  547. console.log('this.currentReasonItem', this.currentReasonItem)
  548. // 直接更新当前项
  549. this.currentReasonItem.testingInstructions = this.reasonChecked.join(',')
  550. this.currentReasonItem.testingImages = this.reasonImages.join(',')
  551. }
  552. this.closeReasonPopup()
  553. },
  554. lockScroll() {
  555. // 获取当前页面滚动位置
  556. uni.createSelectorQuery().selectViewport().scrollOffset((res) => {
  557. this.scrollTop = res.scrollTop
  558. }).exec()
  559. // 禁用页面滚动
  560. this.isPopupOpen = true
  561. },
  562. unlockScroll() {
  563. // 恢复页面滚动
  564. this.isPopupOpen = false
  565. // 恢复滚动位置
  566. this.$nextTick(() => {
  567. if (this.scrollTop > 0) {
  568. uni.pageScrollTo({
  569. scrollTop: this.scrollTop,
  570. duration: 0
  571. })
  572. }
  573. })
  574. },
  575. async onRefresh() {
  576. await this.refreshData && this.refreshData()
  577. },
  578. refreshData() {
  579. // 可根据实际需求刷新质检结果数据
  580. // 例如重新请求接口或重置数据
  581. },
  582. getItemBrand(id) {
  583. if (this.order && this.order.commonOrderList) {
  584. const orderItem = this.order.commonOrderList.find(item => item.id == id)
  585. return orderItem ? (orderItem.pinName || '') : ''
  586. }
  587. return ''
  588. },
  589. parseTestingInstructions() {
  590. return []
  591. },
  592. hasSelectedReason(item) {
  593. // console.log('item', item)
  594. // 检查该商品是否已选择理由或上传图片
  595. return (item.testingInstructions && item.testingInstructions.trim() !== '') ||
  596. (item.testingImages && item.testingImages.trim() !== '')
  597. },
  598. getBrandStyleInfo(item) {
  599. // 获取品牌款式信息
  600. const brandName = item.brandName || ''
  601. const styleName = item.styleName || ''
  602. if (brandName && styleName) {
  603. return `品牌:${brandName} | 款式:${styleName}`
  604. } else if (brandName) {
  605. return `品牌:${brandName}`
  606. } else if (styleName) {
  607. return `款式:${styleName}`
  608. } else {
  609. return this.getItemBrand(item.shopId)
  610. }
  611. },
  612. getQualifiedItemLabel(item, index) {
  613. // 生成合格产品的标签
  614. const goodsName = this.getGoodsName(item.shopId)
  615. const brandName = item.brandName || ''
  616. const styleName = item.styleName || ''
  617. if (brandName && styleName) {
  618. return `(${brandName}-${styleName})`
  619. } else if (brandName) {
  620. return `(${brandName})`
  621. } else {
  622. return `${goodsName}`
  623. }
  624. },
  625. syncPriceToInspectData() {
  626. // 同步合格产品金额 - 按品牌款式唯一标识
  627. this.qualifiedList.forEach(qualifiedItem => {
  628. // 使用uniqueKey或者shopId+pinId+styleId来匹配
  629. const inspectItem = this.inspectData.list.find(item => {
  630. if (qualifiedItem.uniqueKey && item.uniqueKey) {
  631. return item.uniqueKey === qualifiedItem.uniqueKey
  632. }
  633. // 备用匹配方式
  634. return item.shopId === qualifiedItem.shopId &&
  635. (item.pinId || '') === (qualifiedItem.pinId || '') &&
  636. (item.styleId || '') === (qualifiedItem.styleId || '') &&
  637. item.id !== 'unrecyclable' &&
  638. item.id !== 'quality_issue'
  639. })
  640. if (inspectItem) {
  641. inspectItem.price = parseFloat(qualifiedItem.total) || 0
  642. // 同步理由信息
  643. if (qualifiedItem.items && qualifiedItem.items.length > 0) {
  644. inspectItem.commonOrderList = qualifiedItem.items
  645. }
  646. }
  647. })
  648. // 同步不合格产品金额
  649. this.unqualifiedGroups.forEach(group => {
  650. const inspectItem = this.inspectData.list.find(item => item.id === 'quality_issue')
  651. if (inspectItem) {
  652. inspectItem.price = parseFloat(group.total) || 0
  653. }
  654. })
  655. // 同步不可回收产品金额
  656. this.unrecyclableList.forEach(unrecyclableItem => {
  657. const inspectItem = this.inspectData.list.find(item => item.id === 'unrecyclable')
  658. if (inspectItem) {
  659. inspectItem.price = parseFloat(unrecyclableItem.total) || 0
  660. }
  661. })
  662. }
  663. }
  664. }
  665. </script>
  666. <style lang="scss" scoped>
  667. .inspect-result-container {
  668. min-height: 100vh;
  669. background: #f8f8f8;
  670. display: flex;
  671. flex-direction: column;
  672. &.popup-open {
  673. overflow: hidden;
  674. height: 100vh;
  675. }
  676. }
  677. .nav-bar {
  678. display: flex;
  679. align-items: center;
  680. height: calc(150rpx + var(--status-bar-height));
  681. padding: 0 32rpx;
  682. padding-top: var(--status-bar-height);
  683. background: #fff;
  684. position: fixed;
  685. top: 0;
  686. left: 0;
  687. right: 0;
  688. z-index: 999;
  689. box-sizing: border-box;
  690. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
  691. .back {
  692. padding: 22rpx;
  693. margin-left: -22rpx;
  694. }
  695. .nav-title {
  696. flex: 1;
  697. text-align: center;
  698. font-size: 32rpx;
  699. font-weight: 500;
  700. color: #222;
  701. }
  702. .nav-icons {
  703. display: flex;
  704. align-items: center;
  705. gap: 12px;
  706. }
  707. }
  708. .main-content {
  709. margin-top: calc(200rpx + var(--status-bar-height));
  710. display: flex;
  711. flex-direction: column;
  712. background: none;
  713. padding-bottom: 120px;
  714. }
  715. .result-card {
  716. background: #fff;
  717. border-radius: 24px;
  718. box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
  719. margin: 0 24px 24px 24px;
  720. padding: 24px 24px 0 24px;
  721. }
  722. .card-title {
  723. font-size: 16px;
  724. font-weight: bold;
  725. color: #222;
  726. margin-bottom: 18px;
  727. }
  728. .card-title-row {
  729. display: flex;
  730. align-items: center;
  731. justify-content: space-between;
  732. margin-bottom: 18px;
  733. }
  734. .status-tag {
  735. background: #fff7e6;
  736. color: #ffb400;
  737. font-size: 12px;
  738. border-radius: 12px;
  739. padding: 2px 26rpx;
  740. font-weight: 400;
  741. height: 22px;
  742. display: flex;
  743. align-items: center;
  744. }
  745. .result-row,
  746. .result-group {
  747. margin-bottom: 18px;
  748. }
  749. .row-main {
  750. display: flex;
  751. align-items: center;
  752. .goods-name-section {
  753. display: flex;
  754. flex-direction: column;
  755. margin-right: 8px;
  756. .goods-name {
  757. font-size: 26rpx;
  758. font-weight: bold;
  759. color: #222;
  760. line-height: 1.2;
  761. }
  762. .goods-brand {
  763. font-size: 22rpx;
  764. color: #999;
  765. font-weight: normal;
  766. line-height: 1.2;
  767. margin-top: 2px;
  768. }
  769. }
  770. .row-name {
  771. font-size: 26rpx;
  772. font-weight: bold;
  773. color: #222;
  774. margin-right: 8px;
  775. }
  776. .row-count {
  777. font-size: 13px;
  778. color: #888;
  779. margin-right: 8px;
  780. }
  781. .row-total {
  782. font-size: 26rpx;
  783. color: #222;
  784. font-weight: bold;
  785. margin-left: auto;
  786. }
  787. }
  788. .price-input-row {
  789. display: flex;
  790. align-items: center;
  791. margin-top: 12px;
  792. margin-bottom: 8px;
  793. .price-label {
  794. font-size: 26rpx;
  795. color: #888;
  796. min-width: 80px;
  797. flex-shrink: 0;
  798. }
  799. .price-input {
  800. flex: 1;
  801. height: 40px;
  802. border-radius: 12px;
  803. background: #f6f6f6;
  804. border: none;
  805. font-size: 16px;
  806. color: #222;
  807. padding: 0 16px;
  808. margin-left: 12px;
  809. font-weight: bold;
  810. }
  811. }
  812. .row-reason {
  813. display: flex;
  814. align-items: center;
  815. margin-top: 8px;
  816. .reason-label {
  817. font-size: 13px;
  818. color: #bbb;
  819. min-width: 80px;
  820. }
  821. .reason-input {
  822. flex: 1;
  823. height: 36px;
  824. border-radius: 12px;
  825. background: #f6f6f6;
  826. border: none;
  827. font-size: 26rpx;
  828. color: #222;
  829. padding-left: 12px;
  830. margin-left: 8px;
  831. }
  832. .reason-select {
  833. flex: 1;
  834. display: flex;
  835. align-items: center;
  836. height: 36px;
  837. border-radius: 12px;
  838. background: #f6f6f6;
  839. font-size: 26rpx;
  840. color: #bbb;
  841. padding-left: 12px;
  842. margin-left: 8px;
  843. justify-content: flex-end;
  844. }
  845. .reason-placeholder {
  846. color: #bbb;
  847. font-size: 26rpx;
  848. flex-shrink: 0;
  849. &.selected {
  850. color: #52c41a;
  851. font-weight: 500;
  852. }
  853. }
  854. }
  855. .info-card {
  856. background: #fff;
  857. border-radius: 24px;
  858. box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
  859. margin: 0 24px 24px 24px;
  860. padding: 24px 24px 0 24px;
  861. }
  862. .info-row {
  863. display: flex;
  864. align-items: center;
  865. margin-bottom: 16px;
  866. .info-label {
  867. font-size: 13px;
  868. color: #bbb;
  869. min-width: 80px;
  870. }
  871. .info-value {
  872. font-size: 13px;
  873. color: #222;
  874. margin-left: 8px;
  875. }
  876. .copy-btn {
  877. color: #ffb400;
  878. margin-left: 8px;
  879. }
  880. }
  881. .footer-btns {
  882. position: fixed;
  883. left: 0;
  884. right: 0;
  885. bottom: 0;
  886. background: #fff;
  887. display: flex;
  888. gap: 16px;
  889. padding: 12px 16px 24px 16px;
  890. z-index: 101;
  891. .btn-outline {
  892. flex: 1;
  893. height: 40px;
  894. border-radius: 16px;
  895. border: 1px solid #ffe09a;
  896. color: #ffb400;
  897. background: #fff0d2;
  898. font-size: 15px;
  899. font-weight: 500;
  900. box-shadow: none;
  901. padding: 0 18px;
  902. }
  903. .btn-main {
  904. flex: 1;
  905. height: 40px;
  906. border-radius: 16px;
  907. background: linear-gradient(90deg, #ffd01e 0%, #ffac04 100%);
  908. color: #fff;
  909. border: none;
  910. font-size: 15px;
  911. font-weight: 500;
  912. box-shadow: none;
  913. padding: 0 18px;
  914. }
  915. }
  916. .reason-popup-wrapper {
  917. z-index: 10000 !important;
  918. }
  919. .reason-popup-wrapper .uni-popup__wrapper {
  920. z-index: 10000 !important;
  921. }
  922. .reason-popup {
  923. background: #fff;
  924. border-radius: 24px 24px 0 0;
  925. padding: 0;
  926. height: 75vh;
  927. position: relative;
  928. z-index: 9999;
  929. .popup-header {
  930. display: flex;
  931. align-items: center;
  932. justify-content: center;
  933. height: 60px;
  934. border-bottom: 1px solid #f0f0f0;
  935. position: relative;
  936. .popup-close {
  937. position: absolute;
  938. left: 20px;
  939. color: #666;
  940. font-size: 16px;
  941. }
  942. .popup-title {
  943. font-size: 18px;
  944. font-weight: 600;
  945. color: #222;
  946. }
  947. }
  948. .popup-content {
  949. height: calc(75vh - 60px - 82px);
  950. overflow-y: auto;
  951. }
  952. .popup-section {
  953. padding: 0 20px 24px 20px;
  954. &:first-child {
  955. padding-top: 24px;
  956. }
  957. .section-label {
  958. font-size: 16px;
  959. color: #222;
  960. margin-bottom: 16px;
  961. display: block;
  962. font-weight: 500;
  963. }
  964. .img-list {
  965. display: flex;
  966. flex-wrap: wrap;
  967. gap: 20rpx;
  968. margin-bottom: 32px;
  969. .img-item {
  970. width: 150rpx;
  971. height: 150rpx;
  972. border-radius: 12px;
  973. background: #f8f8f8;
  974. position: relative;
  975. .img {
  976. width: 100%;
  977. height: 100%;
  978. border-radius: 24rpx;
  979. object-fit: cover;
  980. }
  981. .img-del {
  982. position: absolute;
  983. top: -6px;
  984. right: -6px;
  985. width: 48rpx;
  986. height: 48rpx;
  987. background: rgba(0, 0, 0, 0.6);
  988. color: #fff;
  989. border-radius: 50%;
  990. text-align: center;
  991. line-height: 48rpx;
  992. font-size: 16px;
  993. font-weight: bold;
  994. }
  995. &.add {
  996. display: flex;
  997. align-items: center;
  998. justify-content: center;
  999. background: #f8f8f8;
  1000. border: 2px dashed #ddd;
  1001. color: #bbb;
  1002. }
  1003. }
  1004. }
  1005. .reason-row {
  1006. display: flex;
  1007. align-items: center;
  1008. padding: 16px 0;
  1009. border-bottom: 1px solid #f0f0f0;
  1010. &:last-child {
  1011. border-bottom: none;
  1012. }
  1013. .checkbox {
  1014. width: 20px;
  1015. height: 20px;
  1016. border-radius: 4px;
  1017. border: 2px solid #ddd;
  1018. margin-right: 12px;
  1019. background: #fff;
  1020. position: relative;
  1021. &.checked {
  1022. border-color: #ffb400;
  1023. background: #ffb400;
  1024. &::after {
  1025. content: '✓';
  1026. position: absolute;
  1027. top: 50%;
  1028. left: 50%;
  1029. transform: translate(-50%, -50%);
  1030. color: #fff;
  1031. font-size: 12px;
  1032. font-weight: bold;
  1033. }
  1034. }
  1035. }
  1036. .reason-text {
  1037. font-size: 16px;
  1038. color: #222;
  1039. line-height: 1.4;
  1040. }
  1041. }
  1042. }
  1043. .popup-save-btn {
  1044. position: fixed;
  1045. bottom: 34px;
  1046. left: 20px;
  1047. right: 20px;
  1048. height: 48px;
  1049. border-radius: 24px;
  1050. background: linear-gradient(90deg, #ffd01e 0%, #ffac04 100%);
  1051. color: #fff;
  1052. font-size: 18px;
  1053. font-weight: 600;
  1054. border: none;
  1055. display: flex;
  1056. align-items: center;
  1057. justify-content: center;
  1058. box-shadow: 0 4px 12px rgba(255, 172, 4, 0.3);
  1059. }
  1060. }
  1061. </style>