订单退款

master
lion 3 weeks ago
parent b13295c0f7
commit d5873953d9

@ -1,6 +1,6 @@
const mode = 'devLocal'; //devLocal本地测试、devOnline线上测试、production生产环境
let ROOTPATH = "https://yikangyang.ali251.langye.net"; //域名
// let ROOTPATH = "https://yikangyang-test.ali251.langye.net"; //域名
// let ROOTPATH = "https://yikangyang.ali251.langye.net"; //域名
let ROOTPATH = "https://yikangyang-test.ali251.langye.net"; //域名
switch (mode) {
case 'devLocal':

@ -16,6 +16,7 @@ let apiApp = {
accompanyProductOrder: '/api/mobile/hospital/accompany-order',
accompanyPay: '/api/mobile/hospital/accompany-pay',
orderRefund: '/api/mobile/user/accompany-order-refund',
accompanyRefundOrders: '/api/mobile/user/accompany-refund-orders',
// 服务对象
userArchive: '/api/mobile/user-archive/index',
userArchiveShow: '/api/mobile/user-archive/show',
@ -81,6 +82,7 @@ const install = (Vue, vm) => {
const accompanyProductOrder = (params = {}) => vm.$u.post(apiApp.accompanyProductOrder, params)
const accompanyPay = (params = {}) => vm.$u.post(apiApp.accompanyPay, params)
const orderRefund = (params = {}) => vm.$u.get(apiApp.orderRefund, params)
const accompanyRefundOrders = (params = {}) => vm.$u.get(apiApp.accompanyRefundOrders, params)
// 服务对象
const userArchive = (params = {}) => vm.$u.get(apiApp.userArchive, params)
const userArchiveShow = (params = {}) => vm.$u.get(apiApp.userArchiveShow, params)
@ -136,7 +138,7 @@ const install = (Vue, vm) => {
//用户订单
accompanyOrders,accompanyOrdersDetail,
// 医院 陪护下单
listHospital,detailHospital,accompanyProduct,accompanyProductDetail,accompanyProductOrder,accompanyPay,orderRefund,
listHospital,detailHospital,accompanyProduct,accompanyProductDetail,accompanyProductOrder,accompanyPay,orderRefund,accompanyRefundOrders,
// 服务对象
userArchive,userArchiveShow,userArchiveSave,userArchiveDestroy,
// other

@ -0,0 +1,49 @@
/**
* 申请退单弹窗收集必填理由
* @returns {Promise<string|null>}
*/
export function promptRefundReason() {
return new Promise((resolve) => {
uni.showModal({
title: '申请退单',
content: '',
editable: true,
placeholderText: '请填写退单理由(必填)',
confirmText: '下一步',
cancelText: '取消',
success: (r) => {
if (!r.confirm) {
resolve(null)
return
}
const reason = String(r.content || '').trim()
if (!reason) {
uni.showToast({ title: '请填写退单理由', icon: 'none' })
resolve(null)
return
}
resolve(reason)
},
fail: () => resolve(null)
})
})
}
export function hasPendingRefund(item) {
if (!item) return false
if (item.has_pending_refund === true || item.has_pending_refund === 1 || item.has_pending_refund === '1') {
return true
}
if (item.refund_display_status === 'pending') return true
const pending = item.pending_accompany_order_refund || item.pendingAccompanyOrderRefund
if (pending && pending.id) return true
return false
}
export function refundOrderStatusText(refundStatus) {
const n = Number(refundStatus)
if (n === 0) return '退款审核中'
if (n === 1) return '已退款'
if (n === 2) return '已驳回'
return '—'
}

@ -425,7 +425,8 @@
@click="pay"
>立即{{ form.pay_status === 0 && !orderId ? '下单' : '支付' }}</u-button
>
<view v-if="orderId && form.pay_status === 1 && form.status !== 4" class="more" @click="isShowMoreAction = true"></view>
<view v-if="canApplyChargeback" class="more" @click="applyChargeback">退</view>
<view v-else-if="refundPendingTip" class="more more--disabled">{{ refundPendingTip }}</view>
</view>
<!-- 服务列表 -->
<u-popup
@ -687,6 +688,7 @@
import serviceArchive from "@/component/serviceArchive/service-archive.vue";
import { ROOTPATH as baseUrl } from "@/common/config.js";
import {isNull} from "@/common/util.js"
import { promptRefundReason, hasPendingRefund } from "@/common/refundApply.js"
export default {
components: {
serviceArchive,
@ -905,6 +907,8 @@ export default {
this.form['created_at'] = res['created_at']
this.form['status'] = res['status']
this.form['nurse_id'] = res['nurse_id']
this.$set(this.form, 'has_pending_refund', !!res.has_pending_refund)
this.$set(this.form, 'refund_display_status', res.refund_display_status || 'none')
this.nurse = res['nurse']
this.fileList = res.files.map(i => ({
url: i.url
@ -1127,7 +1131,11 @@ export default {
title: "支付成功",
icon: "none",
});
await this.getDetail();
setTimeout(() => {
uni.switchTab({
url: "/pages/order/order",
});
}, 800);
}
} catch (err) {
uni.showToast({
@ -1151,33 +1159,74 @@ export default {
})
break;
case 'chargeback':
fn = () => this.$u.api.orderRefund({
id: this.orderId
}).then(_ => {
this.getDetail().then(_ => {
if (this.form.status) {
uni.showModal({
title: "已成功提交退款申请",
content: "当前订单已分配护工,需等待客服处理",
showCancel: false
})
}
fn = async () => {
const reason = await promptRefundReason()
if (!reason) return
const res = await this.$u.api.orderRefund({
id: this.orderId,
reason
})
})
if (res === false) return
this.$set(this.form, 'has_pending_refund', true)
this.$set(this.form, 'refund_display_status', 'pending')
await this.getDetail()
if (this.form.status) {
uni.showModal({
title: "已成功提交退单申请",
content: "当前订单已分配护工,需等待客服处理",
showCancel: false
})
} else {
uni.showModal({
title: "已提交退单申请",
showCancel: false
})
}
}
break;
}
uni.showModal({
title: "操作",
content: `是否确认${name}`,
success: (status) => {
success: async (status) => {
if (status.confirm) {
fn()
} else {
await fn()
}
},
fail: () => {
},
})
},
async applyChargeback() {
const reason = await promptRefundReason()
if (!reason) return
uni.showModal({
title: '确认退单',
content: '提交后将等待审核,通过后原路退款',
success: async (r) => {
if (!r.confirm) return
const res = await this.$u.api.orderRefund({
id: this.orderId,
reason
})
if (res === false) return
this.$set(this.form, 'has_pending_refund', true)
this.$set(this.form, 'refund_display_status', 'pending')
await this.getDetail()
if (this.form.status) {
uni.showModal({
title: "已成功提交退单申请",
content: "当前订单已分配护工,需等待客服处理",
showCancel: false
})
} else {
uni.showModal({
title: "已提交退单申请",
showCancel: false
})
}
}
})
}
},
computed: {
@ -1262,6 +1311,24 @@ export default {
tag: 'chargeback'
}]
}
},
canApplyChargeback() {
if (!this.orderId) return false
if (Number(this.form.pay_status) !== 1) return false
if (Number(this.form.status) === 4) return false
if (Number(this.form.pay_status) === 2) return false
if (hasPendingRefund(this.form)) return false
if (this.form.refund_display_status === 'refunded') return false
return true
},
refundPendingTip() {
if (!this.orderId || Number(this.form.pay_status) !== 1) return ''
if (hasPendingRefund(this.form) || this.form.refund_display_status === 'pending') {
return '退款审核中'
}
if (this.form.refund_display_status === 'rejected') return '退单已驳回'
if (Number(this.form.pay_status) === 2 || this.form.refund_display_status === 'refunded') return '已退款'
return ''
}
},
};
@ -1795,6 +1862,10 @@ export default {
font-size: 26rpx;
color: #666;
padding-right: 20rpx;
&--disabled {
color: #c20d12;
}
}
}

@ -4,13 +4,73 @@
<view class="u-tabs-box">
<u-tabs-swiper :height="76" :font-size="24" active-color="#c20d12" bg-color="transparent"
inactive-color="#999" ref="tabs" :list="tabs" :current="swiperCurrent" @change="change"
:is-scroll="false" :offset="[5, 5]" swiperWidth="750"></u-tabs-swiper>
</view>
<!-- @transition="transition"
@animationfinish="animationfinish" -->
:is-scroll="true" :offset="[5, 5]" swiperWidth="750"></u-tabs-swiper>
</view>
<swiper class="swiper-box" :current="swiperCurrent" @animationfinish="animationfinish" >
<swiper-item class="swiper-item" v-for="(swiper, swiperIndex) in tabs" :key="swiperIndex">
<scroll-view scroll-y style="height: 100%; width: 100%" @scrolltolower="reachBottom">
<block v-if="swiper.type === 'refund'">
<view class="refund-sub-tabs">
<view v-for="(st, idx) in refundSubTabs" :key="'rs-' + idx"
:class="['refund-sub-tab', { active: refundSubTab === idx }]"
@click="changeRefundSubTab(idx)">
{{ st.name }}
</view>
</view>
<scroll-view scroll-y style="height: calc(100% - 72rpx); width: 100%" @scrolltolower="reachBottom">
<view>
<view class="order" v-for="row in list[swiperIndex]" :key="row.id">
<block v-if="row.order && row.order.id">
<view class="title">
<view class="title__name">
<u-tag size="mini" type="primary" mode="dark" shape="circleLeft"
:text="row.order.type === 1 ? '陪诊' : '陪护'"></u-tag>
<text style="padding-left: 10rpx;">{{ row.order.accompany_product ? row.order.accompany_product.name : '' }}</text>
</view>
<view class="title__status">{{ refundStatusText(row.status) }}</view>
</view>
<view class="price">
<view class="price-icon">
<image style="width:90rpx" mode="widthFix"
:src="row.order.accompany_product ? (row.order.accompany_product.cover ? row.order.accompany_product.cover.url : vuex_default_icon) : vuex_default_icon"></image>
</view>
<view class="price-text">
<view class="price-text__num">{{ row.order.price ? row.order.price : 0 }}</view>
<view class="price-text__no">订单号 {{ row.order.no }}</view>
<view class="price-text__no" v-if="row.apply_reason">退{{ row.apply_reason }}</view>
</view>
</view>
<view class="info" @click.stop.native="goOrderDetail(row.order)">
<view class="info__item flex100">
<text>被服务人</text>
<text>{{ row.order.user_archive ? row.order.user_archive.name : '' }}</text>
</view>
<view class="info__item flex100">
<text>{{ (row.order.type == 1 ? '就诊' : '服务') + '时间' }}</text>
<text>{{ row.order.time ? $moment(row.order.time).format('YYYY年MM月DD日 HH:mm') : '' }}</text>
</view>
<view class="info__item flex100" v-if="row.order.type==2">
<text>详细地址</text>
<text>{{ row.order.city || ' ' }}</text>
</view>
<view class="info__item flex100" v-if="row.order.type==1">
<text>就诊医院</text>
<text>{{ row.order.hospital ? row.order.hospital.name : '' }}</text>
</view>
</view>
<view class="bottom">
<view class="row1" style="justify-content: flex-end;">
<u-button ripple shape="circle" :custom-style="payBtnStyle"
:throttle-time="2000" @click="goOrderDetail(row.order)">查看订单</u-button>
</view>
</view>
</block>
</view>
<u-empty v-if="list[swiperIndex].length === 0 && loadStatus[swiperIndex] !== 'loading'" margin-top="120" text="暂无退款订单" mode="list"></u-empty>
<u-loadmore :status="loadStatus[swiperIndex]" bgColor="#f2f2f2"></u-loadmore>
</view>
</scroll-view>
</block>
<scroll-view v-else scroll-y style="height: 100%; width: 100%" @scrolltolower="reachBottom">
<view>
<view class="order" v-for="i in list[swiperIndex]" :key="i.id">
<view class="title">
@ -19,13 +79,12 @@
:text="i.type === 1 ? '陪诊' : '陪护'"></u-tag>
<text style="padding-left: 10rpx;">{{i.accompany_product?i.accompany_product.name:''}}</text>
</view>
<view class="title__status">{{ statusFormat(i.pay_status) }}</view>
<view class="title__status">{{ orderListStatusLabel(i) }}</view>
</view>
<view class="price">
<view class="price-icon">
<view class="price-icon">
<image style="width:90rpx" mode="widthFix" :src="i.accompany_product?(i.accompany_product.cover?i.accompany_product.cover.url:vuex_default_icon):vuex_default_icon"></image>
<!-- <u-icon :name="" size="30"></u-icon> -->
</view>
<view class="price-text">
@ -35,14 +94,7 @@
</view>
</view>
<view class="info" @click.stop.native="$u.route({
url: '/package_sub/pages/AddOrder/AddOrder',
params: {
order_id: i.id,
type: i.type,
site_id: i.hospital?i.hospital.site_id:''
}
})">
<view class="info" @click.stop.native="goOrderDetail(i)">
<view class="info__item flex100">
<text>{{i.type == 1 ? '被服务人' : '被服务人'}}</text>
<text>{{ i.user_archive ? i.user_archive.name : i.user_archive_id }}</text>
@ -58,34 +110,27 @@
<view class="info__item flex100" v-if="i.type==1">
<text>就诊医院</text>
<text>{{ i.hospital ? i.hospital.name : '' }}</text>
</view>
<view class="info__item flex100">
<text>下单时间 </text>
<text>{{ i.created_at ? $moment(i.created_at).format('YYYY年MM月DD日 HH:mm') : '' }}</text>
</view>
<view class="info__item flex100">
<text>下单时间 </text>
<text>{{ i.created_at ? $moment(i.created_at).format('YYYY年MM月DD日 HH:mm') : '' }}</text>
</view>
</view>
<view class="bottom">
<view class="row1" style="justify-content: flex-end;">
<u-button v-if="i.pay_status === 0" ripple shape="circle" :custom-style="editBtnStyle"
:throttle-time="2000" @click="$u.route({
url: '/package_sub/pages/AddOrder/editOrder',
params: {
order_id: i.id,
type: i.type,
site_id: i.hospital ? i.hospital.site_id : ''
}
<u-button v-if="i.pay_status === 0" ripple shape="circle" :custom-style="editBtnStyle"
:throttle-time="2000" @click="$u.route({
url: '/package_sub/pages/AddOrder/editOrder',
params: {
order_id: i.id,
type: i.type,
site_id: i.hospital ? i.hospital.site_id : ''
}
})">修改信息</u-button>
<u-button style="margin-left:20rpx" ripple shape="circle" :custom-style="payBtnStyle"
:throttle-time="2000" @click="$u.route({
url: '/package_sub/pages/AddOrder/AddOrder',
params: {
order_id: i.id,
type: i.type,
site_id: i.hospital ? i.hospital.site_id : ''
}
})">{{ i.pay_status === 0 ? '立即支付' : '查看订单' }}</u-button>
:throttle-time="2000" @click="goOrderDetail(i)">{{ i.pay_status === 0 ? '立即支付' : '查看订单' }}</u-button>
</view>
</view>
</view>
@ -111,20 +156,21 @@
<script>
import Tabbar from "@/component/Tabbar/Tabbar.vue";
import { hasPendingRefund, refundOrderStatusText } from "@/common/refundApply.js";
export default {
components: {
Tabbar,
},
data() {
return {
editBtnStyle:{
"background-image": "linear-gradient(-90deg, #ddd 0%, #ccc 94%, #ccc 100%)",
"font-weight": "500",
"font-size": "28rpx",
color: "#fff",
width: "185rpx",
height: "60rpx",
"line-height": "60rpx",
return {
editBtnStyle:{
"background-image": "linear-gradient(-90deg, #ddd 0%, #ccc 94%, #ccc 100%)",
"font-weight": "500",
"font-size": "28rpx",
color: "#fff",
width: "185rpx",
height: "60rpx",
"line-height": "60rpx",
},
payBtnStyle: {
"background-image": "linear-gradient(-90deg, #e26165 0%, #c10d12 94%, #c10d12 100%)",
@ -146,7 +192,6 @@
count: 0,
},
{
// name: '',
name: "已支付",
value: 1,
count: 0,
@ -161,10 +206,19 @@
value: 3,
count: 0,
},
// {
// name: ''
// }
{
name: "退款订单",
value: "refund",
type: "refund",
count: 0,
},
],
refundSubTabs: [
{ status: 0, name: '退款审核中' },
{ status: 1, name: '已退款' },
{ status: 2, name: '已驳回' },
],
refundSubTab: 0,
swiperCurrent: 0,
tabsHeight: 0,
dx: 0,
@ -176,6 +230,11 @@
pay_status: "",
page_size: 10,
},
refundSelect: {
page: 1,
page_size: 10,
refund_status: 0,
},
list: [],
};
},
@ -199,16 +258,36 @@
},
token() {
return this.vuex_token || uni.getStorageSync('lifeData')?.vuex_token
},
isRefundTab() {
const tab = this.tabs[this.swiperCurrent]
return !!(tab && tab.type === 'refund')
}
},
methods: {
refundStatusText(status) {
return refundOrderStatusText(status)
},
orderListStatusLabel(i) {
if (hasPendingRefund(i)) return '退款审核中'
if (i.refund_display_status === 'rejected') return '已驳回'
if (Number(i.pay_status) === 2 || i.refund_display_status === 'refunded') return '已退款'
return this.statusFormat(i.pay_status)
},
goOrderDetail(i) {
this.$u.route({
url: '/package_sub/pages/AddOrder/AddOrder',
params: {
order_id: i.id,
type: i.type,
site_id: i.hospital ? i.hospital.site_id : ''
}
})
},
reachBottom() {
// tab
this.getOrder()
},
// tab
change(index) {
console.log("index",index)
change(index) {
this.swiperCurrent = index;
},
transition({
@ -225,10 +304,26 @@
}) {
this.$refs.tabs.setFinishCurrent(current);
this.swiperCurrent = current;
this.select.pay_status = this.tabs[this.swiperCurrent].value;
const tab = this.tabs[this.swiperCurrent]
if (tab && tab.type === 'refund') {
const sub = this.refundSubTabs[this.refundSubTab]
this.refundSelect.refund_status = sub ? sub.status : 0
} else {
this.select.pay_status = tab ? tab.value : ''
}
this.getOrder(true)
},
changeRefundSubTab(idx) {
if (this.refundSubTab === idx) return
this.refundSubTab = idx
const sub = this.refundSubTabs[idx]
this.refundSelect.refund_status = sub ? sub.status : 0
this.getOrder(true)
},
async getOrder(isRefresh = false) {
if (this.isRefundTab) {
return this.getRefundOrders(isRefresh)
}
if (isRefresh) {
this.select.page = 1;
this.loadStatus[this.swiperCurrent] = 'loadmore'
@ -237,11 +332,10 @@
try {
this.loadStatus[this.swiperCurrent] = 'loading'
const res = await this.$u.api.accompanyOrders(this.select);
console.log(res);
this.tabs[0].count = res.pay_status_count_0+res.pay_status_count_1+res.pay_status_count_2+res.pay_status_count_3;
this.tabs[1].count = res.pay_status_count_0;
this.tabs[2].count = res.pay_status_count_1;
this.tabs[3].count = res.pay_status_count_2;
this.tabs[0].count = res.pay_status_count_0+res.pay_status_count_1+res.pay_status_count_2+res.pay_status_count_3;
this.tabs[1].count = res.pay_status_count_0;
this.tabs[2].count = res.pay_status_count_1;
this.tabs[3].count = res.pay_status_count_2;
this.tabs[4].count = res.pay_status_count_3;
if (isRefresh) {
this.list[this.swiperCurrent].length = 0;
@ -258,6 +352,38 @@
this.loadStatus[this.swiperCurrent] = 'loadmore'
}
},
async getRefundOrders(isRefresh = false) {
const idx = this.swiperCurrent
if (isRefresh) {
this.refundSelect.page = 1
this.loadStatus[idx] = 'loadmore'
}
if (this.loadStatus[idx] === 'nomore') return
try {
this.loadStatus[idx] = 'loading'
const sub = this.refundSubTabs[this.refundSubTab]
const res = await this.$u.api.accompanyRefundOrders({
page: this.refundSelect.page,
page_size: this.refundSelect.page_size,
refund_status: sub ? sub.status : 0,
})
if (isRefresh) {
this.list[idx].length = 0
}
const rows = res && res.data ? res.data : []
this.list[idx].push(...rows)
const total = res && res.total != null ? res.total : rows.length
if (this.list[idx].length >= total) {
this.loadStatus[idx] = 'nomore'
} else {
this.refundSelect.page++
this.loadStatus[idx] = 'loadmore'
}
} catch (err) {
console.error(err)
this.loadStatus[idx] = 'loadmore'
}
},
},
};
</script>
@ -282,6 +408,27 @@
}
}
.refund-sub-tabs {
display: flex;
align-items: center;
padding: 16rpx 25rpx 8rpx;
background: #eee;
}
.refund-sub-tab {
font-size: 24rpx;
color: #999;
padding: 12rpx 24rpx;
margin-right: 16rpx;
border-radius: 999rpx;
background: #fff;
&.active {
color: #c20d12;
background: rgba(194, 13, 18, 0.08);
}
}
.d-flex {
display: flex;
}
@ -364,7 +511,6 @@
width: 90rpx;
height: 84rpx;
border-radius: 10rpx;
// background-color: #f3e7d8;
}
&-text {
@ -433,4 +579,4 @@
}
}
}
</style>
</style>

Loading…
Cancel
Save