526 lines
21 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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="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>