You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

726 lines
18 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="container">
<!-- 顶部标题 -->
<view class="header">
<view class="header-title">管理您的账户信息和服务</view>
</view>
<!-- 用户信息卡片 -->
<view class="user-card">
<view class="user-info">
<image class="avatar" src="/static/imgs/avatar.png" mode="aspectFill"></image>
<view class="user-details">
<view class="username">
{{userStore.userInfo.username}}
<u-icon name="checkmark-circle-fill" color="#ffa500" size="16"></u-icon>
</view>
<view class="user-id">用户ID: {{userStore.userInfo.id}}</view>
<view class="membership">
会员等级: 年会员 (有效期至: 2026-05-15)
</view>
</view>
</view>
<view class="edit-btn" @click="editProfile">
<text>点击编辑资料</text>
</view>
</view>
<!-- 账户余额 -->
<view class="balance-card">
<div>
<view class="balance-header">
<view class="balance-label">账户余额</view>
</view>
<view class="balance-amount">¥50.00</view>
</div>
<uv-button type="primary" color="#fff" customStyle="color:#000;width:90px" shape="circle" @click="recharge">
</uv-button>
</view>
<!-- 会员套餐 -->
<view class="membership-section">
<view class="membership-grid">
<view class="membership-item" :class="{ active: selectedPlan === item.id }" v-for="item in planList"
:key="item.id" @click="selectPlan(item)">
<view class="tag" v-if="item.tags">{{item.tags}}</view>
<view class="plan-name">{{item.name}}</view>
<view class="plan-price">¥{{item.price_cents}}</view>
</view>
<!-- <view
class="membership-item"
:class="{ active: selectedPlan === 'season' }"
@click="selectPlan('season')"
>
<view class="tag">最受欢迎</view>
<view class="plan-name">包季会员</view>
<view class="plan-price">¥300</view>
<view class="plan-discount">立减¥60</view>
</view>
<view
class="membership-item"
:class="{ active: selectedPlan === 'yearly' }"
@click="selectPlan('yearly')"
>
<view class="tag">年度首享</view>
<view class="plan-name">包年会员</view>
<view class="plan-price">¥1000</view>
<view class="plan-discount">立减¥440</view>
</view> -->
</view>
</view>
<!-- 支付方式 -->
<view class="payment-section">
<!-- 微信支付 -->
<view class="payment-item" :class="{ active: selectedPayment === 'wallet' }"
@click="selectPayment('wallet')">
<view class="payment-info">
<uv-icon name="empty-coupon" color="#07c160" size="24"></uv-icon>
<text class="payment-name">钱包支付</text>
</view>
<uv-icon name="checkmark-circle-fill" :color="selectedPayment === 'wallet' ? '#ff3333' : '#ccc'"
size="20"></uv-icon>
</view>
<view class="payment-item" :class="{ active: selectedPayment === 'wxpay' }" @click="selectPayment('wxpay')">
<view class="payment-info">
<uv-icon name="weixin-fill" color="#07c160" size="24"></uv-icon>
<text class="payment-name">微信支付</text>
</view>
<uv-icon name="checkmark-circle-fill" :color="selectedPayment === 'wxpay' ? '#ff3333' : '#ccc'"
size="20"></uv-icon>
</view>
<!-- 支付宝支付 - 仅在H5环境显示 -->
<view v-if="showAlipay" class="payment-item" :class="{ active: selectedPayment === 'alipay' }"
@click="selectPayment('alipay')">
<view class="payment-info">
<uv-icon name="/static/imgs/alipay.png" color="#1677ff" size="24"></uv-icon>
<text class="payment-name">支付宝</text>
</view>
<uv-icon name="checkmark-circle-fill" :color="selectedPayment === 'alipay' ? '#ff3333' : '#ccc'"
size="20"></uv-icon>
</view>
</view>
<!-- 底部按钮 -->
<view class="bottom-section">
<uv-button type="primary" size="large" @click="confirmPayment" :loading="paying">
</uv-button>
<view class="agreement">
购买前请仔细阅读
<text class="link" @click="showAgreement"></text>
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from "vue";
import {
onShow
} from "@dcloudio/uni-app";
import HeaderCustom from "/components/HeaderCustom/HeaderCustom.vue";
import {
useAuthStore,
useUserStore
} from "@/store";
import RPC from "@/utils/request";
const modal = ref(null);
const authStore = useAuthStore();
const userStore = useUserStore();
// 响应式数据
const selectedPlan = ref("");
const selectedPayment = ref("wxpay"); // 默认选中微信支付
const paying = ref(false);
const showAlipay = ref(true); // 控制是否显示支付宝选项
const planList = ref([])
const getPlanList = () => {
RPC.post('/membership_plan/list', {
page: 1,
limit: -1,
orderby: [{
sort: 'asc'
}]
}).then(res => {
planList.value = (res.rows || []).filter(item => item.enabled)
selectedPlan.value = planList.value[0] ? planList.value[0].id : ''
})
}
getPlanList()
// 选择会员套餐
const selectPlan = (plan) => {
selectedPlan.value = plan.id;
};
// 选择支付方式
const selectPayment = (payment) => {
selectedPayment.value = payment;
};
// 编辑资料
const editProfile = () => {
uni.navigateTo({
url: "/pagesA/edit-profile/edit-profile",
});
};
// 充值
const recharge = () => {
uni.navigateTo({
url: "/pagesA/recharge/recharge",
});
};
// 微信支付
const wechatPay = async (orderInfo) => {
// #ifdef MP-WEIXIN
// 微信小程序支付
try {
const paymentResult = await uni.requestPayment({
provider: "wxpay",
timeStamp: orderInfo.timeStamp,
nonceStr: orderInfo.nonceStr,
package: orderInfo.package,
signType: orderInfo.signType,
paySign: orderInfo.paySign,
});
console.log("微信支付成功:", paymentResult);
return {
success: true,
data: paymentResult
};
} catch (error) {
console.error("微信支付失败:", error);
if (error.errMsg === "requestPayment:fail cancel") {
throw new Error("用户取消支付");
}
throw new Error("微信支付失败");
}
// #endif
// #ifdef H5
// H5微信支付
if (typeof WeixinJSBridge !== "undefined") {
return new Promise((resolve, reject) => {
WeixinJSBridge.invoke(
"getBrandWCPayRequest", {
appId: orderInfo.appId,
timeStamp: orderInfo.timeStamp,
nonceStr: orderInfo.nonceStr,
package: orderInfo.package,
signType: orderInfo.signType,
paySign: orderInfo.paySign,
},
(res) => {
if (res.err_msg === "get_brand_wcpay_request:ok") {
resolve({
success: true,
data: res
});
} else if (res.err_msg === "get_brand_wcpay_request:cancel") {
reject(new Error("用户取消支付"));
} else {
reject(new Error("微信支付失败"));
}
}
);
});
} else {
// 不在微信环境,跳转到支付页面
const payUrl = orderInfo.mwebUrl; // 后端返回的H5支付链接
window.location.href = payUrl;
return {
success: true,
redirect: true
};
}
// #endif
};
// 支付宝支付仅H5环境
const alipayPay = async (orderInfo) => {
// #ifdef H5
// H5支付宝支付
const payUrl = orderInfo.payUrl; // 后端返回的支付宝H5支付链接
window.location.href = payUrl;
return {
success: true,
redirect: true
};
// #endif
};
// 确认支付
const confirmPayment = async () => {
if (!selectedPlan.value) {
uni.showToast({
title: "请选择会员套餐",
icon: "none",
});
return;
}
if (!selectedPayment.value) {
uni.showToast({
title: "请选择支付方式",
icon: "none",
});
return;
}
paying.value = true;
try {
// 1. 创建订单参数
const orderParams = {
user_id: userStore.userInfo?.id,
plan_id: selectedPlan.value,
pay_channel: selectedPayment.value
};
uni.showLoading({
title: "正在创建订单...",
});
// 2. 创建订单
const orderNum = await RPC.post('/membership/order/create', orderParams);
// 模拟创建订单返回的数据
// const orderResult = {
// orderId: "ORDER_" + Date.now(),
// amount: orderParams.amount,
// // 微信支付参数(实际应该由后端返回)
// wechatPayInfo: {
// appId: "wx1234567890",
// timeStamp: String(Date.now()),
// nonceStr: "random_string_" + Date.now(),
// package: "prepay_id=wx_prepay_id_123456",
// signType: "RSA",
// paySign: "mock_pay_sign_123456",
// // H5支付链接
// mwebUrl: "https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx_prepay_id_123456",
// },
// // 支付宝支付参数(实际应该由后端返回)
// alipayInfo: {
// orderString: "mock_alipay_order_string_123456",
// payUrl: "https://openapi.alipay.com/gateway.do?mock_pay_url",
// },
// };
uni.hideLoading();
// 3. 根据支付方式调用对应的支付方法
let paymentResult;
if (selectedPayment.value === "wxpay") {
const orderResult = await RPC.post('/membership/order/pay_by_wechat', {
"order_no": orderNum.order_no
})
uni.showLoading({
title: "正在跳转微信支付...",
});
if (1 == 1) {
console.log('orderResult', orderResult)
return
}
paymentResult = await wechatPay(orderResult.wechatPayInfo);
} else if (selectedPayment.value === "alipay") {
uni.showLoading({
title: "正在跳转支付宝...",
});
paymentResult = await alipayPay(orderResult.alipayInfo);
}
uni.hideLoading();
// 4. 处理支付结果
if (paymentResult.success) {
if (paymentResult.redirect) {
// H5跳转支付不需要额外处理
uni.showToast({
title: "正在跳转支付页面...",
icon: "loading",
duration: 2000,
});
} else {
// 支付成功
uni.showToast({
title: "支付成功",
icon: "success",
});
// 延迟跳转到订单页面或刷新用户信息
setTimeout(() => {
// 可以跳转到订单详情页面
uni.navigateTo({
url: `/pages/order-detail/order-detail?orderId=${orderResult.orderId}`,
});
// 或者刷新当前页面的用户信息
// loadUserInfo();
}, 1500);
}
}
} catch (error) {
uni.hideLoading();
console.error("支付失败:", error);
console.log('error', error)
let errorMessage = "支付失败,请重试";
if (error.message === "用户取消支付") {
errorMessage = "支付已取消";
} else if (error.message) {
errorMessage = error.message;
} else if (error) {
errorMessage = error
}
uni.showToast({
title: errorMessage,
icon: "none",
});
} finally {
paying.value = false;
}
};
// 显示协议
const showAgreement = () => {
uni.navigateTo({
url: "/pagesA/user-agreement/user-agreement",
});
};
// 检查支付结果用于H5支付回调
const checkPaymentResult = async (orderId) => {
try {
// 调用后端接口检查支付结果
// const result = await RPC.get(`/api/order/check-payment/${orderId}`);
// 模拟检查结果
const result = {
success: true,
status: "paid", // paid, pending, failed
};
if (result.status === "paid") {
uni.showToast({
title: "支付成功",
icon: "success",
});
// 更新用户信息或跳转到成功页面
setTimeout(() => {
uni.navigateTo({
url: `/pages/order-detail/order-detail?orderId=${orderId}`,
});
}, 1500);
} else if (result.status === "failed") {
uni.showToast({
title: "支付失败",
icon: "none",
});
} else {
uni.showToast({
title: "支付处理中,请稍后查看订单状态",
icon: "none",
});
}
} catch (error) {
console.error("检查支付结果失败:", error);
}
};
// 页面显示时检查是否有支付回调参数
const handlePaymentCallback = () => {
// #ifdef H5
const urlParams = new URLSearchParams(window.location.search);
const orderId = urlParams.get("orderId");
const payResult = urlParams.get("payResult");
if (orderId && payResult) {
// 清理URL参数
const url = new URL(window.location);
url.searchParams.delete("orderId");
url.searchParams.delete("payResult");
window.history.replaceState({}, document.title, url.pathname + url.search);
// 检查支付结果
if (payResult === "success") {
checkPaymentResult(orderId);
} else {
uni.showToast({
title: "支付失败或取消",
icon: "none",
});
}
}
// #endif
};
// 检查当前环境并设置支付选项
const checkEnvironment = () => {
// #ifdef MP-WEIXIN
// 微信小程序环境,隐藏支付宝选项,默认选择微信支付
showAlipay.value = false;
// #endif
// #ifdef H5
// H5环境显示所有支付选项
showAlipay.value = true;
// 如果在微信浏览器内,默认选择微信支付
const ua = navigator.userAgent.toLowerCase();
const isWechat = ua.indexOf("micromessenger") !== -1;
// #endif
};
// 页面挂载时处理支付回调和环境检查
onMounted(() => {
checkEnvironment();
handlePaymentCallback();
});
// 页面显示时也检查支付回调(从其他页面返回时)
onShow(() => {
handlePaymentCallback();
});
</script>
<style lang="scss" scoped>
.container {
font-size: 14px;
background: linear-gradient(180deg, #7b9cdb 0%, #f8f8f8 40%);
padding-bottom: 120px;
/* #ifdef H5 */
padding-bottom: 170px;
/* H5环境下需要更多空间 (底部按钮高度 + tabBar高度 + 间距) */
/* #endif */
}
.header {
padding: 20px 16px 16px;
.header-title {
color: white;
font-size: 18px;
font-weight: 500;
}
}
.user-card {
margin: 0 16px 16px;
background: white;
border-radius: 12px;
padding: 16px;
position: relative;
.user-info {
display: flex;
align-items: center;
// margin-bottom: 12px;
.avatar {
width: 60px;
height: 60px;
border-radius: 50%;
margin-right: 12px;
overflow: hidden;
.img {
width: 100%;
height: 100%;
}
}
.user-details {
flex: 1;
.username {
display: flex;
align-items: center;
gap: 4px;
font-size: 18px;
font-weight: bold;
color: #333;
margin-bottom: 4px;
}
.user-id {
font-size: 12px;
color: #999;
margin-bottom: 2px;
}
.membership {
font-size: 12px;
color: #999;
}
}
}
.edit-btn {
text-align: right;
color: #4285f4;
font-size: 14px;
position: absolute;
top: 10px;
right: 20px;
}
}
.balance-card {
display: flex;
justify-content: space-between;
align-items: center;
margin: 0 16px 16px;
background: linear-gradient(135deg, #4285f4 0%, #6fa8f5 100%);
border-radius: 12px;
padding: 16px;
color: white;
.balance-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
.balance-label {
font-size: 14px;
opacity: 0.9;
}
}
.balance-amount {
font-size: 28px;
font-weight: bold;
}
}
.membership-section {
margin: 0 16px 16px;
.membership-grid {
display: flex;
justify-content: space-between;
gap: 8px;
overflow-x: auto;
.membership-item {
width: 24%;
flex-shrink: 0;
background: white;
border-radius: 12px;
padding: 16px 12px;
text-align: center;
position: relative;
border: 2px solid transparent;
transition: all 0.3s;
overflow: hidden;
&.active {
border-color: #4285f4;
background-color: #f0f7ff;
}
.tag {
position: absolute;
top: 0px;
left: 0;
padding: 2px 8px;
border-radius: 0 0 10px 0;
font-size: 10px;
color: white;
background: #ff3333;
}
.plan-name {
font-size: 14px;
color: #333;
margin: 12px 0 8px;
}
.plan-price {
font-size: 20px;
font-weight: bold;
color: #ff3333;
margin-bottom: 4px;
}
.plan-discount {
font-size: 12px;
color: #999;
}
}
}
}
.payment-section {
margin: 0 16px 24px;
.payment-item {
display: flex;
align-items: center;
justify-content: space-between;
background: white;
border-radius: 12px;
padding: 16px;
margin-bottom: 8px;
border: 1px solid #e5e5e5;
transition: all 0.3s;
&.active {
border-color: #4285f4;
background-color: #f0f7ff;
}
&:last-child {
margin-bottom: 0;
}
.payment-info {
display: flex;
align-items: center;
gap: 12px;
.payment-name {
font-size: 14px;
color: #333;
}
}
}
}
.bottom-section {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
padding: 16px;
border-top: 1px solid #e5e5e5;
/* #ifdef H5 */
bottom: 50px;
/* H5环境下给tabBar留出空间 */
/* #endif */
.agreement {
text-align: center;
font-size: 12px;
color: #999;
margin-top: 12px;
.link {
color: #4285f4;
}
}
}
</style>