This commit is contained in:
xiaoxian 2026-06-11 21:44:54 +08:00
parent a8de74cfd4
commit 9ad01ccce2
6 changed files with 135 additions and 84 deletions

View File

@ -79,5 +79,25 @@ export default {
*/ */
terminate(id, data) { terminate(id, data) {
return post(`/rentals/${id}/terminate`, data) return post(`/rentals/${id}/terminate`, data)
},
/**
* 办理续租
* @param {number} id - 租赁记录ID
* @param {Object} data - 续租数据
* @returns {Promise}
*/
renew(id, data) {
return post(`/rentals/${id}/renew`, data)
},
/**
* 办理换房
* @param {number} id - 租赁记录ID
* @param {Object} data - 换房数据
* @returns {Promise}
*/
changeRoom(id, data) {
return post(`/rentals/${id}/change-room`, data)
} }
} }

View File

@ -108,6 +108,18 @@
"navigationStyle": "custom" "navigationStyle": "custom"
} }
}, },
{
"path": "pages/rental-renew/rental-renew",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/rental-change-room/rental-change-room",
"style": {
"navigationStyle": "custom"
}
},
{ {
"path": "pages/rental-add/rental-add", "path": "pages/rental-add/rental-add",
"style": { "style": {

View File

@ -72,17 +72,21 @@
<view class="section-title">费用明细</view> <view class="section-title">费用明细</view>
<view class="price-list"> <view class="price-list">
<view class="price-row"> <view class="price-row">
<text class="row-label">套餐</text> <text class="row-label">套餐</text>
<text class="row-value">¥{{order.unitPrice || order.subscriptionPlan?.monthlyPrice || 0}}/</text> <text class="row-value">{{orderPriceText}}</text>
</view> </view>
<view class="price-row"> <view class="price-row">
<text class="row-label">购买时长</text> <text class="row-label">购买时长</text>
<text class="row-value">{{order.months}} 个月</text> <text class="row-value">{{order.months}} 个月</text>
</view> </view>
<view class="price-row"> <view class="price-row">
<text class="row-label">基础费用</text> <text class="row-label">订阅费用</text>
<text class="row-value">¥{{order.amount}}</text> <text class="row-value">¥{{order.amount}}</text>
</view> </view>
<view class="price-row" v-if="order.billingCycle">
<text class="row-label">计费周期</text>
<text class="row-value">{{billingCycleText}}</text>
</view>
<view class="price-row" v-if="order.discountAmount > 0"> <view class="price-row" v-if="order.discountAmount > 0">
<text class="row-label">优惠金额</text> <text class="row-label">优惠金额</text>
<text class="row-value discount">-¥{{order.discountAmount}}</text> <text class="row-value discount">-¥{{order.discountAmount}}</text>
@ -133,7 +137,7 @@
<!-- 底部操作栏 --> <!-- 底部操作栏 -->
<view class="bottom-bar safe-area-bottom" v-if="order && order.status === 'pending'"> <view class="bottom-bar safe-area-bottom" v-if="order && order.status === 'pending'">
<button class="action-btn cancel" @click="cancelOrder">取消订单</button> <button class="action-btn cancel" @click="cancelOrder">取消订单</button>
<button class="action-btn pay" @click="payOrder">立即支付</button> <button class="action-btn pay" @click="showPaymentInfo">付款说明</button>
</view> </view>
</view> </view>
</template> </template>
@ -167,11 +171,26 @@
}, },
statusDesc() { statusDesc() {
const map = { const map = {
pending: '请在24小时内完成支付否则订单将自动取消', pending: '请按平台收款信息付款,管理员确认后套餐生效',
paid: '订单已支付成功,套餐已生效', paid: '订单已支付成功,套餐已生效',
cancelled: '订单已取消,如有疑问请联系客服' cancelled: '订单已取消,如有疑问请联系客服'
} }
return map[this.order?.status] || '' return map[this.order?.status] || ''
},
orderPriceText() {
if (!this.order) return '¥0/月'
if (this.order.billingCycle === 'yearly' && this.order.subscriptionPlan?.yearlyPrice) {
return `¥${this.order.subscriptionPlan.yearlyPrice}/年`
}
return `¥${this.order.unitPrice || this.order.subscriptionPlan?.monthlyPrice || 0}/月`
},
billingCycleText() {
const map = {
monthly: '月付',
yearly: '年付',
custom: '自定义周期'
}
return map[this.order?.billingCycle] || this.order?.billingCycle || '-'
} }
}, },
onLoad(options) { onLoad(options) {
@ -246,34 +265,12 @@
}) })
}, },
payOrder() { showPaymentInfo() {
uni.showModal({ uni.showModal({
title: '支付确认', title: '付款说明',
content: `确认支付订单 #${this.order.orderNo},金额 ¥${this.order.actualAmount}`, content: `订单 #${this.order.orderNo}\n应付金额¥${this.order.actualAmount}\n请按平台收款信息完成转账管理员确认后套餐自动生效。`,
success: async (res) => { showCancel: false,
if (res.confirm) { confirmText: '我知道了'
try {
uni.showLoading({ title: '支付中...' })
const result = await billingApi.payOrder(this.orderId)
uni.hideLoading()
if (result.code === 200) {
uni.showToast({
title: '支付成功',
icon: 'success',
duration: 2000
})
this.loadOrderDetail()
} else {
uni.showToast({ title: result.message || '支付失败', icon: 'none' })
}
} catch (error) {
uni.hideLoading()
console.error('支付失败:', error)
uni.showToast({ title: '支付失败', icon: 'none' })
}
}
}
}) })
}, },

View File

@ -67,7 +67,7 @@
<view class="order-footer" v-if="order.status === 'pending'"> <view class="order-footer" v-if="order.status === 'pending'">
<button class="action-btn cancel" @click.stop="cancelOrder(order)">取消订单</button> <button class="action-btn cancel" @click.stop="cancelOrder(order)">取消订单</button>
<button class="action-btn pay" @click.stop="payOrder(order)">去支付</button> <button class="action-btn pay" @click.stop="showPaymentInfo(order)">付款说明</button>
</view> </view>
</view> </view>
@ -223,33 +223,12 @@
}) })
}, },
async payOrder(order) { showPaymentInfo(order) {
uni.showModal({ uni.showModal({
title: '支付确认', title: '付款说明',
content: `确认支付订单 #${order.orderNo},金额 ¥${order.actualAmount}`, content: `订单 #${order.orderNo}\n应付金额¥${order.actualAmount}\n请按平台收款信息完成转账管理员确认后套餐自动生效。`,
success: async (res) => { showCancel: false,
if (res.confirm) { confirmText: '我知道了'
uni.showLoading({ title: '支付中...' })
try {
const result = await billingApi.payOrder(order.id)
uni.hideLoading()
if (result.code === 200) {
uni.showToast({
title: '支付成功',
icon: 'success',
duration: 2000
})
this.refreshData()
} else {
uni.showToast({ title: result.message || '支付失败', icon: 'none' })
}
} catch (error) {
uni.hideLoading()
console.error('支付失败:', error)
uni.showToast({ title: '支付失败', icon: 'none' })
}
}
}
}) })
}, },

View File

@ -30,6 +30,7 @@
<view class="plan-price"> <view class="plan-price">
<text class="price">¥{{plan.monthlyPrice}}</text> <text class="price">¥{{plan.monthlyPrice}}</text>
<text class="unit">/</text> <text class="unit">/</text>
<text v-if="plan.yearlyPrice" class="yearly-price">¥{{plan.yearlyPrice}}/</text>
</view> </view>
</view> </view>
@ -76,20 +77,20 @@
<view class="section-title">费用明细</view> <view class="section-title">费用明细</view>
<view class="price-list"> <view class="price-list">
<view class="price-row"> <view class="price-row">
<text class="row-label">套餐</text> <text class="row-label">套餐</text>
<text class="row-value">¥{{selectedPlan?.monthlyPrice || 0}}/</text> <text class="row-value">{{selectedPriceText}}</text>
</view> </view>
<view class="price-row"> <view class="price-row">
<text class="row-label">购买时长</text> <text class="row-label">购买时长</text>
<text class="row-value">{{selectedMonths}} 个月</text> <text class="row-value">{{selectedMonths}} 个月</text>
</view> </view>
<view class="price-row"> <view class="price-row">
<text class="row-label">基础费用</text> <text class="row-label">订阅费用</text>
<text class="row-value">¥{{baseAmount}}</text> <text class="row-value">¥{{baseAmount}}</text>
</view> </view>
<view class="price-row" v-if="discountAmount > 0"> <view class="price-row" v-if="billingCycle">
<text class="row-label">优惠金额</text> <text class="row-label">计费周期</text>
<text class="row-value discount">-¥{{discountAmount}}</text> <text class="row-value">{{billingCycleText}}</text>
</view> </view>
<view class="price-row total"> <view class="price-row total">
<text class="row-label">合计</text> <text class="row-label">合计</text>
@ -165,20 +166,36 @@
selectedMonths: 1, selectedMonths: 1,
periods: [ periods: [
{ label: '1个月', value: 1, discount: 0 }, { label: '1个月', value: 1, discount: 0 },
{ label: '3个月', value: 3, discount: 5 }, { label: '3个月', value: 3, discount: 0 },
{ label: '6个月', value: 6, discount: 10 }, { label: '6个月', value: 6, discount: 0 },
{ label: '12个月', value: 12, discount: 15 } { label: '12个月', value: 12, discount: 0 }
], ],
paymentSettings: null, paymentSettings: null,
submitting: false, submitting: false,
baseAmount: 0, baseAmount: 0,
discountAmount: 0, discountAmount: 0,
totalAmount: 0 totalAmount: 0,
billingCycle: ''
} }
}, },
computed: { computed: {
selectedPlan() { selectedPlan() {
return this.plans.find(p => p.id === this.selectedPlanId) return this.plans.find(p => p.id === this.selectedPlanId)
},
selectedPriceText() {
if (!this.selectedPlan) return '¥0/月'
if (this.selectedMonths === 12 && Number(this.selectedPlan.yearlyPrice) > 0) {
return `¥${this.selectedPlan.yearlyPrice}/年`
}
return `¥${this.selectedPlan.monthlyPrice}/月`
},
billingCycleText() {
const map = {
monthly: '月付',
yearly: '年付',
custom: '自定义周期'
}
return map[this.billingCycle] || this.billingCycle
} }
}, },
watch: { watch: {
@ -198,8 +215,8 @@
try { try {
const res = await billingApi.getPlans() const res = await billingApi.getPlans()
if (res.code === 200) { if (res.code === 200) {
// monthlyPrice <= 0 //
this.plans = (res.data || []).filter(p => p.status === 'active' && p.monthlyPrice > 0) this.plans = (res.data || []).filter(p => p.status === 'active' && (Number(p.monthlyPrice) > 0 || Number(p.yearlyPrice) > 0))
// //
const recommended = this.plans.find(p => p.isRecommended) const recommended = this.plans.find(p => p.isRecommended)
if (recommended) { if (recommended) {
@ -233,25 +250,29 @@
this.selectedMonths = months this.selectedMonths = months
}, },
calculatePrice() { async calculatePrice() {
if (!this.selectedPlan) { if (!this.selectedPlan) {
this.baseAmount = 0 this.baseAmount = 0
this.discountAmount = 0 this.discountAmount = 0
this.totalAmount = 0 this.totalAmount = 0
this.billingCycle = ''
return return
} }
try {
const monthlyPrice = this.selectedPlan.monthlyPrice const res = await billingApi.calculatePrice({
const baseAmount = monthlyPrice * this.selectedMonths planId: this.selectedPlanId,
months: this.selectedMonths
// })
const period = this.periods.find(p => p.value === this.selectedMonths) if (res.code === 200 && res.data) {
const discountRate = period ? period.discount / 100 : 0 this.baseAmount = res.data.baseAmount || 0
const discountAmount = Math.round(baseAmount * discountRate) this.discountAmount = res.data.discountAmount || 0
this.totalAmount = res.data.totalAmount || 0
this.baseAmount = baseAmount this.billingCycle = res.data.billingCycle || ''
this.discountAmount = discountAmount }
this.totalAmount = baseAmount - discountAmount } catch (error) {
console.error('计算价格失败:', error)
uni.showToast({ title: '计算价格失败', icon: 'none' })
}
}, },
async createOrder() { async createOrder() {
@ -431,6 +452,13 @@
color: #94A3B8; color: #94A3B8;
} }
.yearly-price {
display: block;
margin-top: 6rpx;
font-size: 22rpx;
color: #64748B;
}
.plan-features { .plan-features {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -134,6 +134,10 @@
<uni-icons type="refresh-filled" size="20" color="#FFFFFF"></uni-icons> <uni-icons type="refresh-filled" size="20" color="#FFFFFF"></uni-icons>
<text>办理续租</text> <text>办理续租</text>
</view> </view>
<view class="action-btn warning" @click="handleChangeRoom">
<uni-icons type="home-filled" size="20" color="#FFFFFF"></uni-icons>
<text>办理换房</text>
</view>
<view class="action-btn danger" @click="handleTerminate"> <view class="action-btn danger" @click="handleTerminate">
<uni-icons type="closeempty" size="20" color="#FFFFFF"></uni-icons> <uni-icons type="closeempty" size="20" color="#FFFFFF"></uni-icons>
<text>办理退租</text> <text>办理退租</text>
@ -367,6 +371,12 @@
}) })
}, },
handleChangeRoom() {
uni.navigateTo({
url: `/pages/rental-change-room/rental-change-room?id=${this.rentalId}`
})
},
handleTerminate() { handleTerminate() {
this.terminateForm = { this.terminateForm = {
waterMeterEnd: '', waterMeterEnd: '',
@ -757,6 +767,11 @@
box-shadow: 0 8rpx 24rpx rgba(37, 99, 235, 0.3); box-shadow: 0 8rpx 24rpx rgba(37, 99, 235, 0.3);
} }
.action-btn.warning {
background: linear-gradient(135deg, #F59E0B 0%, #D97706 100%);
box-shadow: 0 8rpx 24rpx rgba(245, 158, 11, 0.28);
}
.action-btn.danger { .action-btn.danger {
background: linear-gradient(135deg, #EF4444 0%, #DC2626 100%); background: linear-gradient(135deg, #EF4444 0%, #DC2626 100%);
box-shadow: 0 8rpx 24rpx rgba(239, 68, 68, 0.3); box-shadow: 0 8rpx 24rpx rgba(239, 68, 68, 0.3);