Bladeren bron

feat(refund): 统一商城售后申请与审核入口

- 将花店端商城售后审核合并到统一退款列表与详情,并支持通过、驳回及商品快照展示
- 商城订单详情补充查看最新售后申请入口,返回页面时同步申请状态
- 商城店铺底部导航改用重定向,避免重复页面堆栈
shizhongqi 23 uur geleden
bovenliggende
commit
a56fd50e08

+ 1 - 2
hdApp/src/admin/home/apply.vue

@@ -98,8 +98,7 @@ export default {
             { name: '配送方式', icon: 'delivery-outline', url: '/admin/home/psMethod', pf: 1 },
             { name: '配置范围', icon: 'address', url: '/admin/shop/sk', pf: 1 },
             { name: '分销规则', icon: 'settings-filled', url: '/admin/home/distributionRule', pf: 1 },
-            { name: '分销报表', icon: 'stats-pie-filled', url: '/admin/home/distributionReport', pf: 1 },
-            { name: '退款审核', icon: 'order-filled', url: '/admin/order/mallRefundList', pf: 1 }
+            { name: '分销报表', icon: 'stats-pie-filled', url: '/admin/home/distributionReport', pf: 1 }
           ]
         },
         {

+ 0 - 7
hdApp/src/admin/order/detail.vue

@@ -659,13 +659,6 @@ export default {
     goRefund() {
       this.$util.pageTo({url: '/admin/order/refund?id=' + this.option.id})
     },
-    /** 跳转商城顾客售后申请列表(可按当前订单号过滤) */
-    goMallRefundList() {
-      this.$util.pageTo({
-        url: '/admin/order/mallRefundList',
-        query: { orderSn: this.orderInfo.orderSn || '' }
-      })
-    },
     init() {
       return this.getDetailInfo()
     },

+ 0 - 409
hdApp/src/admin/order/mallRefundDetail.vue

@@ -1,409 +0,0 @@
-<!--
-  商城顾客售后申请详情 / 审核页
-  用途:mallRefundList 进入;样式对齐 ghs pagesOrder/refundDetail;待审核时可驳回或通过。
--->
-<template>
-	<view class="refund-page">
-		<view class="refund-card" v-if="info.id">
-			<view class="refund-row">
-				<view class="refund-label">退款状态</view>
-				<view class="refund-value">
-					<text :class="statusClass">{{ info.statusText || statusText }}</text>
-				</view>
-			</view>
-
-			<view class="refund-row">
-				<view class="refund-label">订单信息</view>
-				<view class="refund-value">
-					<text class="refund-link refund-link--bold" @click="goOrder">{{ info.orderSn || '查看订单' }}</text>
-				</view>
-			</view>
-
-			<view class="refund-row">
-				<view class="refund-label">退款方式</view>
-				<view class="refund-value">{{ Number(info.refundType) === 1 ? '退货并退款' : '仅退款' }}</view>
-			</view>
-
-			<view class="refund-section" v-if="!$util.isEmpty(productList)">
-				<view class="refund-label refund-section__title">退货商品</view>
-				<view class="refund-product-list">
-					<view class="refund-product-item" v-for="(p, idx) in productList" :key="idx">
-						<view class="refund-product-item__name">
-							{{ p.name || ('商品' + p.productId) }}
-							¥{{ parseFloat(p.unitPrice) || 0 }}×{{ p.num }}{{ p.unitName || (p.property == 0 ? '份' : '') }}
-						</view>
-					</view>
-				</view>
-			</view>
-
-			<view class="refund-row">
-				<view class="refund-label">退款金额</view>
-				<view class="refund-value">
-					<text class="refund-amount">¥{{ parseFloat(info.refundPrice) || 0 }}</text>
-				</view>
-			</view>
-
-			<view class="refund-row">
-				<view class="refund-label">申请时间</view>
-				<view class="refund-value">{{ info.addTime ? String(info.addTime).substr(5, 11) : (info.addTime || '') }}</view>
-			</view>
-
-			<view class="refund-row" v-if="info.auditAdminName">
-				<view class="refund-label">审核人</view>
-				<view class="refund-value">{{ info.auditAdminName }}</view>
-			</view>
-
-			<view class="refund-row" v-if="info.auditTime">
-				<view class="refund-label">审核时间</view>
-				<view class="refund-value">{{ info.auditTime }}</view>
-			</view>
-
-			<view class="refund-row refund-row--top" v-if="info.status == 2 && !$util.isEmpty(info.rejectReason)">
-				<view class="refund-label">驳回原因</view>
-				<view class="refund-value refund-value--multiline">{{ info.rejectReason }}</view>
-			</view>
-
-			<view class="refund-row refund-row--top" v-if="!$util.isEmpty(info.remark)">
-				<view class="refund-label">顾客备注</view>
-				<view class="refund-value refund-value--multiline">{{ info.remark }}</view>
-			</view>
-		</view>
-
-		<view class="refund-actions">
-			<button
-				v-if="info.status == 0"
-				class="admin-button-com big refund-actions__btn refund-actions__btn--pass"
-				@click="askPass"
-			>通过</button>
-			<button
-				v-if="info.status == 0"
-				class="admin-button-com big refund-actions__btn refund-actions__btn--reject"
-				@click="openReject"
-			>驳回</button>
-			<button class="admin-button-com big default refund-actions__btn" @click="goBack">返回</button>
-		</view>
-
-		<!-- 驳回原因弹层:样式对齐 ghs refund-popup -->
-		<view class="reject-mask" v-if="showReject" @click="showReject = false">
-			<view class="refund-popup" @click.stop="">
-				<textarea
-					v-model="rejectReason"
-					class="refund-popup__textarea"
-					placeholder="驳回原因..."
-					placeholder-class="tui-placeholder"
-				/>
-				<view class="refund-popup__btns">
-					<button class="admin-button-com big default refund-popup__btn" @click="showReject = false">取消</button>
-					<button class="admin-button-com big blue refund-popup__btn refund-popup__btn--confirm" @click="confirmReject">确认</button>
-				</view>
-			</view>
-		</view>
-	</view>
-</template>
-
-<script>
-import { mallApplyDetail, mallApplyPass, mallApplyReject } from '@/api/refund'
-
-export default {
-	name: 'mallRefundDetail',
-	data() {
-		return {
-			info: {},
-			productList: [],
-			busy: false,
-			showReject: false,
-			rejectReason: ''
-		}
-	},
-	computed: {
-		/** 退款状态文案 */
-		statusText() {
-			const map = { 0: '待审核', 1: '已通过', 2: '已驳回', 3: '已取消' }
-			return map[this.info.status] || ''
-		},
-		/** 退款状态样式,对齐 ghs */
-		statusClass() {
-			const classMap = {
-				0: 'refund-status refund-status--pending',
-				1: 'refund-status refund-status--pass',
-				2: 'refund-status refund-status--reject',
-				3: 'refund-status refund-status--cancel'
-			}
-			return classMap[this.info.status] || 'refund-status'
-		}
-	},
-	methods: {
-		/** 拉取商城售后申请详情 */
-		init() {
-			const id = this.option.id
-			if (!id) {
-				return
-			}
-			uni.showLoading({ mask: true })
-			mallApplyDetail({ id }).then(res => {
-				uni.hideLoading()
-				if (res.code != 1) {
-					return
-				}
-				this.info = res.data || {}
-				this.productList = this.info.productList || []
-			}).catch(() => {
-				uni.hideLoading()
-			})
-		},
-		goOrder() {
-			if (!this.info.orderId) {
-				this.$msg('没有关联订单')
-				return
-			}
-			this.$util.pageTo({ url: '/admin/order/detail?id=' + this.info.orderId })
-		},
-		goBack() {
-			uni.navigateBack()
-		},
-		openReject() {
-			this.showReject = true
-		},
-		askPass() {
-			if (this.busy) {
-				return
-			}
-			this.$util.confirmModal(
-				{ content: '确认通过并退款 ¥' + (parseFloat(this.info.refundPrice) || 0) + '?' },
-				() => {
-					this.doPass()
-				}
-			)
-		},
-		doPass() {
-			this.busy = true
-			uni.showLoading({ mask: true, title: '处理中' })
-			mallApplyPass({ id: this.info.id }).then(res => {
-				uni.hideLoading()
-				this.busy = false
-				if (res.code == 1) {
-					this.$msg(res.msg || '已通过')
-					this.init()
-				}
-			}).catch(() => {
-				uni.hideLoading()
-				this.busy = false
-			})
-		},
-		confirmReject() {
-			const reason = (this.rejectReason || '').trim()
-			if (!reason) {
-				this.$msg('请填写驳回原因')
-				return
-			}
-			this.showReject = false
-			this.doReject(reason)
-		},
-		doReject(reason) {
-			this.busy = true
-			uni.showLoading({ mask: true })
-			mallApplyReject({ id: this.info.id, rejectReason: reason }).then(res => {
-				uni.hideLoading()
-				this.busy = false
-				if (res.code == 1) {
-					this.$msg(res.msg || '已驳回')
-					this.rejectReason = ''
-					this.init()
-				}
-			}).catch(() => {
-				uni.hideLoading()
-				this.busy = false
-			})
-		}
-	}
-}
-</script>
-
-<style lang="scss" scoped>
-/* 布局/色板对齐 ghsApp pagesOrder/refundDetail.vue */
-.refund-page {
-	min-height: 100vh;
-	padding: 24upx 24upx 40upx;
-	background: #f5f7fa;
-	box-sizing: border-box;
-}
-
-.refund-card {
-	background: #ffffff;
-	border-radius: 16upx;
-	padding: 8upx 24upx 24upx;
-	box-shadow: 0 2upx 12upx rgba(0, 0, 0, 0.04);
-}
-
-.refund-row {
-	display: flex;
-	flex-direction: row;
-	align-items: center;
-	padding: 22upx 0;
-	border-bottom: 1upx solid #f0f2f5;
-
-	&:last-child {
-		border-bottom: none;
-	}
-
-	&--top {
-		align-items: flex-start;
-	}
-}
-
-.refund-label {
-	width: 168upx;
-	flex-shrink: 0;
-	font-size: 28upx;
-	color: #909399;
-	line-height: 42upx;
-}
-
-.refund-value {
-	flex: 1;
-	min-width: 0;
-	font-size: 28upx;
-	color: #303133;
-	line-height: 42upx;
-
-	&--multiline {
-		word-break: break-all;
-	}
-}
-
-.refund-amount {
-	font-weight: 700;
-	color: #fa3534;
-}
-
-.refund-link {
-	font-size: 28upx;
-	color: #3385ff;
-	&--bold {
-		font-weight: 700;
-	}
-}
-
-.refund-status {
-	font-weight: 700;
-
-	&--pending {
-		color: #ff9900;
-	}
-
-	&--pass {
-		color: #19be6b;
-	}
-
-	&--reject {
-		color: #fa3534;
-	}
-
-	&--cancel {
-		color: #909399;
-	}
-}
-
-.refund-section {
-	padding: 22upx 0;
-	border-bottom: 1upx solid #f0f2f5;
-
-	&__title {
-		margin-bottom: 16upx;
-	}
-}
-
-.refund-product-list {
-	padding-left: 0;
-}
-
-.refund-product-item {
-	padding: 12upx 16upx;
-	margin-top: 12upx;
-	background: #f7f9fc;
-	border-radius: 12upx;
-
-	&:first-child {
-		margin-top: 0;
-	}
-
-	&__name {
-		font-size: 32upx;
-		color: #303133;
-		line-height: 40upx;
-	}
-}
-
-.refund-actions {
-	margin-top: 32upx;
-	padding-bottom: 40upx;
-
-	&__btn {
-		width: 100%;
-		margin: 0;
-
-		& + & {
-			margin-top: 20upx;
-		}
-
-		&--pass {
-			background-color: #19be6b;
-			border: 1upx solid #19be6b;
-			color: #ffffff;
-		}
-
-		&--reject {
-			background-color: #fa3534;
-			border: 1upx solid #fa3534;
-			color: #ffffff;
-		}
-	}
-}
-
-.reject-mask {
-	position: fixed;
-	left: 0;
-	right: 0;
-	top: 0;
-	bottom: 0;
-	background: rgba(0, 0, 0, 0.45);
-	z-index: 99;
-	display: flex;
-	align-items: center;
-	justify-content: center;
-}
-
-.refund-popup {
-	width: 86%;
-	background: #fff;
-	border-radius: 16upx;
-	padding: 30upx;
-	box-sizing: border-box;
-
-	&__textarea {
-		width: 100%;
-		box-sizing: border-box;
-		border: 1upx solid #dcdfe6;
-		border-radius: 12upx;
-		height: 160upx;
-		padding: 16upx;
-		font-size: 28upx;
-	}
-
-	&__btns {
-		display: flex;
-		flex-direction: row;
-		justify-content: center;
-		margin-top: 30upx;
-	}
-
-	&__btn {
-		width: 45%;
-		margin: 0;
-
-		&--confirm {
-			margin-left: 24upx;
-		}
-	}
-}
-</style>

+ 0 - 177
hdApp/src/admin/order/mallRefundList.vue

@@ -1,177 +0,0 @@
-<!--
-  商城顾客售后申请列表(商家端)
-  供订单详情「商城售后」入口进入;按状态筛选待审核/已通过/已驳回,点击进入审核详情
--->
-<template>
-	<view class="app-content">
-		<view class="tabs">
-			<view
-				v-for="(tab, idx) in tabs"
-				:key="idx"
-				class="tab-item"
-				:class="{ active: statusFilter === tab.value }"
-				@click="switchTab(tab.value)"
-			>{{ tab.label }}</view>
-		</view>
-
-		<block v-if="!$util.isEmpty(list.data)">
-			<view
-				class="flex-space list"
-				v-for="(item, index) in list.data"
-				:key="index"
-				@click="goDetail(item)"
-			>
-				<view class="label">
-					<text>日期:{{ item.addTime ? item.addTime.substr(5, 11) : '' }}</text>
-					<text>单号:{{ item.orderSn }}</text>
-					<text>类型:{{ item.refundType == 1 ? '退货并退款' : '仅退款' }}</text>
-					<text class="status-text" :class="'st-' + item.status">
-						{{ item.statusText || statusText(item.status) }}
-					</text>
-				</view>
-				<view class="flex val">
-					¥{{ parseFloat(item.refundPrice) }}
-					<view class="iconfont iconxiangyou"></view>
-				</view>
-			</view>
-		</block>
-		<block v-else>
-			<app-wrapper-empty title="暂无数据" :is-empty="$util.isEmpty(list.data)" />
-		</block>
-	</view>
-</template>
-
-<script>
-import { list } from '@/mixins'
-import { mallApplyList } from '@/api/refund'
-
-export default {
-	name: 'mallRefundList',
-	mixins: [list],
-	data() {
-		return {
-			statusFilter: '0',
-			tabs: [
-				{ label: '待审核', value: '0' },
-				{ label: '已通过', value: '1' },
-				{ label: '已驳回', value: '2' },
-				{ label: '全部', value: 'all' }
-			]
-		}
-	},
-	onPullDownRefresh() {
-		this.resetList()
-		this._list().then(() => {
-			uni.stopPullDownRefresh()
-		})
-	},
-	onReachBottom() {
-		if (!this.list.finished) {
-			this._list()
-		}
-	},
-	onShow() {
-		this.resetList()
-		this._list()
-	},
-	methods: {
-		statusText(status) {
-			const map = { 0: '待审核', 1: '已通过', 2: '已驳回', 3: '已取消' }
-			return map[status] || ''
-		},
-		switchTab(val) {
-			if (this.statusFilter === val) {
-				return
-			}
-			this.statusFilter = val
-			this.resetList()
-			this._list()
-		},
-		goDetail(item) {
-			this.$util.pageTo({ url: '/admin/order/mallRefundDetail', query: { id: item.id } })
-		},
-		_list() {
-			const params = {
-				page: this.list.page,
-				status: this.statusFilter
-			}
-			if (this.option.orderSn) {
-				params.orderSn = this.option.orderSn
-			}
-			return mallApplyList(params).then(res => {
-				this.completes(res)
-			})
-		}
-	}
-}
-</script>
-
-<style lang="scss" scoped>
-.app-content {
-	.tabs {
-		display: flex;
-		background: #fff;
-		padding: 10upx 0;
-		margin-bottom: 16upx;
-		.tab-item {
-			flex: 1;
-			text-align: center;
-			font-size: 28upx;
-			color: #666;
-			padding: 16upx 0;
-			position: relative;
-			&.active {
-				color: #09c567;
-				font-weight: 600;
-				&::after {
-					content: '';
-					position: absolute;
-					left: 50%;
-					bottom: 0;
-					width: 48upx;
-					height: 4upx;
-					background: #09c567;
-					transform: translateX(-50%);
-					border-radius: 2upx;
-				}
-			}
-		}
-	}
-	.list {
-		padding: 20upx 30upx;
-		margin-bottom: 20upx;
-		background: #ffffff;
-		.label {
-			position: relative;
-			line-height: 50upx;
-			text {
-				display: block;
-			}
-			.status-text {
-				position: absolute;
-				right: -425upx;
-				top: 0;
-				&.st-0 {
-					color: #fa8c16;
-				}
-				&.st-1 {
-					color: #09c567;
-				}
-				&.st-2 {
-					color: #ff4757;
-				}
-				&.st-3 {
-					color: #999;
-				}
-			}
-		}
-		.val {
-			font-size: 28upx;
-			.iconfont {
-				margin-left: 12upx;
-				color: #999999;
-			}
-		}
-	}
-}
-</style>

+ 180 - 4
hdApp/src/admin/order/refundDetail.vue

@@ -1,6 +1,6 @@
 <!--
   销售单售后详情
-  用途:refundList 点进查看;样式对齐 ghs pagesOrder/refundDetail;含当天/隔天、原单付款、退回状态
+  用途:refundList / freeRefundList 点进查看;待审核的商城顾客申请可在本页通过/驳回
 -->
 <template>
 	<view class="refund-page">
@@ -15,7 +15,7 @@
 				<view class="refund-value">{{ refundTypeLabel }}</view>
 			</view>
 
-			<view class="refund-section" v-if="!$util.isEmpty(itemList) || !$util.isEmpty(goodsList)">
+			<view class="refund-section" v-if="!$util.isEmpty(itemList) || !$util.isEmpty(goodsList) || showProductSnapshot">
 				<view class="refund-label refund-section__title">{{ isFreeRefund ? '花材明细' : '退货商品' }}</view>
 				<view class="refund-product-list">
 					<view class="refund-product-item" v-for="(res, i) in itemList" :key="'item-' + i">
@@ -28,6 +28,19 @@
 							{{ res.name }} ¥{{ parseFloat(res.unitPrice) }}×{{ res.num }}份=¥{{ parseFloat(res.price) }}
 						</view>
 					</view>
+					<!-- 待审核尚未写明细行,用顾客勾选快照撑起商品 -->
+					<block v-if="showProductSnapshot">
+						<view
+							class="refund-product-item"
+							v-for="(p, idx) in productList"
+							:key="idx"
+						>
+							<view class="refund-product-item__name">
+								{{ p.name || ('商品' + p.productId) }}
+								¥{{ parseFloat(p.unitPrice) || 0 }}×{{ p.num }}{{ p.unitName || (p.property == 0 ? '份' : '') }}
+							</view>
+						</view>
+					</block>
 				</view>
 			</view>
 
@@ -82,6 +95,11 @@
 				</view>
 			</view>
 
+			<view class="refund-row refund-row--top" v-if="Number(info.status) === 2 && !$util.isEmpty(info.rejectReason)">
+				<view class="refund-label">驳回原因</view>
+				<view class="refund-value refund-value--multiline">{{ info.rejectReason }}</view>
+			</view>
+
 			<view class="refund-row refund-row--top" v-if="!$util.isEmpty(remark)">
 				<view class="refund-label">备注信息</view>
 				<view class="refund-value refund-value--multiline">{{ remark }}</view>
@@ -89,6 +107,16 @@
 		</view>
 
 		<view class="refund-actions">
+			<button
+				v-if="canAudit"
+				class="admin-button-com big refund-actions__btn refund-actions__btn--pass"
+				@click="askPass"
+			>通过</button>
+			<button
+				v-if="canAudit"
+				class="admin-button-com big refund-actions__btn refund-actions__btn--reject"
+				@click="openReject"
+			>驳回</button>
 			<button
 				v-if="canFundAction"
 				class="admin-button-com big blue refund-actions__btn"
@@ -101,11 +129,27 @@
 			>查看凭证</button>
 			<button class="admin-button-com big default refund-actions__btn" @click="goBack">返回</button>
 		</view>
+
+		<view class="reject-mask" v-if="showReject" @click="showReject = false">
+			<view class="refund-popup" @click.stop="">
+				<textarea
+					:value="rejectReason"
+					class="refund-popup__textarea"
+					placeholder="驳回原因..."
+					placeholder-class="tui-placeholder"
+					@input="onRejectInput"
+				/>
+				<view class="refund-popup__btns">
+					<button class="admin-button-com big default refund-popup__btn" @click="showReject = false">取消</button>
+					<button class="admin-button-com big blue refund-popup__btn refund-popup__btn--confirm" @click="confirmReject">确认</button>
+				</view>
+			</view>
+		</view>
 	</view>
 </template>
 
 <script>
-import { refundDetail } from '@/api/refund/index'
+import { refundDetail, passRefund, rejectRefund } from '@/api/refund/index'
 
 export default {
 	name: 'orderRefundDetail',
@@ -114,9 +158,13 @@ export default {
 			info: {},
 			itemList: [],
 			goodsList: [],
+			productList: [],
 			refundPrice: 0,
 			refundType: 1,
-			remark: ''
+			remark: '',
+			busy: false,
+			showReject: false,
+			rejectReason: ''
 		}
 	},
 	computed: {
@@ -175,6 +223,16 @@ export default {
 		/** 已通过且返充余额时可查看凭证 */
 		canShowPoster() {
 			return Number(this.info.status) === 1 && Number(this.info.returnBalance) === 1
+		},
+		/** 待审核:商城顾客申请,超管可在本页通过/驳回 */
+		canAudit() {
+			return Number(this.info.status) === 0
+		},
+		/** 待审核尚未生成明细行时,用 product JSON 快照展示勾选商品 */
+		showProductSnapshot() {
+			return this.$util.isEmpty(this.itemList)
+				&& this.$util.isEmpty(this.goodsList)
+				&& !this.$util.isEmpty(this.productList)
 		}
 	},
 	onShow() {
@@ -191,6 +249,7 @@ export default {
 				this.info = info
 				this.itemList = res.data.itemList || []
 				this.goodsList = res.data.goodsList || []
+				this.productList = res.data.productList || []
 				this.refundType = info.refundType || 0
 				this.refundPrice = info.refundPrice || 0
 				this.remark = info.remark || res.data.remark || ''
@@ -206,6 +265,64 @@ export default {
 		goBack() {
 			uni.navigateBack()
 		},
+        onRejectInput(e) {
+			// textarea 不用 v-model,避免小程序编译 data-event-opts 问题
+			this.rejectReason = e.detail ? e.detail.value : e
+		},
+		openReject() {
+			this.showReject = true
+		},
+		askPass() {
+			if (this.busy) {
+				return
+			}
+			this.$util.confirmModal(
+				{ content: '确认通过并退款 ¥' + (parseFloat(this.info.refundPrice) || 0) + '?' },
+				() => {
+					this.doPass()
+				}
+			)
+		},
+		doPass() {
+			this.busy = true
+			uni.showLoading({ mask: true, title: '处理中' })
+			passRefund({ id: this.info.id }).then(res => {
+				uni.hideLoading()
+				this.busy = false
+				if (res.code == 1) {
+					this.$msg(res.msg || '已通过')
+					this.init()
+				}
+			}).catch(() => {
+				uni.hideLoading()
+				this.busy = false
+			})
+		},
+		confirmReject() {
+			const reason = (this.rejectReason || '').trim()
+			if (!reason) {
+				this.$msg('请填写驳回原因')
+				return
+			}
+			this.showReject = false
+			this.doReject(reason)
+		},
+		doReject(reason) {
+			this.busy = true
+			uni.showLoading({ mask: true })
+			rejectRefund({ id: this.info.id, rejectReason: reason }).then(res => {
+				uni.hideLoading()
+				this.busy = false
+				if (res.code == 1) {
+					this.$msg(res.msg || '已驳回')
+					this.rejectReason = ''
+					this.init()
+				}
+			}).catch(() => {
+				uni.hideLoading()
+				this.busy = false
+			})
+		},
 		/** 组装 forwardResult 所需参数 */
 		buildForwardPayload(mode) {
 			const info = this.info || {}
@@ -376,6 +493,65 @@ export default {
 		& + & {
 			margin-top: 20upx;
 		}
+
+		&--pass {
+			background-color: #19be6b;
+			border: 1upx solid #19be6b;
+			color: #ffffff;
+		}
+
+		&--reject {
+			background-color: #fa3534;
+			border: 1upx solid #fa3534;
+			color: #ffffff;
+		}
+	}
+}
+
+.reject-mask {
+	position: fixed;
+	left: 0;
+	right: 0;
+	top: 0;
+	bottom: 0;
+	background: rgba(0, 0, 0, 0.45);
+	z-index: 99;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.refund-popup {
+	width: 86%;
+	background: #fff;
+	border-radius: 16upx;
+	padding: 30upx;
+	box-sizing: border-box;
+
+	&__textarea {
+		width: 100%;
+		box-sizing: border-box;
+		border: 1upx solid #dcdfe6;
+		border-radius: 12upx;
+		height: 160upx;
+		padding: 16upx;
+		font-size: 28upx;
+	}
+
+	&__btns {
+		display: flex;
+		flex-direction: row;
+		justify-content: center;
+		margin-top: 30upx;
+	}
+
+	&__btn {
+		width: 45%;
+		margin: 0;
+
+		&--confirm {
+			margin-left: 24upx;
+		}
 	}
 }
 </style>

+ 6 - 16
hdApp/src/api/refund/index.js

@@ -17,24 +17,14 @@ export const refundList = data => {
 	return https.get('/refund/list', data)
 }
 
-/** 商城顾客售后申请列表(商家审核) */
-export const mallApplyList = data => {
-	return https.get('/refund/mall-apply-list', data)
+/** 审核通过并执行退款(商城顾客待审核申请) */
+export const passRefund = data => {
+	return https.post('/refund/pass', data)
 }
 
-/** 商城顾客售后申请详情 */
-export const mallApplyDetail = data => {
-	return https.get('/refund/mall-apply-detail', data)
-}
-
-/** 审核通过并执行退款 */
-export const mallApplyPass = data => {
-	return https.post('/refund/mall-apply-pass', data)
-}
-
-/** 驳回申请 */
-export const mallApplyReject = data => {
-	return https.post('/refund/mall-apply-reject', data)
+/** 驳回商城顾客售后申请 */
+export const rejectRefund = data => {
+	return https.post('/refund/reject', data)
 }
 
 /** 无原单退款(工作台「退款」:选客户+花材,不关联销售单) */

+ 1 - 3
hdApp/src/pages.json

@@ -474,7 +474,7 @@
 				{ "path": "freeRefund", "style": { "navigationBarTitleText": "退款选花材" } },
 				{ "path": "freeRefundConfirm", "style": { "navigationBarTitleText": "确认退款" } },
 				{ "path": "forwardResult", "style": { "navigationBarTitleText": "退款结果" } },
-				{ "path": "freeRefundList", "style": { "navigationBarTitleText": "退款记录", "enablePullDownRefresh": true } },
+				{ "path": "freeRefundList", "style": { "navigationBarTitleText": "退款/售后", "enablePullDownRefresh": true } },
 				{ "path": "freeRefundDetail", "style": { "navigationBarTitleText": "退款详情" } }
 			]
 		},
@@ -490,8 +490,6 @@
 				{ "path": "refund", "style": { "navigationBarTitleText": "退款" } },
 				{ "path": "refundList", "style": { "navigationBarTitleText": "售后记录" } },
 				{ "path": "refundDetail", "style": { "navigationBarTitleText": "售后详情" } },
-				{ "path": "mallRefundList", "style": { "navigationBarTitleText": "商城售后", "enablePullDownRefresh": true } },
-				{ "path": "mallRefundDetail", "style": { "navigationBarTitleText": "售后审核" } },
 				{ "path": "ship", "style": { "navigationBarTitleText": "发货" } },
 				{ "path": "fill-form", "style": { "navigationBarTitleText": "填写收花人" } },
 				{ "path": "debtList", "style": { "navigationBarTitleText": "待结订单" } },

+ 1 - 0
mallApp/src/api/order/index.js

@@ -21,6 +21,7 @@ export const getList = data => {
 
 /** *
  * 订单详情 m
+ * canApplyRefund:可否新开售后;refundApplyId:最新顾客申请,有值则详情页展示「查看申请」
  */
 export const getDetail = data => {
 	return https.get('/order/detail', data)

+ 4 - 4
mallApp/src/pages/home/components/shop-tab-bar.vue

@@ -94,7 +94,7 @@ export default {
       if (item.key === 'home') {
         this.$util.pageTo({
           url: '/pages/home/index',
-          type: 1,
+          type: 2,
           query: shopQuery
         })
         return
@@ -102,7 +102,7 @@ export default {
       if (item.key === 'category') {
         this.$util.pageTo({
           url: '/pages/home/shop-category',
-          type: 1,
+          type: 2,
           query: shopQuery
         })
         return
@@ -110,7 +110,7 @@ export default {
       if (item.key === 'cart') {
         this.$util.pageTo({
           url: '/pages/home/cart',
-          type: 1,
+          type: 2,
           query: shopQuery
         })
         return
@@ -119,7 +119,7 @@ export default {
       if (item.key === 'order') {
         this.$util.pageTo({
           url: '/pages/home/course',
-          type: 1,
+          type: 2,
           query: shopQuery
         })
       }

+ 98 - 6
mallApp/src/pages/order/detail.vue

@@ -136,9 +136,12 @@
       <template v-if="data.status == 1">
         <button class="button-com green big" @click="payFn">立即支付</button>
       </template>
-      <!-- 仅可售后时展示;不可售后(未付/取消/已退完/分销已结算/待审核等)隐藏 -->
-      <template v-else-if="showRefundBtn">
-        <button class="button-com default big refund-btn" @click="goRefund">申请售后</button>
+      <!-- 待审核不可再申请,但仍展示「查看申请」;可申请时保留「申请售后」 -->
+      <template v-else>
+        <view v-if="showViewRefundBtn || showRefundBtn" class="refund-actions" :class="{ 'is-double': showBothRefundBtns }">
+          <button v-if="showViewRefundBtn" class="button-com default big refund-btn" @click="goViewRefund">查看申请</button>
+          <button v-if="showRefundBtn" class="button-com default big refund-btn" @click="goRefund">申请售后</button>
+        </view>
       </template>
     </view>
   </view>
@@ -146,13 +149,14 @@
 <script>
 /**
  * 商城订单详情
- * 底部「申请售后」由详情接口 canApplyRefund / 本地兜底规则控制显隐
+ * 底部「申请售后」看 canApplyRefund;待审核/已有申请用 get-refund-data 的 apply 展示「查看申请」
  */
 import { mapGetters } from "vuex";
 import orderItem from "@/components/module/app-order-item";
 import ListModule from "@/components/module/app-order-list";
 import AppCouponSel from "@/components/app-coupon-sel";
 import { getDetail } from "@/api/order";
+import { getRefundData } from "@/api/refund";
 import { getShopUser } from "@/utils/auth";
 export default {
   name: "detail",
@@ -163,7 +167,11 @@ export default {
       data: {},
       discountArr: false,
       payWayInfo: "微信支付",
-      reachPeriodInfo: "上午"
+      reachPeriodInfo: "上午",
+      /** 首次 init 完成后再在 onShow 里重拉,避免进页连打两次 */
+      detailInited: false,
+      /** get-refund-data 的最新申请;待审核 status=0 也要能进查看 */
+      refundApply: null
     };
   },
   computed: {
@@ -195,6 +203,26 @@ export default {
       }
       return true;
     },
+    /**
+     * 最新顾客申请 id:详情 refundApplyId 或补拉的 apply;待审核 status=0 仍有效
+     */
+    currentRefundApplyId() {
+      const fromDetail = Number((this.data && this.data.refundApplyId) || 0);
+      if (fromDetail) {
+        return fromDetail;
+      }
+      return Number((this.refundApply && this.refundApply.id) || 0);
+    },
+    /**
+     * 是否展示查看申请:有申请记录就展示,含待审核
+     */
+    showViewRefundBtn() {
+      return !!this.currentRefundApplyId;
+    },
+    /** 同时有查看与申请时收窄按钮,避免底栏挤出 */
+    showBothRefundBtns() {
+      return this.showViewRefundBtn && this.showRefundBtn;
+    },
     statusImg() {
       let img = "";
       if (this.data.status == 0) {
@@ -209,10 +237,34 @@ export default {
       return img;
     }
   },
+  /**
+   * 从申请页返回后重拉详情,才能把「申请售后」换成「查看申请」
+   */
+  onShow() {
+    if (!this.detailInited) {
+      return;
+    }
+    this.getDetail();
+  },
   methods: {
     goShOrder(info){
       this.$util.pageTo({url:'/pages/order/detail?id='+info.shOrderId})
     },
+    /**
+     * 查看本单最新售后申请(含待审核);有申请 id 进详情,否则进申请进度页
+     */
+    goViewRefund() {
+      const applyId = this.currentRefundApplyId;
+      const account = (this.option && this.option.account) || "";
+      if (applyId) {
+        this.$util.pageTo({ url: "/pages/order/refund-detail?account=" + account + "&id=" + applyId });
+        return;
+      }
+      if (!this.data || !this.data.id) {
+        return;
+      }
+      this.$util.pageTo({ url: "/pages/order/refund?account=" + account + "&id=" + this.data.id });
+    },
     /** 跳转申请售后;分销已结算时兜底提示 */
     goRefund() {
       if (!this.data.id) {
@@ -226,6 +278,7 @@ export default {
     },
     init() {
       this.getDetail().then(() => {
+        this.detailInited = true;
         getShopUser().then(res => {
           if (this.data.status == 0) {
             this.$refs.couponSel._getUserDiscount().then(status => {
@@ -233,6 +286,8 @@ export default {
             });
           }
         });
+      }).catch(() => {
+        this.detailInited = true;
       });
     },
     getDetail() {
@@ -266,6 +321,28 @@ export default {
         } else {
           this.reachPeriodInfo = "上午";
         }
+        // 待审核申请详情接口可能还不带 id,补拉 get-refund-data 才能露出查看入口
+        return this.syncRefundApply();
+      });
+    },
+    /**
+     * 补拉本单最新售后申请;待审核 status=0 也必须留下记录
+     */
+    syncRefundApply() {
+      const orderId = (this.data && this.data.id) || (this.option && this.option.id);
+      if (!orderId) {
+        this.refundApply = null;
+        return Promise.resolve();
+      }
+      return getRefundData({ id: orderId }).then(res => {
+        if (!res || res.code != 1) {
+          this.refundApply = null;
+          return;
+        }
+        const apply = res.data ? res.data.apply : null;
+        this.refundApply = apply && apply.id ? apply : null;
+      }).catch(() => {
+        this.refundApply = null;
       });
     },
     payFn() {
@@ -379,12 +456,27 @@ export default {
   .default {
     width: 300upx;
   }
-  /* 申请售后:描边主色,与「立即支付」实心区分 */
+  /* 申请售后 / 查看申请:描边主色,与「立即支付」实心区分 */
   .refund-btn {
     color: $mainColor !important;
     border: 1upx solid $mainColor !important;
     background-color: #fff !important;
   }
+  .refund-actions {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+  }
+  .refund-actions .refund-btn {
+    margin-left: 0;
+  }
+  /* 两个按钮同时出现时收窄,避免和金额抢宽度 */
+  .refund-actions.is-double .refund-btn {
+    width: 200upx;
+  }
+  .refund-actions.is-double .refund-btn + .refund-btn {
+    margin-left: 16upx;
+  }
 }
 // 公共模块
 .module-com {

+ 1 - 1
mallApp/src/pages/order/refund-list.vue

@@ -1,6 +1,6 @@
 <!--
   商城「退款售后」列表
-  从「我的」退款/售后进入;按全部/处理中/已退款/已关闭筛选 xhMallRefundApply
+  从「我的」退款/售后进入;按全部/处理中/已退款/已关闭筛选 xhHdRefund 顾客申请
 -->
 <template>
   <view class="refund-list-page">