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

1266 lines
37 KiB

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