<template>
|
<view class="order-page">
|
<view class="toolbar">
|
<view class="vehicle-filter" @click="toggleVehicleType">
|
<text class="vehicle-filter__text">{{ currentVehicleTypeLabel }}</text>
|
<image class="vehicle-filter__arrow" src="/static/icon/ar_drop@2x.png" mode="widthFix"></image>
|
</view>
|
<view class="search-box">
|
<image class="search-box__icon" src="/static/icon/ic_search@2x.png" mode="aspectFit"></image>
|
<input
|
class="search-box__input"
|
type="text"
|
v-model="keyword"
|
placeholder="搜索用户手机号"
|
placeholder-class="search-box__placeholder"
|
confirm-type="search"
|
@confirm="handleSearch"
|
/>
|
</view>
|
</view>
|
|
<view class="tab-bar">
|
<view :class="activeTab === 'renting' ? 'tab-bar__item active' : 'tab-bar__item'" @click="activeTab = 'renting'">租用中</view>
|
<view :class="activeTab === 'completed' ? 'tab-bar__item active' : 'tab-bar__item'" @click="activeTab = 'completed'">已完成</view>
|
</view>
|
|
<view class="order-list">
|
<view class="order-card" v-for="item in list" :key="item.id">
|
<view class="order-card__head">
|
<view class="order-card__code">订单编号:{{ item.code || '-' }}</view>
|
</view>
|
<view class="order-card__row">车辆编号:{{ item.bikeCode || '-' }}</view>
|
<view class="order-card__row">订单类型:{{ item.bikeTypeText }}</view>
|
<view class="order-card__row">结算车型:{{ item.paramName || '-' }}</view>
|
<view class="order-card__row">用户手机:{{ item.phone || '-' }}</view>
|
<view class="order-card__row">骑行开始时间:{{ item.rentDate || '-' }}</view>
|
<view v-if="activeTab === 'completed'" class="order-card__row">结算时间:{{ item.closeDate || '-' }}</view>
|
|
<view class="order-card__footer">
|
<view v-if="Number(item.bikeType) === 1" class="order-card__track" @click="handleAction('查看轨迹', item)">查看轨迹</view>
|
<view class="order-card__actions" v-if="activeTab === 'renting'">
|
<view v-if="Number(item.bikeType) === 1" class="action-btn action-btn--line" @click="handleAction('开锁', item)">开锁</view>
|
<view v-if="Number(item.bikeType) === 1" class="action-btn action-btn--line" @click="handleAction('关锁', item)">关锁</view>
|
<view class="action-btn action-btn--primary" @click="handleAction('强制还车', item)">强制还车</view>
|
</view>
|
<view class="order-card__actions" v-else>
|
<view class="action-btn action-btn--warn" @click="handleAction('退款', item)">退款</view>
|
</view>
|
</view>
|
</view>
|
|
<view class="empty-state" v-if="!list.length && !loading">暂无订单数据</view>
|
<view class="load-more-tip" v-if="list.length">
|
{{ loading ? '加载中...' : (finished ? '没有更多了' : '上拉加载更多') }}
|
</view>
|
</view>
|
|
<u-popup :show="showVehicleTypePopup" :round="16" mode="bottom" @close="showVehicleTypePopup = false">
|
<view class="type-popup">
|
<view class="type-popup__title">选择车辆类型</view>
|
<view
|
:class="selectedVehicleType === item.value ? 'type-popup__item active' : 'type-popup__item'"
|
v-for="item in vehicleTypeOptions"
|
:key="item.value"
|
@click="selectVehicleType(item.value)"
|
>
|
{{ item.label }}
|
</view>
|
</view>
|
</u-popup>
|
<!-- 强制还车 -->
|
<u-popup :show="show" :closeOnClickOverlay="true" mode="bottom" bgColor="#fff" :round="10" @close="show = false">
|
<view class="huanche">
|
<view class="huanche_title">强制还车</view>
|
<view class="huanche_tips">强制还车后,车辆行程结束并自动结算</view>
|
<view class="huanche_footer">
|
<view class="huanche_footer_btn line" @click="show = false">取消</view>
|
<view class="huanche_footer_btn pr" @click="confirmForceBack">确定还车</view>
|
</view>
|
</view>
|
</u-popup>
|
<!-- 订单退款 -->
|
<u-popup :show="show1" :closeOnClickOverlay="true" mode="bottom" bgColor="#fff" :round="10" @close="closeRefundPopup">
|
<view class="huanche">
|
<view class="huanche_title">订单退款</view>
|
<view class="huanche_info">
|
<view class="huanche_info_item">
|
<view class="label">充值金额:</view>
|
<view class="val">{{ formatMoneyText(refundInfo.goodsorderMoney) }}</view>
|
</view>
|
<view class="huanche_info_item">
|
<view class="label">结算金额:</view>
|
<view class="val">{{ formatMoneyText(refundInfo.closeMoney) }}</view>
|
</view>
|
<view class="huanche_info_item">
|
<view class="label">已退金额:</view>
|
<view class="val">{{ formatMoneyText(refundInfo.hasRefundMoney) }}</view>
|
</view>
|
<view class="huanche_info_item">
|
<view class="label">可退金额:</view>
|
<view class="val">{{ formatMoneyText(refundInfo.canBanlanceMoney) }}</view>
|
</view>
|
</view>
|
<view class="huanche_form">
|
<view class="huanche_form_item">
|
<view class="lable"><text>*</text>退款金额(元)</view>
|
<view class="val">
|
<input v-model="refundForm.money" type="digit" placeholder="请输入退款金额" />
|
</view>
|
</view>
|
<view class="huanche_form_item">
|
<view class="lable">退款说明</view>
|
<view class="val">
|
<input v-model="refundForm.reason" type="text" placeholder="请输入退款原因" />
|
</view>
|
</view>
|
</view>
|
<view class="huanche_footer">
|
<view class="huanche_footer_btn line" @click="closeRefundPopup">取消</view>
|
<view class="huanche_footer_btn que" @click="confirmRefund">确认退款</view>
|
</view>
|
</view>
|
</u-popup>
|
</view>
|
</template>
|
|
<script>
|
export default {
|
data() {
|
return {
|
show: false,
|
show1: false,
|
activeTab: 'renting',
|
keyword: '',
|
selectedVehicleType: '',
|
page: 1,
|
list: [],
|
loading: false,
|
finished: false,
|
updatingLock: false,
|
forceBackLoading: false,
|
refundLoading: false,
|
currentOrderId: '',
|
refundOrderId: '',
|
refundInfo: {
|
canBanlanceMoney: '',
|
closeMoney: '',
|
goodsorderMoney: '',
|
hasRefundMoney: ''
|
},
|
refundForm: {
|
money: '',
|
reason: ''
|
},
|
searchTimer: null,
|
showVehicleTypePopup: false,
|
vehicleTypeOptions: [
|
{ label: '订单类型', value: '' },
|
{ label: '自行车', value: '0' },
|
{ label: '电车', value: '1' }
|
]
|
}
|
},
|
computed: {
|
currentVehicleTypeLabel() {
|
const current = this.vehicleTypeOptions.find(item => item.value === this.selectedVehicleType)
|
return current ? current.label : '订单类型'
|
},
|
currentStatus() {
|
return this.activeTab === 'renting' ? 1 : 4
|
}
|
},
|
watch: {
|
activeTab() {
|
this.resetAndLoad()
|
},
|
selectedVehicleType() {
|
this.resetAndLoad()
|
},
|
keyword() {
|
clearTimeout(this.searchTimer)
|
this.searchTimer = setTimeout(() => {
|
this.resetAndLoad()
|
}, 300)
|
}
|
},
|
onLoad() {
|
this.getList()
|
},
|
onReachBottom() {
|
this.getList()
|
},
|
onUnload() {
|
clearTimeout(this.searchTimer)
|
},
|
methods: {
|
handleSearch() {
|
clearTimeout(this.searchTimer)
|
this.resetAndLoad()
|
},
|
toggleVehicleType() {
|
this.showVehicleTypePopup = true
|
},
|
selectVehicleType(value) {
|
this.selectedVehicleType = value
|
this.showVehicleTypePopup = false
|
},
|
resetAndLoad() {
|
this.page = 1
|
this.list = []
|
this.finished = false
|
this.getList()
|
},
|
formatMoneyText(value) {
|
if (value === '' || value === null || value === undefined) {
|
return '-'
|
}
|
|
return `${value}元`
|
},
|
resetRefundState() {
|
this.refundOrderId = ''
|
this.refundInfo = {
|
canBanlanceMoney: '',
|
closeMoney: '',
|
goodsorderMoney: '',
|
hasRefundMoney: ''
|
}
|
this.refundForm = {
|
money: '',
|
reason: ''
|
}
|
},
|
closeRefundPopup() {
|
this.show1 = false
|
this.resetRefundState()
|
},
|
async updateOrderLock(item, lockStatus) {
|
if (!item.bikeCode) {
|
uni.showToast({
|
title: '车辆编号不能为空',
|
icon: 'none'
|
})
|
return
|
}
|
|
if (this.updatingLock) {
|
return
|
}
|
|
this.updatingLock = true
|
|
try {
|
const res = await this.$u.api.updateLockStatus({
|
ids: item.bikeCode,
|
lockStatus
|
})
|
|
uni.showToast({
|
title: res.code === 200 ? (lockStatus === 1 ? '开锁成功' : '关锁成功') : (res.msg || (lockStatus === 1 ? '开锁失败' : '关锁失败')),
|
icon: 'none'
|
})
|
} catch (error) {
|
uni.showToast({
|
title: error.message || (lockStatus === 1 ? '开锁失败' : '关锁失败'),
|
icon: 'none'
|
})
|
} finally {
|
this.updatingLock = false
|
}
|
},
|
async openRefundPopup(item) {
|
if (!item.id) {
|
uni.showToast({
|
title: '订单ID不能为空',
|
icon: 'none'
|
})
|
return
|
}
|
|
if (this.refundLoading) {
|
return
|
}
|
|
this.refundLoading = true
|
|
try {
|
const res = await this.$u.api.getGoodsorderCanBanlanceDTO({
|
orderId: item.id
|
})
|
|
if (res.code === 200) {
|
this.refundOrderId = item.id
|
this.refundInfo = {
|
canBanlanceMoney: res.data ? res.data.canBanlanceMoney : '',
|
closeMoney: res.data ? res.data.closeMoney : '',
|
goodsorderMoney: res.data ? res.data.goodsorderMoney : '',
|
hasRefundMoney: res.data ? res.data.hasRefundMoney : ''
|
}
|
this.refundForm = {
|
money: '',
|
reason: ''
|
}
|
this.show1 = true
|
}
|
|
|
} finally {
|
this.refundLoading = false
|
}
|
},
|
async confirmRefund() {
|
if (!this.refundOrderId) {
|
uni.showToast({
|
title: '订单ID不能为空',
|
icon: 'none'
|
})
|
return
|
}
|
|
if (!this.refundForm.money) {
|
uni.showToast({
|
title: '请输入退款金额',
|
icon: 'none'
|
})
|
return
|
}
|
|
if (this.refundLoading) {
|
return
|
}
|
|
this.refundLoading = true
|
|
try {
|
const res = await this.$u.api.backGoodsorder({
|
orderId: this.refundOrderId,
|
reason: this.refundForm.reason.trim(),
|
money: this.refundForm.money
|
})
|
|
|
|
if (res.code === 200) {
|
this.closeRefundPopup()
|
this.resetAndLoad()
|
uni.showToast({
|
title: '退款成功',
|
icon: 'none'
|
})
|
}
|
} finally {
|
this.refundLoading = false
|
}
|
},
|
async confirmForceBack() {
|
if (!this.currentOrderId) {
|
uni.showToast({
|
title: '订单ID不能为空',
|
icon: 'none'
|
})
|
return
|
}
|
|
if (this.forceBackLoading) {
|
return
|
}
|
|
this.forceBackLoading = true
|
|
try {
|
const res = await this.$u.api.forceBack({
|
id: this.currentOrderId
|
})
|
if (res.code === 200) {
|
this.show = false
|
this.currentOrderId = ''
|
this.resetAndLoad()
|
}
|
} finally {
|
this.forceBackLoading = false
|
}
|
},
|
formatBikeType(bikeType) {
|
return Number(bikeType) === 1 ? '电车' : '自行车'
|
},
|
async getList() {
|
if (this.loading || this.finished) {
|
return
|
}
|
|
this.loading = true
|
|
const model = {
|
phone: this.keyword.trim(),
|
status: this.currentStatus
|
}
|
|
if (this.selectedVehicleType !== '') {
|
model.bikeType = Number(this.selectedVehicleType)
|
}
|
|
try {
|
const res = await this.$u.api.operationOrderPage({
|
capacity: 10,
|
page: this.page,
|
model
|
})
|
|
if (res.code === 200) {
|
const records = (res.data && res.data.records) || []
|
const total = Number(res.data && res.data.total)
|
const list = records.map(item => ({
|
...item,
|
bikeTypeText: this.formatBikeType(item.bikeType)
|
}))
|
|
this.list.push(...list)
|
this.page += 1
|
|
if (!records.length || (Number.isFinite(total) && total > 0 ? this.list.length >= total : records.length < 10)) {
|
this.finished = true
|
}
|
}
|
} finally {
|
this.loading = false
|
}
|
},
|
async handleAction(text, item = {}) {
|
if (text === '开锁') {
|
await this.updateOrderLock(item, 1)
|
return
|
}
|
|
if (text === '关锁') {
|
await this.updateOrderLock(item, 0)
|
return
|
}
|
|
if (text === '强制还车') {
|
this.currentOrderId = item.id || ''
|
this.show = true
|
} else if (text === '退款') {
|
await this.openRefundPopup(item)
|
} else if (text === '查看轨迹') {
|
uni.navigateTo({
|
url: `/pages/trajectory/trajectory?id=${item.id}`
|
})
|
}
|
// uni.showToast({
|
// title: `${text}功能待接入`,
|
// icon: 'none'
|
// })
|
}
|
}
|
}
|
</script>
|
|
<style lang="scss" scoped>
|
page {
|
background: #f7f7f7;
|
}
|
|
.order-page {
|
min-height: 100vh;
|
background: #f7f7f7;
|
}
|
|
.huanche {
|
width: 100%;
|
padding: 0 20rpx;
|
box-sizing: border-box;
|
.huanche_title {
|
font-weight: 500;
|
font-size: 32rpx;
|
color: #222222;
|
margin-top: 40rpx;
|
text-align: center;
|
}
|
.huanche_tips {
|
width: 100%;
|
font-weight: 400;
|
font-size: 30rpx;
|
color: #333333;
|
text-align: center;
|
margin-top: 76rpx;
|
}
|
.huanche_info {
|
width: 100%;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
flex-wrap: wrap;
|
margin-top: 30rpx;
|
.huanche_info_item {
|
width: 50%;
|
display: flex;
|
align-items: center;
|
margin-bottom: 20rpx;
|
.label {
|
font-weight: 400;
|
font-size: 28rpx;
|
color: #666666;
|
}
|
.val {
|
font-weight: 400;
|
font-size: 28rpx;
|
color: #222222;
|
}
|
}
|
}
|
.huanche_form {
|
width: 100%;
|
margin-top: 20rpx;
|
display: flex;
|
flex-direction: column;
|
.huanche_form_item {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 30rpx;
|
&:last-child {
|
margin: 0 !important;
|
}
|
.lable {
|
flex-shrink: 0;
|
font-weight: 600;
|
font-size: 30rpx;
|
color: #222222;
|
text {
|
color: #FF5A31;
|
}
|
}
|
.val {
|
width: 468rpx;
|
height: 98rpx;
|
background: #F8F9FB;
|
border-radius: 140rpx;
|
border: 1rpx solid #E5E5E5;
|
padding: 0 32rpx;
|
box-sizing: border-box;
|
input {
|
width: 100%;
|
height: 100%;
|
font-weight: 400;
|
font-size: 30rpx;
|
color: #222222;
|
text-align: right;
|
}
|
}
|
}
|
}
|
.huanche_footer {
|
width: 100%;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-top: 94rpx;
|
.huanche_footer_btn {
|
width: 344rpx;
|
height: 96rpx;
|
line-height: 96rpx;
|
text-align: center;
|
border-radius: 46rpx;
|
}
|
.line {
|
border: 1rpx solid #01B6AD;
|
font-weight: 500;
|
font-size: 32rpx;
|
color: #01B6AD;
|
background-color: #ffffff;
|
}
|
.pr {
|
background: #01B6AD;
|
font-weight: 500;
|
font-size: 32rpx;
|
color: #FFFFFF;
|
}
|
.que {
|
background: #FF5A31;
|
font-weight: 500;
|
font-size: 32rpx;
|
color: #FFFFFF;
|
}
|
}
|
}
|
|
.toolbar {
|
padding: 18rpx 20rpx 16rpx;
|
display: flex;
|
align-items: center;
|
background: #ffffff;
|
}
|
|
.vehicle-filter {
|
width: 184rpx;
|
height: 72rpx;
|
border-radius: 31rpx;
|
background: #f7f7f7;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
flex-shrink: 0;
|
}
|
|
.vehicle-filter__text {
|
line-height: 40rpx;
|
font-weight: 400;
|
font-size: 26rpx;
|
color: #333333;
|
}
|
|
.vehicle-filter__arrow {
|
flex-shrink: 0;
|
width: 20rpx;
|
margin-left: 12rpx;
|
}
|
|
.search-box {
|
flex: 1;
|
height: 72rpx;
|
margin-left: 14rpx;
|
padding: 0 24rpx;
|
border-radius: 31rpx;
|
background: #f7f7f7;
|
display: flex;
|
align-items: center;
|
}
|
|
.search-box__icon {
|
width: 28rpx;
|
height: 28rpx;
|
flex-shrink: 0;
|
}
|
|
.search-box__input {
|
flex: 1;
|
height: 72rpx;
|
margin-left: 16rpx;
|
font-size: 26rpx;
|
color: #333333;
|
background: transparent;
|
}
|
|
.search-box__placeholder {
|
color: #b7b7b7;
|
font-size: 28rpx;
|
}
|
|
.tab-bar {
|
height: 82rpx;
|
padding: 0 96rpx;
|
background: #ffffff;
|
display: flex;
|
align-items: flex-end;
|
justify-content: space-between;
|
}
|
|
.tab-bar__item {
|
position: relative;
|
padding-bottom: 12rpx;
|
font-weight: 400;
|
font-size: 30rpx;
|
color: #666666;
|
line-height: 54rpx;
|
}
|
|
.tab-bar__item.active {
|
color: #01B6AD;
|
font-weight: 600;
|
}
|
|
.tab-bar__item.active::after {
|
content: '';
|
position: absolute;
|
left: 50%;
|
bottom: 0;
|
width: 108rpx;
|
height: 4rpx;
|
background: #01B6AD;
|
border-radius: 4rpx;
|
transform: translateX(-50%);
|
}
|
|
.order-list {
|
padding: 20rpx;
|
box-sizing: border-box;
|
}
|
|
.order-card {
|
margin-top: 16rpx;
|
padding: 22rpx 24rpx 0;
|
background: #ffffff;
|
border-radius: 24rpx;
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.03);
|
overflow: hidden;
|
&:first-child {
|
margin: 0 !important;
|
}
|
}
|
|
.order-card__head {
|
display: flex;
|
align-items: flex-start;
|
justify-content: space-between;
|
}
|
|
.order-card__code {
|
font-weight: 600;
|
font-size: 32rpx;
|
color: #222222;
|
line-height: 58rpx;
|
}
|
|
.order-card__row {
|
font-weight: 400;
|
font-size: 26rpx;
|
color: #666666;
|
line-height: 44rpx;
|
}
|
|
.load-more-tip {
|
padding: 28rpx 0 10rpx;
|
font-size: 24rpx;
|
line-height: 36rpx;
|
text-align: center;
|
color: #999999;
|
}
|
|
.order-card__footer {
|
margin-top: 22rpx;
|
padding: 18rpx 0 20rpx;
|
border-top: 2rpx solid #f1f1f1;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
}
|
|
.order-card__track {
|
font-weight: 400;
|
font-size: 26rpx;
|
color: #01B6AD;
|
line-height: 44rpx;
|
}
|
|
.order-card__actions {
|
display: flex;
|
align-items: center;
|
}
|
|
.action-btn {
|
min-width: 120rpx;
|
height: 68rpx;
|
padding: 0 32rpx;
|
margin-left: 20rpx;
|
border-radius: 34rpx;
|
font-size: 28rpx;
|
line-height: 68rpx;
|
text-align: center;
|
box-sizing: border-box;
|
}
|
|
.action-btn--line {
|
border: 2rpx solid #01B6AD;
|
color: #01B6AD;
|
background: #ffffff;
|
}
|
|
.action-btn--primary {
|
background: #01B6AD;
|
color: #ffffff;
|
}
|
|
.action-btn--warn {
|
border: 2rpx solid #FF5A31;
|
color: #FF5A31;
|
background: #ffffff;
|
}
|
|
.empty-state {
|
padding: 120rpx 0;
|
font-size: 28rpx;
|
line-height: 40rpx;
|
text-align: center;
|
color: #999999;
|
}
|
|
.type-popup {
|
padding: 32rpx 24rpx 40rpx;
|
background: #ffffff;
|
}
|
|
.type-popup__title {
|
font-size: 32rpx;
|
font-weight: 600;
|
line-height: 44rpx;
|
text-align: center;
|
color: #222222;
|
}
|
|
.type-popup__item {
|
height: 88rpx;
|
margin-top: 20rpx;
|
border-radius: 16rpx;
|
background: #f7f7f7;
|
font-size: 30rpx;
|
line-height: 88rpx;
|
text-align: center;
|
color: #666666;
|
}
|
|
.type-popup__item.active {
|
background: rgba(1, 182, 173, 0.12);
|
color: #01b6ad;
|
font-weight: 600;
|
}
|
</style>
|