526 lines
21 KiB
Vue
526 lines
21 KiB
Vue
<template>
|
||
<view class="monthly-payment-container">
|
||
<!-- 顶部标题 -->
|
||
<view class="header" @tap="toggleDropdown">
|
||
<text class="header-title">{{ headerTitle }}</text>
|
||
<u-icon :name="isDropdownOpen ? 'arrow-up' : 'arrow-down'" size="28"></u-icon>
|
||
</view>
|
||
<!-- 下拉停车场列表 -->
|
||
<view v-if="isDropdownOpen" class="parking-list">
|
||
<!-- 搜索框 -->
|
||
<view class="search-box">
|
||
<u-icon name="search" size="40" color="#999" class="search-icon" />
|
||
<input type="text" placeholder="搜索停车场" class="search-input" />
|
||
</view>
|
||
|
||
<!-- 停车场列表 -->
|
||
<view class="parking-items">
|
||
<view class="parking-item" v-for="(park, index) in parkingLots" :key="index"
|
||
@tap="selectParkingLot(park)">
|
||
<view class="parking-item-left">
|
||
<view class="parking-spaces">
|
||
<text class="spaces-label">剩余车位</text>
|
||
<text class="spaces-number">{{ park.space_count }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="parking-item-right">
|
||
<text class="parking-name">{{ park.parking_name }}</text>
|
||
<text class="parking-distance">{{ park.distance }}km</text>
|
||
<text class="parking-address">{{ park.address }}</text>
|
||
</view>
|
||
<view class="parking-selected" v-if="park.isSelected">✔</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view>
|
||
<view class="overlay" v-if="isDropdownOpen" />
|
||
<!-- 车辆选择区域 -->
|
||
<view class="changeCar">
|
||
<view class="car-selector" @tap="toggleCarDropdown">
|
||
<text class="selected-car-text">{{ selectedCar || '选择车辆' }}</text>
|
||
<u-icon :name="isCarDropdownOpen ? 'arrow-up' : 'arrow-down'" size="28"></u-icon>
|
||
</view>
|
||
|
||
<!-- 车辆下拉列表 -->
|
||
<view v-if="isCarDropdownOpen" class="car-dropdown">
|
||
<view class="dropdown-arrow"></view>
|
||
<view class="car-item" v-for="(car, index) in cars" :key="index" @click="selectCar(car)">
|
||
{{ car.car_number }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="container">
|
||
<view class="title">请输入车牌号码</view>
|
||
<car-number-input @numberInputResult="numberInputResult" :defaultStr="defaultNum"
|
||
ref="carNumberInput" />
|
||
<view class="selectColorBox" @click="show = true">
|
||
<view>车牌颜色</view>
|
||
<view class="selectColor">
|
||
<view class="color">{{ color }}</view>
|
||
<u-icon name="arrow-right" size="25"></u-icon>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="order-record" @tap="selectCost">
|
||
<text class="order-record-text">查询停车费>></text>
|
||
</view>
|
||
|
||
<!-- 停车费展示部分 -->
|
||
<view v-if="showCost1" class="cost-container">
|
||
<view>
|
||
<image class="cost-image"
|
||
src="https://wechat-img-file.oss-cn-beijing.aliyuncs.com/park/park_orderLoading.png"
|
||
mode="aspectFit" />
|
||
</view>
|
||
<view class="cost-header">
|
||
<text class="cost-title">{{ parkingLotName }} <text class="cost-title2">待支付</text></text>
|
||
</view>
|
||
<view class="cost-amount">
|
||
<text class="amount-symbol">¥</text>
|
||
<text class="amount-number">{{ costAmount }}</text>
|
||
</view>
|
||
<view class="cost-info">
|
||
<view class="info-item">
|
||
<text class="info-label">车牌号:</text>
|
||
<text class="info-value">{{ currentCarNumber }}</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="info-label">进场时间:</text>
|
||
<text class="info-value">{{ entryTime }}</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="info-label">计费时间:</text>
|
||
<text class="info-value">{{ billingTime }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="notice">
|
||
<text class="notice-text">⚠️ 请于付款后{{ feeOutTime }}分钟内离场否则将加收停车费</text>
|
||
</view>
|
||
<view class="payment-methods">
|
||
<view class="payment-item" :class="{ 'selected': paymentMethod === 'wechat' }"
|
||
@tap="selectPayment('wechat')">
|
||
<view class="payment-item-content">
|
||
<image
|
||
src="https://wechat-img-file.oss-cn-beijing.aliyuncs.com/property-img-file/com_wechat.png"
|
||
mode="aspectFit" class="payment-icon"></image>
|
||
<text class="payment-name">微信支付</text>
|
||
</view>
|
||
<view v-if="paymentMethod === 'wechat'" class="payment-selected"></view>
|
||
</view>
|
||
</view>
|
||
<view class="pay-button" @tap="confirmPayment">立即付款</view>
|
||
</view>
|
||
<view v-if="showCost2" class="cost-container not-found">
|
||
未找到停车记录
|
||
</view>
|
||
|
||
<!-- 选择车牌颜色 -->
|
||
<u-popup :show="show" :round="30" mode="bottom" @close="onClose">
|
||
<view class="payIpt">
|
||
<view class="tit">选择车牌颜色</view>
|
||
<!-- 颜色选择列表 -->
|
||
<scroll-view class="color-list" scroll-y>
|
||
<view class="color-item" :class="{ 'active': selectedColorIndex === index }"
|
||
v-for="(item, index) in colorOptions" :key="index" @click="selectColor(index)">
|
||
{{ item }}
|
||
</view>
|
||
</scroll-view>
|
||
<!-- 底部按钮 -->
|
||
<view class="popup-footer">
|
||
<view class="cancel-btn" @click="onClose">取消</view>
|
||
<view class="confirm-btn" @click="confirmColor">确定</view>
|
||
</view>
|
||
</view>
|
||
</u-popup>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
isPhone,
|
||
picUrl,
|
||
request,
|
||
upload,
|
||
NavgateTo
|
||
} from '../../../utils';
|
||
import { apiArr } from '@/api/park.js'
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
isDropdownOpen: false,
|
||
headerTitle: '请选择停车场',
|
||
parkingLots: [],
|
||
|
||
defaultNum: '',
|
||
color: '请选择',
|
||
show: false,
|
||
// 颜色选项列表
|
||
colorOptions: ['蓝牌', '黄牌', '黑牌', '白牌', '绿牌', '渐变绿底黑字', '黄绿双拼底黑字'],
|
||
// 当前选中的颜色索引
|
||
selectedColorIndex: -1,
|
||
|
||
// 车辆选择相关数据
|
||
isCarDropdownOpen: false,
|
||
selectedCar: '',
|
||
cars: [],
|
||
|
||
// 停车费相关数据
|
||
showCost1: false,
|
||
showCost2: false,
|
||
parkingLotName: '',
|
||
costAmount: '',
|
||
currentCarNumber: '',
|
||
feeOutTime: '',//免费出场时间
|
||
entryTime: '',
|
||
billingTime: '',
|
||
paymentMethod: 'wechat',
|
||
// 定时器ID,用于清除定时器
|
||
timerId: null
|
||
}
|
||
},
|
||
onLoad() {
|
||
this.getParkList();
|
||
},
|
||
|
||
// 在组件卸载时清除定时器
|
||
onUnload() {
|
||
if (this.timerId) {
|
||
clearInterval(this.timerId);
|
||
this.timerId = null;
|
||
}
|
||
},
|
||
methods: {
|
||
// 切换下拉列表显示状态
|
||
toggleDropdown() {
|
||
this.isCarDropdownOpen = false
|
||
this.isDropdownOpen = !this.isDropdownOpen;
|
||
},
|
||
|
||
// 选择停车场
|
||
selectParkingLot(park) {
|
||
// 先取消所有选中状态
|
||
this.parkingLots.forEach(item => {
|
||
item.isSelected = false;
|
||
});
|
||
// 设置当前选中
|
||
park.isSelected = true;
|
||
// 更新头部标题
|
||
this.$set(this, 'headerTitle', park.parking_name);
|
||
// 关闭下拉列表
|
||
this.isDropdownOpen = false;
|
||
},
|
||
numberInputResult(e) {
|
||
this.defaultNum = e;
|
||
|
||
// 使用$nextTick确保DOM更新完成后再尝试操作组件
|
||
this.$nextTick(() => {
|
||
if (this.$refs.carNumberInput) {
|
||
// 直接更新子组件的inputList数组
|
||
const valList = e.split("");
|
||
|
||
if (e == '其他车辆') {
|
||
for (let i = 0; i < 8; i++) {
|
||
this.$refs.carNumberInput.inputList[i] = ' ';
|
||
}
|
||
} else {
|
||
for (let i in valList) {
|
||
this.$refs.carNumberInput.inputList[i] = valList[i];
|
||
}
|
||
}
|
||
// 强制子组件更新
|
||
this.$refs.carNumberInput.$forceUpdate();
|
||
}
|
||
});
|
||
},
|
||
onClose() {
|
||
this.show = false;
|
||
},
|
||
// 更新计费时间
|
||
updateBillingTime() {
|
||
const entryDateTime = new Date(this.entryTime);
|
||
const currentDateTime = new Date();
|
||
const diff = currentDateTime - entryDateTime;
|
||
|
||
// 计算天、时、分、秒
|
||
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
||
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
|
||
|
||
// 格式化时间显示
|
||
let timeStr = '';
|
||
if (days > 0) {
|
||
timeStr += days + '天';
|
||
}
|
||
if (hours > 0 || days > 0) {
|
||
timeStr += hours + '时';
|
||
}
|
||
if (minutes > 0 || hours > 0 || days > 0) {
|
||
timeStr += minutes + '分';
|
||
}
|
||
timeStr += seconds + '秒';
|
||
|
||
this.billingTime = timeStr;
|
||
},
|
||
|
||
// 选择颜色
|
||
selectColor(index) {
|
||
this.selectedColorIndex = index;
|
||
},
|
||
// 确认选择的颜色
|
||
confirmColor() {
|
||
if (this.selectedColorIndex !== -1) {
|
||
this.color = this.colorOptions[this.selectedColorIndex];
|
||
}
|
||
this.show = false;
|
||
},
|
||
|
||
// 切换车辆下拉列表显示状态
|
||
toggleCarDropdown() {
|
||
this.isCarDropdownOpen = !this.isCarDropdownOpen;
|
||
},
|
||
|
||
// 选择车辆
|
||
selectCar(car) {
|
||
this.selectedCar = car.car_number;
|
||
this.isCarDropdownOpen = false;
|
||
if (car.car_number == '其他车辆') {
|
||
NavgateTo('../addCar/index')
|
||
}
|
||
this.numberInputResult(car.car_number)
|
||
},
|
||
|
||
// 查询停车费
|
||
selectCost() {
|
||
const selectedParkingLot = this.parkingLots.find(park => park.isSelected);
|
||
if (!selectedParkingLot) {
|
||
uni.showToast({
|
||
title: '请先选择停车场',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
if (!this.defaultNum) {
|
||
uni.showToast({
|
||
title: '请输入车牌号',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
const params = {
|
||
parking_id: selectedParkingLot.id,
|
||
car_number: this.defaultNum,
|
||
}
|
||
request(apiArr.tempParkingInfo, "POST", params).then((res) => {
|
||
if (res) {
|
||
this.parkingLotName = res.parking.parking_name;
|
||
this.costAmount = res.fee_amount;
|
||
this.currentCarNumber = res.car_number;
|
||
this.entryTime = res.in_time;
|
||
this.feeOutTime = res.parking.free_out_time
|
||
// 清除之前可能存在的定时器
|
||
if (this.timerId) {
|
||
clearInterval(this.timerId);
|
||
this.timerId = null;
|
||
}
|
||
|
||
if (res.car_billing_type == 1) {
|
||
this.billingTime = '月租车';
|
||
} else if (res.car_billing_type == 2) {
|
||
this.billingTime = '临时车';
|
||
} else {
|
||
// 计算并实时更新进场时间到现在的时长
|
||
this.updateBillingTime();
|
||
// 设置定时器,每秒更新一次
|
||
this.timerId = setInterval(() => {
|
||
this.updateBillingTime();
|
||
}, 1000);
|
||
}
|
||
|
||
this.showCost1 = true;
|
||
} else {
|
||
this.showCost2 = true;
|
||
}
|
||
})
|
||
},
|
||
|
||
// 选择支付方式
|
||
selectPayment(method) {
|
||
this.paymentMethod = method;
|
||
},
|
||
|
||
// 确认付款
|
||
confirmPayment() {
|
||
const selectedParkingLot = this.parkingLots.find(park => park.isSelected);
|
||
const params = {
|
||
car_number: this.defaultNum,
|
||
parking_id: selectedParkingLot.id,
|
||
user_id: uni.getStorageSync('userId'),
|
||
}
|
||
request(apiArr.tempParkingCreate, "POST", params).then((resVal) => {
|
||
// 根据平台设置不同的trans_type值
|
||
// 小程序: 71, App: 51
|
||
const systemInfo = uni.getSystemInfoSync();
|
||
let trans_type = 51; // 默认App环境
|
||
|
||
// 运行时判断是否为小程序环境
|
||
if (systemInfo.platform === 'devtools' || systemInfo.platform === 'unknown') {
|
||
trans_type = 71; // 开发工具或未知环境默认为小程序
|
||
}
|
||
|
||
// 条件编译:针对不同平台设置不同值
|
||
// #ifdef MP
|
||
trans_type = 71; // 所有小程序平台
|
||
// #endif
|
||
|
||
// #ifdef APP-PLUS
|
||
trans_type = 51; // App平台
|
||
// #endif
|
||
|
||
const param = {
|
||
order_id: resVal.order_id,
|
||
user_id: uni.getStorageSync('userId'),
|
||
trans_type: trans_type
|
||
}
|
||
request(apiArr.tempParkingOrderPreorder, "POST", param).then(res => {
|
||
if (res && res.timeStamp && res.nonceStr && res.package && res.signType && res.paySign) {
|
||
// 调用微信支付
|
||
uni.requestPayment({
|
||
timeStamp: res.timeStamp,
|
||
nonceStr: res.nonceStr,
|
||
package: res.package,
|
||
signType: res.signType,
|
||
paySign: res.paySign,
|
||
success: (payRes) => {
|
||
// 重置页面状态为加载时的默认状态
|
||
// 清除定时器
|
||
if (this.timerId) {
|
||
clearInterval(this.timerId);
|
||
this.timerId = null;
|
||
}
|
||
|
||
// 重置停车费相关状态
|
||
this.showCost1 = false;
|
||
this.showCost2 = false;
|
||
this.parkingLotName = '';
|
||
this.costAmount = '';
|
||
this.currentCarNumber = '';
|
||
this.entryTime = '';
|
||
this.billingTime = '';
|
||
this.feeOutTime = '';
|
||
|
||
// 重置支付相关状态
|
||
this.paymentMethod = 'wechat';
|
||
|
||
const params = {
|
||
order_id: resVal.order_id,
|
||
}
|
||
request(apiArr.tempParkingOrderQuery, "POST", params).then(res => {
|
||
this.selectCost()
|
||
})
|
||
},
|
||
fail: (payErr) => {
|
||
const params = {
|
||
order_id: resVal.order_id
|
||
}
|
||
request(apiArr.tempParkingOrderDelete, "POST", params).then((res) => {
|
||
})
|
||
uni.showToast({
|
||
title: payErr.errMsg == 'requestPayment:fail cancel' ? '用户取消支付' : '支付失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
} else {
|
||
const params = {
|
||
order_id: resVal.order_id
|
||
}
|
||
request(apiArr.tempParkingOrderDelete, "POST", params).then((res) => {
|
||
})
|
||
console.error("获取支付参数失败,缺少必要参数")
|
||
uni.showToast({
|
||
title: '获取支付信息失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
}).catch(error => {
|
||
console.log("111"); // 请求创建订单失败时输出111
|
||
console.error("创建临时停车订单失败", error);
|
||
uni.showToast({
|
||
title: '创建订单失败',
|
||
icon: 'none'
|
||
})
|
||
})
|
||
},
|
||
// 获取车辆列表
|
||
getCarList() {
|
||
const params = {
|
||
user_id: uni.getStorageSync('userId')
|
||
}
|
||
request(apiArr.carList, "POST", params).then((res) => {
|
||
this.cars = res.car_list;
|
||
this.cars.push({
|
||
car_number: '其他车辆'
|
||
})
|
||
})
|
||
},
|
||
// 获取停车场列表
|
||
getParkList() {
|
||
request(apiArr.parkList, "POST", {}).then((res) => {
|
||
// 计算每个停车场的距离并添加到对象中
|
||
this.parkingLots = res.parking_list.map(park => {
|
||
try {
|
||
let locationData = uni.getStorageSync('location');
|
||
if (locationData) {
|
||
let location = locationData;
|
||
const userLat = location.lat;
|
||
const userLng = location.lng;
|
||
const parkLat = park.lat;
|
||
const parkLng = park.lng;
|
||
|
||
// 使用Haversine公式计算距离(单位:千米)
|
||
const R = 6371; // 地球半径(千米)
|
||
const dLat = (parkLat - userLat) * Math.PI / 180;
|
||
const dLng = (parkLng - userLng) * Math.PI / 180;
|
||
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||
Math.cos(userLat * Math.PI / 180) * Math.cos(parkLat * Math.PI / 180) *
|
||
Math.sin(dLng / 2) * Math.sin(dLng / 2);
|
||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||
const distance = (R * c).toFixed(1);
|
||
|
||
// 将距离添加到停车场对象中
|
||
return { ...park, distance };
|
||
}
|
||
return park;
|
||
} catch (error) {
|
||
console.error('计算停车场距离时出错:', error);
|
||
return park;
|
||
}
|
||
});
|
||
|
||
// 按照距离从近到远排序
|
||
this.parkingLots.sort((a, b) => {
|
||
// 有距离数据的排在前面,无距离数据的排在后面
|
||
if (a.distance !== undefined && b.distance !== undefined) {
|
||
return a.distance - b.distance;
|
||
}
|
||
if (a.distance !== undefined) return -1;
|
||
if (b.distance !== undefined) return 1;
|
||
return 0;
|
||
});
|
||
})
|
||
},
|
||
},
|
||
onShow() {
|
||
this.getCarList();
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
@import url('./index.css');
|
||
</style> |