HomeLease/pages/lease/lease.vue
2025-09-15 17:37:52 +08:00

969 lines
25 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="lease-page">
<!-- 头部区域 -->
<custom-nav-bar
:fill="false"
background-color="#FFDECB"
title="租赁申请"
title-align="center"
/>
<view class="header">
<image
:src="commonEnum.FIRE_BACKGROUND_FULL"
class="fire-background-full"
mode="aspectFill"
></image>
</view>
<!-- 主要内容区域 -->
<view class="main-content">
<!-- 租赁信息表单 -->
<view class="form-section">
<view class="section-header">
<view class="section-indicator"></view>
<text class="section-title">填写租赁信息</text>
</view>
<view class="form-fields">
<!-- 姓名 -->
<view class="form-item">
<text class="field-label">姓名</text>
<input v-model="formData.name" class="field-input" placeholder="请输入姓名" />
</view>
<!-- 手机号 -->
<view class="form-item">
<text class="field-label">手机号</text>
<input
v-model="formData.phone"
class="field-input"
placeholder="请填写手机号"
type="number"
/>
</view>
<!-- 地址 -->
<map-location
ref="mapLocation"
v-model="formData.address"
label="地址"
placeholder="请选择收货地址"
@input="onAddressInput"
@location-success="onLocationSuccess"
@update:location="onLocationUpdate"
/>
<!-- 详细位置 -->
<view class="form-item">
<text class="field-label">详细位置</text>
<textarea
v-model="formData.detailed"
auto-height
class="field-textarea"
placeholder="例:6栋201室"
/>
</view>
<!-- 租赁设备 -->
<view class="form-item">
<text class="field-label">租赁设备</text>
<view class="selector">
<view style="display: flex" @click="selectEquipment">
<text :class="['selector-text', { placeholder: !formData.equipment }]">
{{ formData.equipment || '选择设备类型' }}
</text>
<text class="arrow-icon">></text>
</view>
<uv-number-box v-model="formData.quantity"></uv-number-box>
</view>
</view>
<!-- 租赁周期 -->
<view class="form-item">
<text class="field-label">租赁周期</text>
<view class="selector" @click="selectPeriod">
<text :class="['selector-text', { placeholder: !formData.period }]">
{{ formData.period || '请选择租赁周期' }}
</text>
<text class="arrow-icon">></text>
</view>
</view>
</view>
</view>
<!-- 支付区域 -->
<view class="payment-section">
<button :class="['pay-button']" @click="goTohandlePayment">
{{ `立即支付 ¥${payAmount}` }}
</button>
<view v-if="isLogin" class="agreement">
<view :class="{ checked: hasAgreed }" class="checkbox">
<text v-if="hasAgreed">✓</text>
</view>
<!-- <text class="text">去签署</text>-->
<text v-if="!hasAgreed" class="link" @click="goToInstallationProtocol"
>去签署《安装协议》
</text>
<text v-else class="link" @click="goToInstallationProtocol">已签署《安装协议》</text>
</view>
<view class="payment-details">
<view class="details-header" @click="toggleDetails">
<text class="details-title">明细</text>
<view :class="{ expanded: showDetails }" class="arrow-wrapper">
<image :src="commonEnum.DOWN_ARROW" class="details-arrow"></image>
</view>
</view>
<view v-if="showDetails" class="details-content">
<view v-if="selectedDevice" class="detail-item">
<text class="detail-label">设备类型</text>
<text class="detail-value">{{ selectedDevice.name }}</text>
</view>
<view class="detail-item">
<text class="detail-label">设备数量</text>
<text class="detail-value">{{ formData.quantity }}</text>
</view>
<view v-if="selectedPackage" class="detail-item">
<text class="detail-label">租赁周期</text>
<text class="detail-value">{{ selectedPackage.name }}</text>
</view>
<view v-if="selectedPackage" class="detail-item">
<text class="detail-label">租赁天数</text>
<text class="detail-value">{{ selectedPackage.day }}天</text>
</view>
<view class="detail-item">
<text class="detail-label">租金</text>
<text class="detail-value">¥{{ payAmount }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- <uv-popup ref="popup" :close-on-click-overlay="false" bg-color="#fff" mode="center" round="16">-->
<!-- <view class="protocol-popup">-->
<!-- <view class="popup-header">-->
<!-- <text class="title">安装协议</text>-->
<!-- </view>-->
<!-- <scroll-view class="popup-content" scroll-y>-->
<!-- <text class="content-text">-->
<!-- 本协议由出租方与承租方共同订立,双方就灶台设备租赁安装事宜达成如下约定: ​​第一条-->
<!-- 设备交付​​-->
<!-- 出租方应于约定日期将灶台设备运送至指定安装位置,并负责专业安装调试,确保设备正常运行。-->
<!-- ​​第二条 安装标准​​-->
<!-- 安装过程需符合安全规范,燃气接口必须密封完好,电路连接符合国家标准,排烟管道畅通无阻。-->
<!-- ​​第三条 使用培训​​-->
<!-- 出租方需提供设备操作培训,指导承租方正确使用及日常维护方法,确保使用安全。 ​​第四条-->
<!-- 维护责任​​-->
<!-- 租赁期内正常使用出现的故障,由出租方负责免费维修。人为损坏需承租方承担维修费用。-->
<!-- ​​第五条 安全承诺​​-->
<!-- 承租方应按照操作规程使用设备,定期检查安全隐患。如发现异常应立即停用并通知出租方。-->
<!-- ​​第六条 协议终止​​ 租赁期满后出租方负责设备拆卸回收。如需续租应提前15日重新签订协议。-->
<!-- 本协议一式两份,双方各执一份,自签字之日起生效。 出租方签字__________-->
<!-- 承租方签字__________ 日期:年月日-->
<!-- </text>-->
<!-- </scroll-view>-->
<!-- <view class="popup-footer">-->
<!-- <button class="agree-btn" hover-class="btn-hover" @click="close">同意并继续</button>-->
<!-- </view>-->
<!-- </view>-->
<!-- </uv-popup>-->
</view>
</template>
<script>
import commonEnum from '../../enum/commonEnum'
import MapLocation from '@/components/map-location/map-location.vue'
import {
getDeviceTypes as fetchDeviceTypes,
getPeriodPackages as fetchPeriodPackages,
createLeaseOrder,
} from '@/api/lease/lease.js'
import { checkLoginStatus } from '../../utils/checkLogin'
import { isSignProtocolInstallation } from '../../api/protocol/protocol'
import { getIsRealName } from '../../api'
export default {
name: 'LeasePage',
components: {
MapLocation,
},
computed: {
commonEnum() {
return commonEnum
},
// 计算总金额
payAmount() {
return this.formData.amount * this.formData.quantity
},
},
onShow() {
this.refreshLogin()
this.getIsSignProtocolInstallationOnshow()
// if (!this.hasAgreed) {
// uni.navigateTo({
// url: '/pages/InstallationAgreement/InstallationAgreement',
// })
// }
},
onLoad() {
this.getIsSignProtocolInstallation()
uni.authorize({
scope: 'scope.userLocation',
success: function () {
console.log('用户同意了授权')
},
})
// 页面加载时获取设备类型列表
this.getDeviceTypes()
},
data() {
return {
isRealName: false,
isLogin: false,
hasAgreed: false,
formData: {
quantity: 1,
name: '',
phone: '',
address: '',
detailed: '',
equipment: '',
devTypeId: '', // 设备类型ID
period: '',
suitId: '', // 租赁周期ID
channelId: '3', //渠道id
amount: 0,
payAmount: 0,
},
showDetails: false,
deviceTypes: [], // 设备类型列表
periodPackages: [], // 租赁周期套餐列表
selectedDevice: null, // 选中的设备
selectedPackage: null, // 选中的套餐
}
},
methods: {
refreshLogin() {
if (uni.getStorageSync('token')) {
this.isLogin = true
}
},
async getIsSignProtocolInstallationOnshow() {
if (!uni.getStorageSync('token')) {
return
}
let res = await isSignProtocolInstallation()
console.log('res', res)
console.log('res.data', res.data)
this.hasAgreed = res.data
console.log('this.hasAgreed', this.hasAgreed)
},
async getIsSignProtocolInstallation() {
if (!uni.getStorageSync('token')) {
return
}
let res = await isSignProtocolInstallation()
console.log('res', res)
console.log('res.data', res.data)
this.hasAgreed = res.data
console.log('this.hasAgreed', this.hasAgreed)
this.goToInstallationProtocol()
},
goToInstallationProtocol() {
if (!this.hasAgreed) {
uni.navigateTo({
url: '/pages/InstallationAgreement/InstallationAgreement',
})
}
},
close() {
this.$refs.popup.close()
},
onLocationSuccess(location) {
// 接收到定位成功事件
console.log('定位成功:', location)
this.updateFormLocation(location)
},
onLocationUpdate(location) {
// 通过 v-model 方式更新定位数据
this.updateFormLocation(location)
},
updateFormLocation(location) {
// 更新表单中的经纬度
this.formData.lat = location.latitude
this.formData.lot = location.longitude
},
// 地址输入处理
onAddressInput(value) {
this.formData.address = value
},
onLocationError(error) {
console.error('位置获取失败:', error)
// 可以在这里处理位置获取失败后的逻辑
},
onUseLocation(location) {
console.log('使用位置:', location)
// 可以在这里处理使用位置后的逻辑
},
onMapOpened() {
console.log('地图已打开')
// 可以在这里处理地图打开后的逻辑
},
// 获取设备类型列表
async getDeviceTypes() {
try {
const response = await fetchDeviceTypes()
if (response.code === 200) {
this.deviceTypes = response.data
console.log('设备类型列表:', this.deviceTypes)
} else {
throw new Error(response.message || '获取设备类型失败')
}
} catch (error) {
console.error('获取设备类型失败:', error)
uni.showToast({
title: error.message || '获取设备类型失败',
icon: 'error',
})
}
},
// 根据设备类型获取租赁套餐
async getPeriodPackages(typeId) {
try {
const response = await fetchPeriodPackages(typeId)
if (response.code === 200) {
this.periodPackages = response.data
console.log('租赁套餐列表:', this.periodPackages)
} else {
throw new Error(response.message || '获取租赁套餐失败')
}
} catch (error) {
console.error('获取租赁套餐失败:', error)
uni.showToast({
title: error.message || '获取租赁套餐失败',
icon: 'error',
})
}
},
selectEquipment() {
// 检查是否有设备类型数据
if (this.deviceTypes.length === 0) {
uni.showToast({
title: '暂无可租聘设备',
icon: 'none',
})
return
}
// 构建设备类型选项列表
const itemList = this.deviceTypes.map(item => item.name)
uni.showActionSheet({
itemList: itemList,
success: res => {
const selectedDevice = this.deviceTypes[res.tapIndex]
this.selectedDevice = selectedDevice
this.formData.equipment = selectedDevice.name
this.formData.devTypeId = selectedDevice.id
this.formData.equipmentSuitId = selectedDevice.suitId
// 清空之前选择的套餐
this.formData.period = ''
this.formData.suitId = ''
this.selectedPackage = null
this.payAmount = '0.00'
// 根据选中的设备类型获取租赁套餐
this.getPeriodPackages(selectedDevice.suitId)
console.log('选中设备:', selectedDevice)
},
})
},
selectPeriod() {
// 检查是否已选择设备
if (!this.formData.devTypeId) {
uni.showToast({
title: '请先选择设备类型',
icon: 'none',
})
return
}
// 检查是否有套餐数据
if (this.periodPackages.length === 0) {
uni.showToast({
title: '套餐加载中,请稍后重试',
icon: 'none',
})
return
}
// 构建套餐选项列表
const itemList = this.periodPackages.map(item => `${item.name} ¥${item.amount}`)
uni.showActionSheet({
itemList: itemList,
success: res => {
const selectedPackage = this.periodPackages[res.tapIndex]
this.selectedPackage = selectedPackage
this.formData.period = selectedPackage.name
this.formData.suitId = selectedPackage.id
this.formData.amount = selectedPackage.amount
console.log('选中套餐:', selectedPackage)
console.log('选中租赁周期id:', this.formData.suitId)
},
})
},
toggleDetails() {
this.showDetails = !this.showDetails
},
goTohandlePayment() {
uni.$uv.debounce(this.handlePayment, 1000, true)
},
async goToRealName() {
const res = await getIsRealName()
if (!res.data) {
uni.showModal({
title: '提示',
content: '需要租赁,请先实名',
showCancel: true,
confirmText: '去实名',
cancelText: '暂不',
success: function (res) {
if (res.confirm) {
uni.navigateTo({
url: '/pages/realNameAuthentication/realNameAuthentication',
})
}
},
})
} else {
this.isRealName = true
}
},
async handlePayment() {
await this.goToRealName()
if (!this.isRealName) {
return
}
this.formData.payAmount = this.payAmount
if (checkLoginStatus()) {
return
}
if (!this.hasAgreed) {
uni.showToast({
title: '请签署安装协议',
icon: 'none',
})
return
}
// 表单验证
if (!this.formData.name.trim()) {
uni.showToast({
title: '请输入姓名',
icon: 'none',
})
return
}
if (!this.formData.phone.trim()) {
uni.showToast({
title: '请输入手机号',
icon: 'none',
})
return
}
if (!this.formData.address.trim()) {
uni.showToast({
title: '请选择地址',
icon: 'none',
})
return
}
if (!this.formData.detailed.trim()) {
uni.showToast({
title: '请输入详细地址',
icon: 'none',
})
return
}
if (!this.formData.devTypeId) {
uni.showToast({
title: '请选择设备类型',
icon: 'none',
})
return
}
if (!this.formData.suitId) {
uni.showToast({
title: '请选择租赁周期',
icon: 'none',
})
return
}
if (parseFloat(this.payAmount) <= 0) {
uni.showToast({
title: '金额无效',
icon: 'none',
})
return
}
const locationData = this.$refs.mapLocation.getLocationData()
if (locationData) {
this.formData.lat = locationData.latitude
this.formData.lot = locationData.longitude
}
// 提交表单数据
console.log('提交的表单数据:', this.formData)
// 支付逻辑
uni.showModal({
title: '确认支付',
content: `确认支付 ¥${this.payAmount} 吗?\n\n${this.formData.quantity}${this.formData.equipment}\n周期${this.formData.period}`,
success: async res => {
if (res.confirm) {
// this.$refs.toast.show({
// type: 'loading',
// message: '正在提交订单中...',
// overlay: true, // 启用遮罩
// duration: 10000,
// })
uni.showLoading({
title: '正在生成订单',
mask: 'true',
})
// 这里可以调用支付API
const response = await createLeaseOrder(this.formData)
// this.$refs.toast.hide()
uni.hideLoading()
if (response.code === 200) {
const data = response.data
console.log(data)
const payParams = {
package: response.data.payParams.packageVal, // 后端返回的字段名
timeStamp: response.data.payParams.timeStamp,
nonceStr: response.data.payParams.nonceStr,
signType: response.data.payParams.signType,
paySign: response.data.payParams.paySign,
}
console.log('支付后端回调数据', payParams)
wx.requestPayment({
...payParams,
success: function (res) {
uni.showToast({
title: '支付成功',
icon: 'success',
duration: 3000,
})
setTimeout(() => {
uni.navigateTo({
url: `/pages/myOrder/orderDetail?id=${data.pay.bstId}`,
})
}, 3000)
},
fail: function (res) {
uni.showToast({
title: '支付失败',
icon: 'error',
})
},
complete: function (res) {
console.log('微信支付调用结束')
},
})
// uni.showToast({
// title: '支付成功',
// icon: 'success',
// })
} else {
uni.showToast({
title: response.msg || '添加失败',
icon: 'none',
})
}
console.log('支付信息:', {
...this.formData,
amount: this.payAmount,
selectedDevice: this.selectedDevice,
selectedPackage: this.selectedPackage,
})
}
},
})
},
},
}
</script>
<style lang="scss" scoped>
.lease-page {
position: relative;
background: #f3f5f6;
//border: #120d0d solid 2rpx;
}
// 头部区域
.header {
position: relative;
//border: #120d0d solid 2rpx;
background: #fedfcd;
.fire-background-full {
position: relative;
top: 8rpx;
left: 30rpx;
width: 750rpx;
height: 592rpx;
}
}
// 主要内容区域
.main-content {
position: absolute;
top: 384rpx;
background: #ffffff;
border-radius: 40rpx 40rpx 0 0;
margin: 0 30rpx;
padding: 40rpx;
min-height: 60vh;
width: 610rpx;
//border: #120d0d solid 2rpx;
//box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.3);
}
// 表单区域
.form-section {
margin-bottom: 60rpx;
//border: #0c1387 solid 2rpx;
.section-header {
display: flex;
align-items: center;
margin-bottom: 40rpx;
//border: red solid 2rpx;
.section-indicator {
width: 8rpx;
height: 40rpx;
background: #f15a04;
border-radius: 4rpx;
margin-right: 20rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
}
}
.form-fields {
.form-item {
//margin-bottom: 40rpx;
display: flex;
align-items: center;
border-bottom: 1rpx solid #d8d8d8;
.field-label {
display: block;
font-size: 28rpx;
color: #333;
font-weight: 400;
flex: 1;
//border: 2rpx solid #d81313;
}
.field-input {
//border: 2rpx solid #d81313;
flex: 3;
min-height: 80rpx;
padding: 0 20rpx;
font-size: 28rpx;
&:focus {
border-color: #ff9a9e;
background: #fff;
}
}
.field-textarea {
//border: 2rpx solid #d81313;
flex: 3;
min-height: 28rpx;
padding: 26rpx 20rpx;
font-size: 28rpx;
}
}
}
// 选择器
.selector {
flex: 3;
display: flex;
justify-content: space-between;
align-items: center;
height: 80rpx;
border-radius: 12rpx;
padding: 0 20rpx;
transition: all 0.3s ease;
&:active {
background: #f0f0f0;
border-color: #ff6b6b;
}
.selector-text {
font-size: 28rpx;
color: #666;
&.placeholder {
color: #999;
}
}
.arrow-icon {
font-size: 24rpx;
color: #999;
transition: transform 0.3s ease;
}
&:active .arrow-icon {
transform: scale(1.2);
}
}
// 支付区域
.payment-section {
display: flex;
flex-direction: column;
.pay-button {
width: 100%;
height: 100%;
background: #f15a04;
color: #fff;
border: none;
border-radius: 10rpx;
font-size: 32rpx;
font-weight: bold;
box-shadow: 0 10rpx 30rpx rgba(255, 154, 158, 0.3);
transition: all 0.3s ease;
&.disabled {
background: #ccc;
color: #999;
box-shadow: none;
}
&:not(.disabled):active {
transform: translateY(2rpx);
box-shadow: 0 8rpx 20rpx rgba(255, 154, 158, 0.4);
}
}
.payment-details {
.details-header {
display: flex;
align-items: center;
padding: 20rpx 0;
border-bottom: 2rpx solid #f0f0f0;
.details-title {
font-size: 28rpx;
color: #f15a04;
padding-right: 10rpx;
}
.arrow-wrapper {
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.3s ease;
&.expanded {
transform: rotate(180deg);
}
}
.details-arrow {
width: 20rpx;
height: 10rpx;
}
}
.details-content {
padding: 20rpx 0;
.detail-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
.detail-label {
font-size: 28rpx;
color: #868686;
}
.detail-value {
font-size: 28rpx;
color: #3d3d3d;
font-weight: bold;
}
}
}
}
}
// 底部导航样式已移除使用系统tabBar
// 动画
@keyframes pulse {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.1);
opacity: 0.8;
}
100% {
transform: scale(1);
opacity: 1;
}
}
.protocol-popup {
width: 90vw;
max-width: 700rpx;
padding: 0 30rpx;
box-sizing: border-box;
.popup-header {
padding: 30rpx 0;
text-align: center;
border-bottom: 1rpx solid #f5f5f5;
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
}
.popup-content {
max-height: 50vh;
padding: 30rpx 0;
.content-text {
font-size: 28rpx;
color: #666;
line-height: 1.6;
white-space: pre-line;
}
}
.popup-footer {
padding: 20rpx 0 30rpx;
.agree-btn {
width: 100%;
height: 80rpx;
line-height: 80rpx;
background: #f15a04;
color: #fff;
font-size: 32rpx;
border-radius: 40rpx;
border: none;
&::after {
border: none;
}
}
.btn-hover {
opacity: 0.9;
}
}
}
.agreement {
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
padding: 30rpx;
.checkbox {
width: 28rpx;
height: 28rpx;
border: 2rpx solid #ff6b35;
border-radius: 6rpx;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
&.checked {
background: #ff6b35;
}
text {
color: #fff;
font-size: 18rpx;
font-weight: bold;
}
}
.text {
font-size: 24rpx;
color: #666;
}
.link {
font-size: 24rpx;
color: #ff6b35;
text-decoration: underline;
}
}
</style>