diff --git a/src/apis.js b/src/apis.js
index 5e38eb0..de645e1 100644
--- a/src/apis.js
+++ b/src/apis.js
@@ -366,6 +366,17 @@ export const createOrderAPI = (vipId) => {
});
};
+export const virtualPayOrderAPI = (vipId = 0, code = "") => {
+ return request("POST", "/user/virtualPay/createOrder", {
+ vipId,
+ code,
+ });
+};
+
+export const getOrderDetailAPI = (orderId) => {
+ return request("GET", `/user/order/detail?orderId=${encodeURIComponent(orderId)}`);
+};
+
export const payOrderAPI = (id) => {
return request("POST", "/user/order/pay", {
id,
diff --git a/src/components/ScoreResult.vue b/src/components/ScoreResult.vue
index 3464483..c08381f 100644
--- a/src/components/ScoreResult.vue
+++ b/src/components/ScoreResult.vue
@@ -143,7 +143,7 @@ const openCoachComment = () => {
}}环的成绩,所有箭支上靶后的平均点间距为{{ Number((result.average_distance || 0).toFixed(2)) }}{{ Number((result?.interpretation?.spreadStability || 0).toFixed(2)) }},{{
result.spreadEvaluation === "Dispersed"
? "还需要持续改进哦~"
diff --git a/src/pages/first-try.vue b/src/pages/first-try.vue
index cc40e37..9eef54c 100644
--- a/src/pages/first-try.vue
+++ b/src/pages/first-try.vue
@@ -29,6 +29,7 @@ import { storeToRefs } from "pinia";
const store = useStore();
const { user } = storeToRefs(store);
const scores = ref([]);
+const isSvip = ref(false);
const step = ref(0);
const total = 12;
const stepButtonTexts = [
@@ -114,6 +115,7 @@ const onOver = async () => {
async function onReceiveMessage(msg) {
if (msg.type === MESSAGETYPESV2.ShootResult) {
+ isSvip.value = msg.sVip === true;
scores.value = msg.details;
} else if (msg.type === MESSAGETYPESV2.BattleEnd) {
setTimeout(onOver, 1500);
@@ -204,6 +206,7 @@ const nextStep = async () => {
title.value = "小试牛刀";
await startPractiseAPI();
scores.value = [];
+ isSvip.value = false;
step.value = 5;
start.value = true;
setTimeout(() => {
@@ -230,6 +233,7 @@ const onClose = async () => {
practiseResult.value = {};
start.value = false;
scores.value = [];
+ isSvip.value = false;
step.value = 4;
const result = await createPractiseAPI(total, 120);
if (result) practiseId.value = result.id;
@@ -343,6 +347,7 @@ const onClose = async () => {
:currentRound="step === 5 ? scores.length : 0"
:totalRound="step === 5 ? total : 0"
:scores="scores"
+ :isSvip="isSvip"
/>
-import { computed, ref, onBeforeUnmount } from "vue";
+import { computed, ref } from "vue";
import { onShow } from "@dcloudio/uni-app";
import Container from "@/components/Container.vue";
import Signin from "@/components/Signin.vue";
-import { createOrderAPI, getAppConfig, getHomeData } from "@/apis";
-import { capsuleHeight } from "@/util";
+import { virtualPayOrderAPI, getAppConfig, getHomeData, getOrderDetailAPI } from "@/apis";
+import { capsuleHeight, wxLogin } from "@/util";
import useStore from "@/store";
import { storeToRefs } from "pinia";
@@ -18,9 +18,6 @@ const showModal = ref(false);
const loadingConfig = ref(false);
const paying = ref(false);
const refreshing = ref(false);
-const timer = ref(null);
-const lastDate = ref(user.value.expiredAt || 0);
-const maxRefreshTimes = 12;
// 会员页核心展示数据:视觉、权益、套餐均按蓝湖当前两张设计稿拆分。
const memberTypes = [
@@ -100,16 +97,6 @@ const getVipExpiredValue = (type, source = user.value) => {
return type.key === "super" ? source.superVipExpiredAt : source.normalVipExpiredAt;
};
-// 支付后轮询需要比较最新会员到期时间,兼容旧字段 expiredAt。
-const getLatestVipExpiredTime = (source = user.value) => {
- if (!source) return 0;
- return Math.max(
- toTimestamp(source.normalVipExpiredAt),
- toTimestamp(source.superVipExpiredAt),
- toTimestamp(source.expiredAt)
- );
-};
-
// 未过期才展示“会员生效中”样式,已过期或无值继续展示未开通样式。
const isVipActive = (type) => {
return toTimestamp(getVipExpiredValue(type)) > Date.now();
@@ -263,41 +250,38 @@ const loadVipConfig = async () => {
}
};
-const clearRefreshTimer = () => {
- if (timer.value) {
- clearInterval(timer.value);
- timer.value = null;
+const refreshUserAfterPay = async () => {
+ refreshing.value = true;
+ try {
+ const result = await getHomeData();
+ if (result.user) {
+ updateUser(result.user);
+ }
+ } catch (error) {
+ console.log("refresh user after pay error", error);
+ } finally {
+ refreshing.value = false;
}
};
-const refreshUserAfterPay = () => {
- clearRefreshTimer();
- let refreshTimes = 0;
- // 先记录支付前的最大到期时间,轮询到更大的值就认为会员状态已刷新。
- lastDate.value = getLatestVipExpiredTime();
- refreshing.value = true;
- timer.value = setInterval(async () => {
- refreshTimes += 1;
- try {
- const result = await getHomeData();
- const latestExpiredAt = getLatestVipExpiredTime(result.user);
- if (result.user && latestExpiredAt > lastDate.value) {
- lastDate.value = latestExpiredAt;
- updateUser(result.user);
- clearRefreshTimer();
- refreshing.value = false;
- } else if (refreshTimes >= maxRefreshTimes) {
- clearRefreshTimer();
- refreshing.value = false;
- }
- } catch (error) {
- console.log("refresh user after pay error", error);
- if (refreshTimes >= maxRefreshTimes) {
- clearRefreshTimer();
- refreshing.value = false;
- }
- }
- }, 1000);
+const getVirtualPaySignData = (result) => {
+ // 后端可能返回字符串或对象,这里统一转成微信虚拟支付需要的 JSON 字符串。
+ const signDataObj = typeof result?.signData === "string" ? JSON.parse(result.signData) : result?.signData;
+ if (!signDataObj) return "";
+
+ // 微信虚拟支付短剧/虚拟商品模式需要购买数量;后端未返回时使用订单数量兜底。
+ if (!signDataObj.buyQuantity) {
+ signDataObj.buyQuantity = result.quantity || 1;
+ }
+ return JSON.stringify(signDataObj);
+};
+
+const isVirtualPayCancel = (res = {}) => {
+ const message = String(res.errMsg || res.message || "").toLowerCase();
+ const errCode = Number(res.errCode);
+ const errno = Number(res.errno);
+
+ return message.includes("cancel") || errCode === -23 || errno === -2;
};
const onPay = async () => {
@@ -317,12 +301,22 @@ const onPay = async () => {
});
return;
}
+ if (typeof wx === "undefined" || !wx.requestVirtualPayment) {
+ uni.showToast({
+ title: "当前环境不支持微信虚拟支付",
+ icon: "none",
+ });
+ return;
+ }
paying.value = true;
let waitingPayment = false;
+ let payToast = null;
try {
- const result = await createOrderAPI(vipId);
- const params = result?.pay?.order?.jsApi?.params;
- if (!params?.timeStamp || !params?.nonceStr || !params?.package || !params?.paySign) {
+ // 微信虚拟支付创建订单前需要登录 code,服务端用它换取本次支付签名参数。
+ const wxResult = await wxLogin();
+ const result = await virtualPayOrderAPI(vipId, wxResult.code);
+ const finalSignData = getVirtualPaySignData(result);
+ if (!finalSignData || !result?.paySig || !result?.signature) {
uni.showToast({
title: "支付参数生成失败",
icon: "none",
@@ -330,34 +324,57 @@ const onPay = async () => {
refreshing.value = false;
return;
}
- waitingPayment = true;
- wx.requestPayment({
- timeStamp: params.timeStamp, // 微信支付时间戳
- nonceStr: params.nonceStr, // 微信支付随机串
- package: params.package, // 预支付交易会话标识
- paySign: params.paySign, // 微信支付签名
- signType: params.signType || "RSA",
- success() {
- uni.showToast({
+ wx.requestVirtualPayment({
+ signData: finalSignData,
+ paySig: result.paySig,
+ signature: result.signature,
+ mode: "short_series_goods",
+ async success() {
+ payToast = {
title: "支付成功",
- icon: "none",
- });
+ icon: "success",
+ };
+ if (result?.outTradeNo) {
+ try {
+ const orderDetail = await getOrderDetailAPI(result.outTradeNo);
+ console.log("virtual pay order detail", orderDetail);
+ } catch (error) {
+ console.log("virtual pay order detail error", error);
+ }
+ }
+ // 客户端支付成功后,会员是否真正生效仍以服务端用户信息刷新结果为准。
refreshUserAfterPay();
},
fail(res) {
- console.log("pay error", res);
- if (res.errMsg && res.errMsg.indexOf("cancel") !== -1) return;
- uni.showToast({
- title: "支付失败,请稍后重试",
+ console.log("virtual pay error", res);
+ if (isVirtualPayCancel(res)) {
+ payToast = {
+ title: "支付已取消",
+ icon: "none",
+ };
+ return;
+ }
+ payToast = {
+ title: res.message || "支付失败,请稍后重试",
icon: "none",
- });
+ };
},
complete() {
paying.value = false;
+ if (payToast) {
+ setTimeout(() => {
+ uni.showToast(payToast);
+ }, 200);
+ }
},
});
+ waitingPayment = true;
} catch (error) {
- console.log("create vip order error", error);
+ console.log("create virtual pay order error", error);
+ uni.showToast({
+ title: error.message || "下单失败",
+ icon: "none",
+ });
} finally {
if (!waitingPayment) paying.value = false;
}
@@ -365,9 +382,6 @@ const onPay = async () => {
onShow(loadVipConfig);
-onBeforeUnmount(() => {
- clearRefreshTimer();
-});
diff --git a/src/pages/member/order-detail.vue b/src/pages/member/order-detail.vue
index ebd0f8d..d22b329 100644
--- a/src/pages/member/order-detail.vue
+++ b/src/pages/member/order-detail.vue
@@ -97,12 +97,10 @@ const cancelOrder = async () => {
>复制
- 下单时间:{{ data.vipCreateAt }}
- 支付时间:{{
+ 创建时间:{{ data.orderCreateAt }}
+ 支付时间:{{
data.orderStatus === 4 ? data.paymentTime : ""
- }}
+ }}
金额:{{ data.total }} 元
支付方式:微信
diff --git a/src/pages/rank-intro.vue b/src/pages/rank-intro.vue
index 31769cd..8826219 100644
--- a/src/pages/rank-intro.vue
+++ b/src/pages/rank-intro.vue
@@ -13,7 +13,7 @@ import Container from "@/components/Container.vue";
一、段位体系概述
- 我们的段位体系分为多个等级,从低到高依次为:倔强青铜、秩序白银、黄金王者、永恒钻石、最强王者、非凡王者、无双王者、绝世王者、至圣王者、荣耀王者和传奇王者。每个大段位下又分为若干小段位,玩家需要通过积累积分来提升段位。
+ 我们的段位体系分为多个等级,从低到高依次为:倔强青铜、秩序白银、荣耀黄金、永恒钻石、最强王者、非凡王者、无双王者、绝世王者、至圣王者、荣耀王者和传奇王者。每个大段位下又分为若干小段位,玩家需要通过积累积分来提升段位。
@@ -79,7 +79,7 @@ import Container from "@/components/Container.vue";
每个小段位需要满 3颗星才能晋升到下一个段位,共9颗星。
- 黄金王者
+ 荣耀黄金
黄金1*
黄金2*