Selaa lähdekoodia

售后上传图片

shish 18 tuntia sitten
vanhempi
commit
c436f054c3
2 muutettua tiedostoa jossa 307 lisäystä ja 6 poistoa
  1. 62 1
      hdApp/src/admin/order/refundDetail.vue
  2. 245 5
      mallApp/src/pages/order/refund.vue

+ 62 - 1
hdApp/src/admin/order/refundDetail.vue

@@ -1,6 +1,6 @@
 <!--
 <!--
   销售单售后详情
   销售单售后详情
-  用途:refundList / freeRefundList 点进查看;待审核的商城顾客申请可在本页通过/驳回。
+  用途:refundList / freeRefundList 点进查看;待审核的商城顾客申请可在本页通过/驳回;可预览顾客上传售后图
   审核通过后:线下等待落地进 forwardResult 选手选;已返充进凭证;线上/余额/挂账原路则刷新详情。
   审核通过后:线下等待落地进 forwardResult 选手选;已返充进凭证;线上/余额/挂账原路则刷新详情。
 -->
 -->
 <template>
 <template>
@@ -102,6 +102,21 @@
 				<view class="refund-label">备注信息</view>
 				<view class="refund-label">备注信息</view>
 				<view class="refund-value refund-value--multiline">{{ remark }}</view>
 				<view class="refund-value refund-value--multiline">{{ remark }}</view>
 			</view>
 			</view>
+
+			<!-- 顾客上传的售后图,对齐 ghsApp refundDetail -->
+			<view class="refund-section refund-section--images" v-if="info.smallImgList && !$util.isEmpty(info.smallImgList)">
+				<view class="refund-label refund-section__title">售后图片</view>
+				<view class="refund-img-grid">
+					<image
+						v-for="(itemImg, indexImg) in info.smallImgList"
+						:key="indexImg"
+						class="refund-img-item"
+						:src="itemImg"
+						mode="aspectFill"
+						@click="showBig(indexImg)"
+					></image>
+				</view>
+			</view>
 		</view>
 		</view>
 
 
 		<view class="refund-actions">
 		<view class="refund-actions">
@@ -427,6 +442,31 @@ export default {
 					mode: 'repost'
 					mode: 'repost'
 				})
 				})
 			})
 			})
+		},
+		/** 预览售后图片:去掉 m_fill,避免滑动下一张一直加载 */
+		showBig(index) {
+			const urls = this.getPreviewImageUrls()
+			if (!urls.length) {
+				return
+			}
+			const safeIndex = index >= 0 && index < urls.length ? index : 0
+			uni.previewImage({
+				urls,
+				current: urls[safeIndex]
+			})
+		},
+		getPreviewImageUrls() {
+			const source = this.info.smallImgList && !this.$util.isEmpty(this.info.smallImgList)
+				? this.info.smallImgList
+				: (this.info.bigImgList || [])
+			return source.map(url => this.normalizePreviewUrl(url)).filter(url => !!url)
+		},
+		normalizePreviewUrl(url) {
+			if (!url) {
+				return ''
+			}
+			const base = String(url).split('?')[0]
+			return base + '?x-oss-process=image/resize,w_1200'
 		}
 		}
 	}
 	}
 }
 }
@@ -522,8 +562,14 @@ export default {
 	border-bottom: 1upx solid #f0f2f5;
 	border-bottom: 1upx solid #f0f2f5;
 
 
 	&__title {
 	&__title {
+		width: auto;
 		margin-bottom: 16upx;
 		margin-bottom: 16upx;
 	}
 	}
+
+	&--images {
+		border-bottom: none;
+		padding-bottom: 8upx;
+	}
 }
 }
 
 
 .refund-product-list {
 .refund-product-list {
@@ -547,6 +593,21 @@ export default {
 	}
 	}
 }
 }
 
 
+.refund-img-grid {
+	display: flex;
+	flex-direction: row;
+	flex-wrap: wrap;
+}
+
+.refund-img-item {
+	width: 160upx;
+	height: 160upx;
+	border-radius: 12upx;
+	background: #f0f2f5;
+	margin-right: 16upx;
+	margin-top: 16upx;
+}
+
 .refund-actions {
 .refund-actions {
 	margin-top: 32upx;
 	margin-top: 32upx;
 	padding-bottom: 40upx;
 	padding-bottom: 40upx;

+ 245 - 5
mallApp/src/pages/order/refund.vue

@@ -1,6 +1,6 @@
-	<!--
+<!--
   商城订单申请售后页
   商城订单申请售后页
-  供订单详情「申请售后」进入;顾客勾选商品/金额后提交待审核申请,商家在 hdApp 审核
+  供订单详情「申请售后」进入;顾客勾选商品/金额、可选上传照片后提交待审核申请,商家在 hdApp 审核可见图片
   仅当存在待审核申请时锁进度页不可再提交;无待审核则直接进表单(含已取消/通过/驳回后再申)
   仅当存在待审核申请时锁进度页不可再提交;无待审核则直接进表单(含已取消/通过/驳回后再申)
   退货并退款金额:普通商品按勾选原价分摊会员折/满减;秒杀、团购按活动单价退,不再叠整单优惠
   退货并退款金额:普通商品按勾选原价分摊会员折/满减;秒杀、团购按活动单价退,不再叠整单优惠
   红包全额抵扣导致实付为 0 时仍可申请:提交 0 元,审核通过后退回红包
   红包全额抵扣导致实付为 0 时仍可申请:提交 0 元,审核通过后退回红包
@@ -27,10 +27,24 @@
 						<text class="label">驳回原因</text>
 						<text class="label">驳回原因</text>
 						<text class="value reject">{{ apply.rejectReason }}</text>
 						<text class="value reject">{{ apply.rejectReason }}</text>
 					</view>
 					</view>
-					<view class="status-row">
+			<view class="status-row" v-if="apply.addTime">
 						<text class="label">申请时间</text>
 						<text class="label">申请时间</text>
 						<text class="value">{{ apply.addTime || '' }}</text>
 						<text class="value">{{ apply.addTime || '' }}</text>
 					</view>
 					</view>
+					<!-- 已提交的售后图,便于顾客确认 -->
+					<view class="apply-imgs" v-if="apply.smallImgList && apply.smallImgList.length">
+						<text class="label">售后图片</text>
+						<view class="apply-img-grid">
+							<image
+								v-for="(img, idx) in apply.smallImgList"
+								:key="idx"
+								class="apply-img"
+								:src="img"
+								mode="aspectFill"
+								@tap="previewApplyImg(idx)"
+							/>
+						</view>
+					</view>
 				</view>
 				</view>
 			</view>
 			</view>
 			<view class="bottom-actions">
 			<view class="bottom-actions">
@@ -194,6 +208,40 @@
 				</view>
 				</view>
 			</view>
 			</view>
 
 
+			<!-- 售后图片:选填,提交后商家审核可见 -->
+			<view class="card-section">
+				<view class="section-title">
+					<text class="title-text">售后图片</text>
+					<text class="title-sub">选填,最多9张</text>
+				</view>
+				<view class="upload-body">
+					<view class="upload-grid">
+						<view
+							v-for="(img, idx) in refundImgShow"
+							:key="idx"
+							class="upload-item"
+						>
+							<image
+								class="upload-item__img"
+								:src="img"
+								mode="aspectFill"
+								@tap="previewUploadImg(idx)"
+							/>
+							<view class="upload-item__del" @tap.stop="removeUploadImg(idx)">×</view>
+						</view>
+						<view
+							v-if="uploadRefundImg.length < 9"
+							class="upload-item upload-item--add"
+							@tap="chooseRefundImg"
+						>
+							<text class="upload-add-icon">+</text>
+							<text class="upload-add-text">上传</text>
+						</view>
+					</view>
+					<text class="upload-tip">建议拍摄问题商品照片,便于商家更快审核</text>
+				</view>
+			</view>
+
 			<view class="bottom-actions">
 			<view class="bottom-actions">
 				<button class="action-btn cancel-btn" @tap="onFormCancel">取消</button>
 				<button class="action-btn cancel-btn" @tap="onFormCancel">取消</button>
 				<button class="action-btn confirm-btn" @tap="askSubmit">提交申请</button>
 				<button class="action-btn confirm-btn" @tap="askSubmit">提交申请</button>
@@ -204,6 +252,8 @@
 
 
 <script>
 <script>
 import { getRefundData, createRefund, cancelRefund } from '@/api/refund'
 import { getRefundData, createRefund, cancelRefund } from '@/api/refund'
+import constant from '@/constant/index'
+import { TOKEN_STORAGE_KEY } from '@/constant/storageKeys'
 
 
 export default {
 export default {
 	name: 'orderRefund',
 	name: 'orderRefund',
@@ -218,7 +268,12 @@ export default {
 			apply: null,
 			apply: null,
 			canApply: 0,
 			canApply: 0,
 			refundDeadlineTip: '',
 			refundDeadlineTip: '',
-			submitting: false
+			submitting: false,
+			/** 展示用完整 URL */
+			refundImgShow: [],
+			/** 提交用短路径 */
+			uploadRefundImg: [],
+			uploadingImg: false
 		}
 		}
 	},
 	},
 	computed: {
 	computed: {
@@ -564,13 +619,17 @@ export default {
 				price: this.refundMoney,
 				price: this.refundMoney,
 				remark: this.remark,
 				remark: this.remark,
 				refundType: this.refundType,
 				refundType: this.refundType,
-				product: JSON.stringify(selectProduct || [])
+				product: JSON.stringify(selectProduct || []),
+				// 短路径 JSON,后端写入 xhHdRefund.imgList
+				imgList: JSON.stringify(this.uploadRefundImg || [])
 			}
 			}
 			createRefund(params).then(res => {
 			createRefund(params).then(res => {
 				uni.hideLoading()
 				uni.hideLoading()
 				this.submitting = false
 				this.submitting = false
 				if (res.code == 1) {
 				if (res.code == 1) {
 					this.$msg(res.msg || '提交成功')
 					this.$msg(res.msg || '提交成功')
+					this.refundImgShow = []
+					this.uploadRefundImg = []
 					this.init()
 					this.init()
 				}
 				}
 			}).catch(() => {
 			}).catch(() => {
@@ -578,6 +637,99 @@ export default {
 				this.submitting = false
 				this.submitting = false
 			})
 			})
 		},
 		},
+		/**
+		 * 选择并上传售后图:走 /upload/save-file,短路径入库、展示用 smallUrl
+		 */
+		chooseRefundImg() {
+			if (this.uploadingImg) {
+				return
+			}
+			const remain = 9 - this.uploadRefundImg.length
+			if (remain <= 0) {
+				this.$msg('最多上传9张')
+				return
+			}
+			uni.chooseImage({
+				count: remain,
+				sizeType: ['compressed'],
+				sourceType: ['album', 'camera'],
+				success: (res) => {
+					const paths = res.tempFilePaths || []
+					this.uploadImageFiles(paths)
+				}
+			})
+		},
+		/** 串行上传,避免并发打满接口 */
+		uploadImageFiles(paths) {
+			if (!paths.length) {
+				return
+			}
+			this.uploadingImg = true
+			uni.showLoading({ title: '上传中', mask: true })
+			const token = uni.getStorageSync(TOKEN_STORAGE_KEY) || uni.getStorageSync('token') || ''
+			const uploadOne = (filePath) => new Promise((resolve, reject) => {
+				uni.uploadFile({
+					url: constant.hostUrl + '/upload/save-file',
+					filePath,
+					name: 'file',
+					header: { token },
+					success: (uploadRes) => {
+						try {
+							const body = typeof uploadRes.data === 'string'
+								? JSON.parse(uploadRes.data)
+								: uploadRes.data
+							if (body && body.code == 1 && body.data) {
+								const shortUrl = body.data.shortUrl || ''
+								const showUrl = body.data.smallUrl || body.data.url || ''
+								if (shortUrl) {
+									this.uploadRefundImg.push(shortUrl)
+									this.refundImgShow.push(showUrl || shortUrl)
+								}
+								resolve()
+								return
+							}
+							reject(new Error((body && body.msg) || '上传失败'))
+						} catch (e) {
+							reject(e)
+						}
+					},
+					fail: (err) => reject(err)
+				})
+			})
+			paths.reduce((chain, p) => chain.then(() => uploadOne(p)), Promise.resolve())
+				.catch(() => {
+					this.$msg('图片上传失败')
+				})
+				.finally(() => {
+					this.uploadingImg = false
+					uni.hideLoading()
+				})
+		},
+		removeUploadImg(index) {
+			this.refundImgShow.splice(index, 1)
+			this.uploadRefundImg.splice(index, 1)
+		},
+		previewUploadImg(index) {
+			if (!this.refundImgShow.length) {
+				return
+			}
+			uni.previewImage({
+				urls: this.refundImgShow,
+				current: this.refundImgShow[index]
+			})
+		},
+		/** 进度页预览已提交图片 */
+		previewApplyImg(index) {
+			const list = (this.apply && this.apply.smallImgList) || []
+			if (!list.length) {
+				return
+			}
+			const urls = list.map(url => String(url).split('?')[0] + '?x-oss-process=image/resize,w_1200')
+			uni.previewImage({
+				urls,
+				current: urls[index] || urls[0]
+			})
+		},
 		doCancel() {
 		doCancel() {
 			if (!this.apply || !this.apply.id) {
 			if (!this.apply || !this.apply.id) {
 				return
 				return
@@ -636,11 +788,19 @@ export default {
 .section-title {
 .section-title {
 	padding: 30upx 30upx 20upx;
 	padding: 30upx 30upx 20upx;
 	border-bottom: 1upx solid #f0f0f0;
 	border-bottom: 1upx solid #f0f0f0;
+	display: flex;
+	align-items: center;
 	.title-text {
 	.title-text {
 		font-size: 32upx;
 		font-size: 32upx;
 		font-weight: 600;
 		font-weight: 600;
 		color: #333;
 		color: #333;
 	}
 	}
+	.title-sub {
+		margin-left: 16upx;
+		font-size: 24upx;
+		color: #999;
+		font-weight: 400;
+	}
 }
 }
 
 
 .refund-type-buttons {
 .refund-type-buttons {
@@ -815,6 +975,86 @@ export default {
 	box-sizing: border-box;
 	box-sizing: border-box;
 }
 }
 
 
+.upload-body {
+	padding: 20upx 30upx 30upx;
+}
+.upload-grid {
+	display: flex;
+	flex-direction: row;
+	flex-wrap: wrap;
+}
+.upload-item {
+	position: relative;
+	width: 160upx;
+	height: 160upx;
+	margin-right: 16upx;
+	margin-bottom: 16upx;
+	border-radius: 12upx;
+	overflow: hidden;
+	background: #f5f6f8;
+	box-sizing: border-box;
+}
+.upload-item__img {
+	width: 160upx;
+	height: 160upx;
+}
+.upload-item__del {
+	position: absolute;
+	top: 0;
+	right: 0;
+	width: 40upx;
+	height: 40upx;
+	line-height: 36upx;
+	text-align: center;
+	background: rgba(0, 0, 0, 0.55);
+	color: #fff;
+	font-size: 28upx;
+	border-radius: 0 0 0 12upx;
+}
+.upload-item--add {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+	border: 2upx dashed #d0d3d8;
+	background: #fafbfc;
+}
+.upload-add-icon {
+	font-size: 48upx;
+	color: #999;
+	line-height: 1;
+}
+.upload-add-text {
+	margin-top: 8upx;
+	font-size: 22upx;
+	color: #999;
+}
+.upload-tip {
+	display: block;
+	margin-top: 8upx;
+	font-size: 24upx;
+	color: #999;
+	line-height: 1.4;
+}
+
+.apply-imgs {
+	margin-top: 20upx;
+}
+.apply-img-grid {
+	display: flex;
+	flex-direction: row;
+	flex-wrap: wrap;
+	margin-top: 12upx;
+}
+.apply-img {
+	width: 140upx;
+	height: 140upx;
+	margin-right: 12upx;
+	margin-bottom: 12upx;
+	border-radius: 10upx;
+	background: #f0f2f5;
+}
+
 .status-block {
 .status-block {
 	padding: 30upx;
 	padding: 30upx;
 	.status-tag {
 	.status-tag {