Files
gzh/src/views/JiaoFei_detail copy.vue

805 lines
26 KiB
Vue
Raw Normal View History

2026-01-06 15:03:14 +08:00
<template>
<div class="home">
<nav-bar url="/Member_wdzd"></nav-bar>
<div class="bj"></div>
<div class="app-container">
<!-- 缴费信息 -->
<div class="left-label">
<div class="label-color"></div>
<div class="label-text">缴费信息</div>
</div>
<div class="card">
<div class="cell">
<div class="cell_title">缴费金额</div>
<div class="cell_value">{{ parseFloat(params.BillMoney).toFixed(2) }}</div>
</div>
<div class="cell">
<div class="cell_title">缴费单号</div>
<div class="cell_value">{{ params.SerialFlowNo }}</div>
</div>
<div class="cell">
<div class="cell_title">开单时间</div>
<div class="cell_value">{{ params.BillTime }}</div>
</div>
<div class="cell">
<div class="cell_title">执行科室</div>
<div class="cell_value">{{ params.DoDepartName }}</div>
</div>
</div>
<!-- 缴费项目 -->
<div class="left-label">
<div class="label-color"></div>
<div class="label-text">缴费项目</div>
</div>
<div class="card">
<div v-for="item in payDetail" class="cell">
<div class="cell_title">{{ item.COSTNAME }}</div>
<div class="cell_value">{{ item.AMOUNT }}×{{ item.COSTSUM }} {{ item.UNIT }}</div>
</div>
</div>
<!-- 选择支付方式 -->
<div class="left-label">
<div class="label-color"></div>
<div class="label-text">支付方式</div>
</div>
<div class="card">
<!-- 微信支付选项 -->
<div v-if="provider == 'wxpay'" class="cell">
<label class="pay-item-label">
<div class="pay_type_list">
<div>
<img class="medical-insurance-image"
src="https://hnwjyy.oss-cn-shanghai.aliyuncs.com/%E5%8C%BB%E4%BF%9D%E6%B7%B7%E5%90%88%E6%94%AF%E4%BB%98%402x.png" />
</div>
<div>医保支付</div>
</div>
<input type="radio" name="payType" value="微信医保支付" v-model="radiovalue1" />
</label>
</div>
<div v-if="provider == 'wxpay'" class="cell">
<label class="pay-item-label">
<div class="pay_type_list">
<div>
<img class="pay-image"
src="https://open.weixin.qq.com/zh_CN/htmledition/res/assets/res-design-download/icon64_appwx_logo.png" />
</div>
<div>微信支付</div>
</div>
<input type="radio" name="payType" value="微信支付" v-model="radiovalue1" />
</label>
</div>
<!-- 支付宝支付选项 -->
<!-- <div v-if="provider=='alipay'" class="cell">
<div class="pay_type_list">
<div><img class="pay-image" src="../../static/icon/alipay.svg" /></div>
<div>支付宝支付</div>
</div>
<input type="radio" name="payType" value="支付宝支付" v-model="radiovalue1" />
</div> -->
</div>
<button class="btn" @click="generateOrder" :disabled="isPaying">
{{ isPaying ? '支付处理中...' : '支付' }}
</button>
</div>
</div>
</template>
<script>
import {
preCheck, apiUnifiedOrder,
apiopDjcx, apiopLBQuery,
apiOpWxQuery, getPayAuthCode,
prePay, getAccessToken
} from "@/request/api.js";
import { Dialog, Toast } from "vant";
export default {
data() {
return {
PatientId: '',
SerialFlowNo: '',
payDetail: [],
provider: 'wxpay', // 支付方式 wxpay 或 alipay
radiovalue1: '', // 选择的支付方式
isPaying: false, // 添加防止重复支付的状态
returnUrl: 'https://nxwj.btlsoln.com/nxwj-gzh/#/JiaoFei_detail', // 回调地址
params: {
FlowNo: '',
BillMoney: '',
BillTime: '',
BillType: '1',
DiagnosisCode: '',
DiagnosisName: '',
DoDepartName: '',
DoDepartCode: '',
DoctorName: '',
SerialFlowNo: '',
SerialNo: '',
SetDepartName: '',
SetDepartCode: '',
DoctorCode: '',
BillName: '',
PatientId: '',
},
globalData: {// 医保授权相关
scene: 0,
ghid: '', // 单据id
jzrid: '',//病人id就诊卡Id
authCode: '',
payAuthCode: '',
userCardNo: '',
userName: '',
cityId: '',
hosp_out_trade_no: '',
userLongitudeLatitude: {},
// 医保授权支付相关
orderInfo: {},
// 医保支付金额相关
ybPayInfo: {},
},
accessToken: '',
};
},
// 修改后的 mounted
mounted() {
// 优先从路由query获取授权回调后参数在query中
this.PatientId = this.$route.query.PatientId || this.$route.params.PatientId || sessionStorage.getItem('cachedPatientId') || '';
this.SerialFlowNo = this.$route.query.SerialFlowNo || this.$route.params.SerialFlowNo || sessionStorage.getItem('cachedSerialFlowNo') || '';
// 重新缓存参数(确保后续使用)
if (this.PatientId) sessionStorage.setItem('cachedPatientId', this.PatientId);
if (this.SerialFlowNo) sessionStorage.setItem('cachedSerialFlowNo', this.SerialFlowNo);
// 处理授权回调
let authCode = this.$route.query.authCode;
let retCode = this.$route.query.retCode;
if (retCode === '0' && authCode) {
this.radiovalue1='微信医保支付';
this.getPayAuth(authCode);
// 不return允许后续执行getData()或在getPayAuth中调用
}
// 确保数据加载(如果参数存在)
if (this.PatientId && this.SerialFlowNo) {
this.getData();
} else {
Toast('订单信息丢失,请重新选择订单');
setTimeout(() => this.$router.push('/Member_wdzd'), 1500);
}
},
methods: {
getToken() {
getAccessToken().then(res => {
console.log(res)
this.accessToken = res.data.accessToken
}).catch(err => {
Toast(err.message)
})
},
getData() {
let formdata = {
PatientId: this.PatientId,
QueryType: "0",
OrderNo: this.SerialFlowNo,
};
let queryData = {
FlowNo: this.SerialFlowNo,
PatientId: this.PatientId,
InsuranceType: '0'
};
// 并行执行两个异步请求
Promise.all([
apiopDjcx(formdata),
apiopLBQuery(queryData)
]).then(([djcxRes, lbQueryRes]) => {
// 处理 apiopDjcx 的响应
console.log(djcxRes.data);
let jsonObj = this.$x2js.xml2js(djcxRes.data);
console.log(jsonObj);
let jsonObj2 = jsonObj.Envelope.Body.MOP_OutpBillstoPayQueryResponse.request.request;
if (jsonObj2.returnresult.returncode === "1") {
// 设置参数
this.params.BillMoney = jsonObj2.data.BillMoney;
this.params.BillName = jsonObj2.data.BillName;
this.params.BillTime = jsonObj2.data.BillTime;
this.params.BillType = jsonObj2.data.BillType;
this.params.DiagnosisCode = jsonObj2.data.DiagnosisCode;
this.params.DiagnosisName = jsonObj2.data.DiagnosisName;
this.params.DoDepartName = jsonObj2.data.DoDepartName;
this.params.DoDepartCode = jsonObj2.data.DoDepartCode;
this.params.DoctorName = jsonObj2.data.DoctorName;
this.params.DoctorCode = jsonObj2.data.DoctorCode;
this.params.FlowNo = jsonObj2.data.FlowNo;
this.params.SerialFlowNo = jsonObj2.data.SerialFlowNo;
this.params.SerialNo = jsonObj2.data.SerialNo;
this.params.SetDepartName = jsonObj2.data.SetDepartName;
this.params.SetDepartCode = jsonObj2.data.SetDepartCode;
this.params.PatientId = this.PatientId;
} else {
Toast("获取缴费信息失败");
}
// 处理 apiopLBQuery 的响应
console.log(lbQueryRes);
let lbJsonObj = this.$x2js.xml2js(lbQueryRes.data);
console.log(lbJsonObj);
let lbJsonObj2 = this.$x2js.xml2js(
lbJsonObj.Envelope.Body.MOP_OutpBillsDetailQueryResponse.MOP_OutpBillsDetailQueryResult
);
console.log(lbJsonObj2);
if (Array.isArray(lbJsonObj2.response.data.data_row)) {
this.payDetail = lbJsonObj2.response.data.data_row;
} else {
this.payDetail = lbJsonObj2.response.data;
}
}).catch(err => {
Toast(err.message);
});
},
generateOrder() {
// 防止重复点击
if (this.isPaying) {
return;
}
if (!this.radiovalue1) {
Toast('请选择支付方式');
return;
}
// 设置支付状态为进行中
this.isPaying = true;
// 根据选择的支付方式执行不同逻辑
if (this.radiovalue1 === '微信医保支付') {
//判断单价小数位是否只有2位不是统一错误
console.log(this.payDetail);
if (Array.isArray(this.payDetail)) {
this.payDetail.forEach(item => {
const amountStr = item.AMOUNT.toString();
if (amountStr.includes('.')) {
const parts = amountStr.split('.');
const decimalPart = parts[1];
// 检查小数位数是否超过2位且末尾非0
if (decimalPart.length > 2 && !decimalPart.endsWith('0')) {
Toast("单价有误,暂不支持医保结账,请到门诊缴费");
this.isPaying = false;
return;
}
// 如果超过2位但末尾是0需要进一步检查去除末尾0后是否超过2位
if (decimalPart.length > 2) {
const trimmedDecimal = decimalPart.replace(/0+$/, ''); // 去除末尾的0
if (trimmedDecimal.length > 2) {
Toast("单价有误,暂不支持医保结账,请到门诊缴费");
this.isPaying = false;
return;
}
}
}
});
} else {
// console.log("不是数组");
const amountStr = this.payDetail.data_row.AMOUNT.toString();
if (amountStr.includes('.')) {
const parts = amountStr.split('.');
const decimalPart = parts[1];
// 检查小数位数是否超过2位且末尾非0
if (decimalPart.length > 2 && !decimalPart.endsWith('0')) {
Toast("单价有误,暂不支持医保结账,请到门诊缴费");
this.isPaying = false;
return;
}
// 如果超过2位但末尾是0需要进一步检查去除末尾0后是否超过2位
if (decimalPart.length > 2) {
const trimmedDecimal = decimalPart.replace(/0+$/, ''); // 去除末尾的0
if (trimmedDecimal.length > 2) {
Toast("单价有误,暂不支持医保结账,请到门诊缴费");
this.isPaying = false;
return;
}
}
}
}
// 只有特定患者可以使用医保支付
if (this.PatientId == "90131735" || this.PatientId == "90120780") {
this.handleMedicalInsurancePay();
} else {
Toast("如需医保支付,请携带身份证、医保卡等到线下缴费处操作。");
this.isPaying = false;
return;
}
} else if (this.radiovalue1 === '微信支付') {
this.handleWechatPay();
}
},
handleMedicalInsurancePay() {
preCheck().then(res => {
if (res.code === 200) {
// 生成带参数的returnUrl关键
const returnUrlWithParams = `${this.returnUrl}?PatientId=${this.PatientId}&SerialFlowNo=${this.SerialFlowNo}`;
const encodedReturnUrl = encodeURIComponent(returnUrlWithParams);
let authUrl = res.data;
if (authUrl.includes('?')) {
authUrl += '&redirectUrl=' + encodedReturnUrl;
} else {
authUrl += '?redirectUrl=' + encodedReturnUrl;
}
window.location.href = authUrl; // 跳转授权页
}
});
},
getPayAuth(code) {
const loadingToast = Toast.loading({
message: '加载中...',
forbidClick: true, // 禁止点击背景
duration: 0 // 不自动关闭
});
let params = {
// openid: localStorage.getItem("openid"),
qrcode: code,
}
console.log(params);
getPayAuthCode(params).then((res) => {
console.log("授权流程完毕,授权结果!!!")
console.log(res)
if (res.code == 200) {
// 解析 内层 JSON 字符串
let responseData;
try {
responseData = typeof res.data.json === 'string' ? JSON.parse(res.data.json) : res.data.json;
} catch (e) {
console.error("JSON解析错误", e);
Toast.fail("数据解析异常,请稍后重试");
loadingToast.clear(); // 解析错误时关闭加载
this.isPaying = false;
return;
}
if (responseData.user_card_no == res.data.IdNo) {
if (responseData.code === 0) {
// Toast.success("获取payAuthNo用户信息成功正在发起支付");
// TODO :发起支付
// console.log("用户信息:", responseData);
// {
// "code": 0,
// "message": "success",
// "user_name": "张林",
// "user_card_no": "370911198701055636",
// "pay_auth_no": "AUTH640100202510131615020070042",
// "user_longitude_latitude": {
// "longitude": "116.993209",
// "latitude": "35.978569"
// },
// "city_id": "640199",
// "family_pay_auth_no": null
// }
this.globalData.payAuthCode = responseData.pay_auth_no
this.globalData.userCardNo = responseData.user_card_no
this.globalData.userName = responseData.user_name
this.globalData.cityId = responseData.city_id
this.globalData.userLongitudeLatitude = responseData.user_longitude_latitude
this.globalData.ghid = sessionStorage.getItem('cachedSerialFlowNo') || this.SerialFlowNo;
this.globalData.jzrid = sessionStorage.getItem('cachedPatientId') || this.PatientId;
let YbInfo = {
"payAuthNo": responseData.pay_auth_no,
"userLongitudeLatitude": responseData.user_longitude_latitude,
"userCardNo": responseData.user_card_no,
"userName": responseData.user_name,
"jlid": "YBJF" + sessionStorage.getItem('cachedSerialFlowNo') || this.SerialFlowNo,
"kdsj": this.params.BillTime,
"ysje": this.params.BillMoney,
"jzrid": sessionStorage.getItem('cachedPatientId') || this.PatientId
}
sessionStorage.setItem('YbInfo', JSON.stringify(YbInfo));
// 授权完毕、开始预结算下单
let req = {
"userCardNo": this.globalData.userCardNo,
"userName": this.globalData.userName,
"orderTitle": "门诊缴费",
"openid": res.data.openid,
"ghid": this.globalData.ghid,
"FlowNo":this.params.FlowNo,
"payAuthNo": this.globalData.payAuthCode,
"jzrid": this.globalData.jzrid,
"userLongitudeLatitude": this.globalData.userLongitudeLatitude,
"accessToken": res.data.accessToken
}
this.retryPrePayWithDelay(req, 1) // 1次重试
.then(res => {
console.log(res);
if (res.code == 200) {
loadingToast.clear();
let data = encodeURIComponent(JSON.stringify(res.data));
this.$router.push(`/yborder?data=${data}`);
}
})
.catch(err => {
Toast.fail('支付请求失败');
// 错误时也清除loading提示
loadingToast.clear();
})
.finally(() => {
this.isPaying = false;
// 移除此行避免重复清除loadingToast
});
} else if (responseData.code < 0) {
Toast.fail("系统繁忙,请稍后重试");
this.getData();
loadingToast.clear(); // 异常时关闭
this.isPaying = false;
} else if (responseData.code > 0) {
Toast.fail(responseData.message);
this.radiovalue1 = ''; // 重置支付方式选择
this.getData();
loadingToast.clear(); // 异常时关闭
this.isPaying = false; // 重新启用支付按钮
}
} else {
Toast.fail("不是本人医保,不予办理");
this.getData();
loadingToast.clear(); // 异常时关闭
this.isPaying = false;
return;
}
} else {
Toast.fail(res.message || "授权失败");
this.getData();
loadingToast.clear(); // 异常时关闭
this.isPaying = false;
}
}).catch((err) => {
console.error("getPayAuthCode error", err);
Toast.fail("获取用户信息异常,请稍后重试");
this.getData();
loadingToast.clear(); // 异常时关闭
this.isPaying = false;
});
},
retryPrePayWithDelay(requestData, retries) {
return new Promise((resolve, reject) => {
const doRequest = (attempt) => {
prePay(requestData)
.then(response => {
resolve(response);
})
.catch(error => {
if (attempt < retries) {
console.warn(`prePay请求失败正在进行第${attempt}次重试`);
setTimeout(() => doRequest(attempt + 1), 1000); // 1秒后重试
} else {
reject(error);
}
});
};
doRequest(0);
});
},
// 清除 URL 中的 code
clearCodeFromUrl() {
const urlParams = new URLSearchParams(window.location.search);
urlParams.delete('code');
const newSearch = urlParams.toString() ? '?' + urlParams.toString() : '';
const cleanUrl = window.location.origin + window.location.pathname + window.location.hash.split('?')[0] + newSearch;
window.history.replaceState(null, '', cleanUrl);
},
handleWechatPay() {
console.log("this.params", this.params);
apiUnifiedOrder(this.params).then((ref) => {
if (ref.code === 200) {
//调用微信支付
try {
const data = ref.data;
// 兼容不同字段名
let payParams = {
appId: data.appid || data.appId,
timeStamp: data.timeStamp || data.timestamp,
nonceStr: data.nonce_str || data.nonceStr,
package: data.package || `prepay_id=${data.prepay_id || data.prepayId}`,
signType: 'MD5',
paySign: data.paySign
};
console.log('支付参数:', payParams);
// 查询参数
let queryParams = {
outTradeNo: data.outTradeNo
};
console.log('订单号:', queryParams.outTradeNo);
// 验证必要参数
const requiredParams = ['appId', 'timeStamp', 'nonceStr', 'package', 'paySign'];
for (let param of requiredParams) {
if (!payParams[param]) {
Toast(`缺少支付参数: ${param}`);
this.loading = false;
return;
}
}
if (typeof WeixinJSBridge == "undefined") {
Toast("请在微信中打开页面进行支付");
this.loading = false;
return;
}
// 调起微信支付
WeixinJSBridge.invoke('getBrandWCPayRequest', payParams, (res) => {
console.log('微信支付返回:', res);
console.log("订单号:", queryParams.outTradeNo);
if (res.err_msg && res.err_msg.indexOf('ok') > -1) {
// ✅ 支付成功,启动轮询
this.startPaymentPolling(queryParams); // 调用组件方法
} else if (res.err_msg && res.err_msg.indexOf('cancel') > -1) {
Toast("用户取消支付");
} else {
Toast("支付失败: " + (res.err_msg || '未知错误'));
}
this.loading = false;
});
} catch (error) {
console.error('支付调用异常:', error);
Toast("支付调用失败");
this.loading = false;
}
}
}).catch((err) => {
Toast("通讯异常,请稍后重试");
});
},
// 独立方法:启动轮询查询支付结果
startPaymentPolling(queryParams) {
// 防止重复启动
if (this.pollTimer) {
clearInterval(this.pollTimer);
this.pollTimer = null;
}
const toast = Toast.loading({
forbidClick: true,
message: '正在查询支付结果...',
loadingType: 'circular',
duration: 0
});
let pollCount = 0;
const maxPollCount = 15; // 最多 30 秒15次 × 2秒
this.pollTimer = setInterval(() => {
pollCount++;
console.log(`${pollCount} 次轮询查询支付状态...`);
apiOpWxQuery(queryParams)
.then((res) => {
console.log('查询响应:', res);
if (res.code === 200) {
const data = res.data;
const state = data.trade_state;
if (state === 'SUCCESS') {
// ✅ 支付成功
clearInterval(this.pollTimer);
this.pollTimer = null;
toast.clear();
Toast.success("支付成功");
// 设置支付状态为完成
this.isPaying = false;
// 清理缓存
sessionStorage.removeItem('cachedPatientId');
sessionStorage.removeItem('cachedSerialFlowNo');
// 先跳转再刷新
this.$router.replace({ path: "/Member_wdzd" });
// 确保路由跳转完成后刷新页面
this.$nextTick(() => {
window.location.reload();
});
sessionStorage.removeItem("yuyue");
} else if (state === 'NOTPAY') {
// 继续等待
console.log('用户尚未支付');
} else {
// 其他失败状态
clearInterval(this.pollTimer);
this.pollTimer = null;
toast.clear();
Toast.fail("支付失败:" + (data.trade_state_desc || state));
}
} else {
console.warn('查询接口异常:', res);
}
})
.catch((error) => {
console.error('查询请求失败:', error);
})
.finally(() => {
if (pollCount >= maxPollCount) {
if (this.pollTimer) {
clearInterval(this.pollTimer);
this.pollTimer = null;
}
toast.clear();
Toast.fail("支付超时,请稍后查看订单状态");
}
});
}, 2000);
// 总超时兜底30秒
setTimeout(() => {
if (this.pollTimer) {
clearInterval(this.pollTimer);
this.pollTimer = null;
}
if (toast) {
toast.clear();
}
}, maxPollCount * 2000);
},
}
}
</script>
<style scoped lang="scss">
/* 全局样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, sans-serif;
background-color: #f5f5f5;
color: #333;
line-height: 1.6;
}
.app-container {
padding-bottom: 100px;
}
/* 背景层 */
.bj {
background: #f5f5f5;
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: -1;
}
/* 标题样式 */
.left-label {
display: flex;
align-items: center;
padding: 15px 15px 10px;
font-size: 0.45rem;
font-weight: bold;
}
.label-color {
width: 4px;
height: 16px;
background-color: #166bcc;
margin-right: 8px;
border-radius: 2px;
}
/* 卡片样式 */
.card {
background: #fff;
border-radius: 8px;
margin: 0 12px 15px;
padding: 12px 15px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
}
/* 单元格样式 */
.cell {
display: flex;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px solid #f5f5f5;
}
.cell:last-child {
border-bottom: none;
}
.cell_title {
font-size: 0.35rem;
color: #666;
}
.cell_value {
font-size: 0.35rem;
color: #333;
text-align: right;
font-weight: 500;
}
/* 支付方式样式 */
.pay_type_list {
display: flex;
align-items: center;
}
/* 统一图片容器宽度,确保图片和文字都左对齐 */
.pay_type_list>div:first-child {
display: flex;
justify-content: flex-start;
width: 1.8rem;
/* 调整为适应1.2rem图片的容器宽度 */
margin-right: 0.3rem;
}
.pay_type_list>div:last-child {
font-size: 0.4rem;
font-weight: 500;
}
/* 进一步放大图片扩大0.5倍即原来的1.5倍) */
.pay-image {
width: 0.9rem;
height: 0.9rem;
}
.medical-insurance-image {
width: 1.2rem;
height: 1.2rem;
object-fit: contain;
}
.pay-item-label {
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
width: 100%;
}
/* 按钮样式 */
.btn {
position: fixed;
// bottom: 30px;
margin-top: 1rem;
left: 5%;
width: 90%;
height: 1rem;
background-color: #166bcc;
color: white;
border: none;
border-radius: 10px;
font-size: 0.4rem;
text-align: center;
}
</style>