update:对接会员限制次数
This commit is contained in:
19
src/apis.js
19
src/apis.js
@@ -23,7 +23,9 @@ try {
|
|||||||
console.error("获取环境信息失败,使用默认正式环境", e);
|
console.error("获取环境信息失败,使用默认正式环境", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
function request(method, url, data = {}) {
|
const ADDONS_BASE_URL = BASE_URL.replace(/\/api\/shoot$/, "/api/shoot");
|
||||||
|
|
||||||
|
function request(method, url, data = {}, baseUrl = BASE_URL) {
|
||||||
const token = uni.getStorageSync(
|
const token = uni.getStorageSync(
|
||||||
`${uni.getAccountInfoSync().miniProgram.envVersion}_token`
|
`${uni.getAccountInfoSync().miniProgram.envVersion}_token`
|
||||||
);
|
);
|
||||||
@@ -31,7 +33,7 @@ function request(method, url, data = {}) {
|
|||||||
if (token) header.Authorization = `Bearer ${token || ""}`;
|
if (token) header.Authorization = `Bearer ${token || ""}`;
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
uni.request({
|
uni.request({
|
||||||
url: `${BASE_URL}${url}`,
|
url: `${baseUrl}${url}`,
|
||||||
method,
|
method,
|
||||||
header,
|
header,
|
||||||
data,
|
data,
|
||||||
@@ -41,6 +43,7 @@ function request(method, url, data = {}) {
|
|||||||
const {code, data, message} = res.data;
|
const {code, data, message} = res.data;
|
||||||
if (code === 0) resolve(data);
|
if (code === 0) resolve(data);
|
||||||
else if (message) {
|
else if (message) {
|
||||||
|
const error = {code, data, message};
|
||||||
if (message.indexOf("登录身份已失效") !== -1) {
|
if (message.indexOf("登录身份已失效") !== -1) {
|
||||||
console.log('1111111111111111111,token失效')
|
console.log('1111111111111111111,token失效')
|
||||||
uni.removeStorageSync(
|
uni.removeStorageSync(
|
||||||
@@ -50,6 +53,10 @@ function request(method, url, data = {}) {
|
|||||||
reject({ type: "AUTH_INVALID", message });
|
reject({ type: "AUTH_INVALID", message });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (message.indexOf("已达上限") !== -1) {
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (message === "ROOM_FULL") {
|
if (message === "ROOM_FULL") {
|
||||||
resolve({full: true});
|
resolve({full: true});
|
||||||
return;
|
return;
|
||||||
@@ -88,8 +95,10 @@ function request(method, url, data = {}) {
|
|||||||
title: message,
|
title: message,
|
||||||
icon: "none",
|
icon: "none",
|
||||||
});
|
});
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
reject("");
|
reject({code, data, message});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fail: (err) => {
|
fail: (err) => {
|
||||||
@@ -158,6 +167,10 @@ export const getAppConfig = () => {
|
|||||||
return request("GET", "/index/appConfig");
|
return request("GET", "/index/appConfig");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getDailyCountAPI = () => {
|
||||||
|
return request("GET", "/index/dailyCount", {}, ADDONS_BASE_URL);
|
||||||
|
};
|
||||||
|
|
||||||
export const getHomeData = (seasonId) => {
|
export const getHomeData = (seasonId) => {
|
||||||
return request("GET", `/user/myHome?seasonId=${seasonId}`);
|
return request("GET", `/user/myHome?seasonId=${seasonId}`);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -456,23 +456,29 @@ export const generateShareImage = async (canvasId, data) => {
|
|||||||
// 2D 即时绘制,无需 ctx.draw()
|
// 2D 即时绘制,无需 ctx.draw()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("generateShareImage 绘制失败:", e);
|
console.error("generateShareImage 绘制失败:", e);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 顶部导入与工具方法
|
// 顶部导入与工具方法
|
||||||
async function getCanvas2DContext(canvasId, targetWidth, targetHeight) {
|
async function getCanvas2DContext(canvasId, targetWidth, targetHeight) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve, reject) => {
|
||||||
const query = uni.createSelectorQuery();
|
const query = uni.createSelectorQuery();
|
||||||
query
|
query
|
||||||
.select(`#${canvasId}`)
|
.select(`#${canvasId}`)
|
||||||
.fields({ node: true, size: true })
|
.fields({ node: true, size: true })
|
||||||
.exec((res) => {
|
.exec((res) => {
|
||||||
const { node: canvas } = res[0] || {};
|
const canvasInfo = res && res[0];
|
||||||
|
const { node: canvas } = canvasInfo || {};
|
||||||
|
if (!canvas || typeof canvas.getContext !== "function") {
|
||||||
|
reject(new Error(`canvas ${canvasId} not found`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
const dpr = uni.getSystemInfoSync().pixelRatio || 1;
|
const dpr = uni.getSystemInfoSync().pixelRatio || 1;
|
||||||
|
|
||||||
const w = targetWidth || res[0].width;
|
const w = targetWidth || canvasInfo.width;
|
||||||
const h = targetHeight || res[0].height;
|
const h = targetHeight || canvasInfo.height;
|
||||||
|
|
||||||
canvas.width = w * dpr;
|
canvas.width = w * dpr;
|
||||||
canvas.height = h * dpr;
|
canvas.height = h * dpr;
|
||||||
@@ -561,6 +567,7 @@ export const sharePointData = async (canvasId, data) => {
|
|||||||
// 2D 即时绘制,无需 ctx.draw()
|
// 2D 即时绘制,无需 ctx.draw()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("generateShareImage 绘制失败:", e);
|
console.error("generateShareImage 绘制失败:", e);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -778,6 +785,7 @@ export async function sharePractiseData(canvasId, type, user, data) {
|
|||||||
// 2D 模式下无需 ctx.draw()
|
// 2D 模式下无需 ctx.draw()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import GuideTwo from "@/components/GuideTwo.vue";
|
|||||||
import SButton from "@/components/SButton.vue";
|
import SButton from "@/components/SButton.vue";
|
||||||
import Avatar from "@/components/Avatar.vue";
|
import Avatar from "@/components/Avatar.vue";
|
||||||
import ScreenHint from "@/components/ScreenHint.vue";
|
import ScreenHint from "@/components/ScreenHint.vue";
|
||||||
|
import ModalDialog from "@/components/ModalDialog.vue";
|
||||||
import {
|
import {
|
||||||
getRoomAPI,
|
getRoomAPI,
|
||||||
exitRoomAPI,
|
exitRoomAPI,
|
||||||
@@ -14,6 +15,7 @@ import {
|
|||||||
getReadyAPI,
|
getReadyAPI,
|
||||||
kickPlayerAPI,
|
kickPlayerAPI,
|
||||||
} from "@/apis";
|
} from "@/apis";
|
||||||
|
import { isLimitError } from "@/util";
|
||||||
import { MESSAGETYPES, MESSAGETYPESV2 } from "@/constants";
|
import { MESSAGETYPES, MESSAGETYPESV2 } from "@/constants";
|
||||||
import useStore from "@/store";
|
import useStore from "@/store";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
@@ -56,6 +58,7 @@ const ready = ref(false);
|
|||||||
const allReady = ref(false);
|
const allReady = ref(false);
|
||||||
const timer = ref(null);
|
const timer = ref(null);
|
||||||
const goBattle = ref(false);
|
const goBattle = ref(false);
|
||||||
|
const showLimitModal = ref(false);
|
||||||
/** 从结算页返回时为 true,跳过进场靶纸语音 */
|
/** 从结算页返回时为 true,跳过进场靶纸语音 */
|
||||||
const skipTargetAudio = ref(false);
|
const skipTargetAudio = ref(false);
|
||||||
|
|
||||||
@@ -137,7 +140,26 @@ async function refreshRoomData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getReady = async () => {
|
const getReady = async () => {
|
||||||
|
try {
|
||||||
await getReadyAPI(roomNumber.value);
|
await getReadyAPI(roomNumber.value);
|
||||||
|
} catch (error) {
|
||||||
|
if (isLimitError(error)) {
|
||||||
|
showLimitModal.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("room ready error", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeLimitModal = () => {
|
||||||
|
showLimitModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const goVipPage = () => {
|
||||||
|
showLimitModal.value = false;
|
||||||
|
uni.navigateTo({
|
||||||
|
url: "/pages/member/be-vip",
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshMembers = (members = []) => {
|
const refreshMembers = (members = []) => {
|
||||||
@@ -456,6 +478,14 @@ onBeforeUnmount(() => {
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</Container>
|
</Container>
|
||||||
|
<ModalDialog
|
||||||
|
:show="showLimitModal"
|
||||||
|
:content="'今日约战次数已经用完\n开通会员可增加次数'"
|
||||||
|
cancelText="知道了"
|
||||||
|
confirmText="去开通"
|
||||||
|
:onCancel="closeLimitModal"
|
||||||
|
:onConfirm="goVipPage"
|
||||||
|
/>
|
||||||
<!-- 踢出玩家二次确认弹窗(不传 onClose,屏蔽 X 关闭按钮) -->
|
<!-- 踢出玩家二次确认弹窗(不传 onClose,屏蔽 X 关闭按钮) -->
|
||||||
<ScreenHint :show="showKickConfirm">
|
<ScreenHint :show="showKickConfirm">
|
||||||
<view class="kick-confirm">
|
<view class="kick-confirm">
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ const practiseId = ref("");
|
|||||||
const showGuide = ref(false);
|
const showGuide = ref(false);
|
||||||
const laserActive = ref(false);
|
const laserActive = ref(false);
|
||||||
const guideSwiperIndex = ref(0);
|
const guideSwiperIndex = ref(0);
|
||||||
|
const sharing = ref(false);
|
||||||
|
|
||||||
const guideImages = [
|
const guideImages = [
|
||||||
"https://static.shelingxingqiu.com/shootmini/static/target.png",
|
"https://static.shelingxingqiu.com/shootmini/static/target.png",
|
||||||
@@ -139,8 +140,19 @@ async function onReceiveMessage(msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onClickShare = debounce(async () => {
|
const onClickShare = debounce(async () => {
|
||||||
|
if (sharing.value) return;
|
||||||
|
sharing.value = true;
|
||||||
|
try {
|
||||||
await sharePractiseData("shareCanvas", 1, user.value, practiseResult.value);
|
await sharePractiseData("shareCanvas", 1, user.value, practiseResult.value);
|
||||||
await wxShare("shareCanvas");
|
await wxShare("shareCanvas");
|
||||||
|
} catch (e) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "海报生成失败,请稍后重试",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
sharing.value = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -8,14 +8,16 @@ import SModal from "@/components/SModal.vue";
|
|||||||
import Signin from "@/components/Signin.vue";
|
import Signin from "@/components/Signin.vue";
|
||||||
import CreateRoom from "@/components/CreateRoom.vue";
|
import CreateRoom from "@/components/CreateRoom.vue";
|
||||||
import Avatar from "@/components/Avatar.vue";
|
import Avatar from "@/components/Avatar.vue";
|
||||||
|
import ModalDialog from "@/components/ModalDialog.vue";
|
||||||
|
|
||||||
import { getRoomAPI, joinRoomAPI, getBattleDataAPI } from "@/apis";
|
import { getRoomAPI, joinRoomAPI, getBattleDataAPI, getDailyCountAPI } from "@/apis";
|
||||||
import { debounce, canEenter } from "@/util";
|
import { debounce, canEenter, getLimitCountText, isLimitReached } from "@/util";
|
||||||
|
|
||||||
import useStore from "@/store";
|
import useStore from "@/store";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const { user, device, online, game } = storeToRefs(store);
|
const { user, device, online, game, dailyCount } = storeToRefs(store);
|
||||||
|
const { updateDailyCount } = store;
|
||||||
|
|
||||||
const showModal = ref(false);
|
const showModal = ref(false);
|
||||||
const showSignin = ref(false);
|
const showSignin = ref(false);
|
||||||
@@ -24,8 +26,12 @@ const roomNumber = ref("");
|
|||||||
const data = ref({});
|
const data = ref({});
|
||||||
const roomID = ref("");
|
const roomID = ref("");
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const showLimitModal = ref(false);
|
||||||
const isSVip = computed(() => user.value.sVip === true);
|
const isSVip = computed(() => user.value.sVip === true);
|
||||||
const isVip = computed(() => user.value.vip === true && !isSVip.value);
|
const isVip = computed(() => user.value.vip === true && !isSVip.value);
|
||||||
|
const challengeLimitText = computed(() =>
|
||||||
|
getLimitCountText("约战", dailyCount.value.challenge)
|
||||||
|
);
|
||||||
|
|
||||||
const enterRoom = debounce(async (number) => {
|
const enterRoom = debounce(async (number) => {
|
||||||
if (loading.value) return;
|
if (loading.value) return;
|
||||||
@@ -67,9 +73,37 @@ const enterRoom = debounce(async (number) => {
|
|||||||
});
|
});
|
||||||
const onCreateRoom = async () => {
|
const onCreateRoom = async () => {
|
||||||
if (!canEenter(user.value, device.value, online.value)) return;
|
if (!canEenter(user.value, device.value, online.value)) return;
|
||||||
|
const countData = await loadDailyCount();
|
||||||
|
if (isLimitReached(countData.challenge)) {
|
||||||
|
showLimitModal.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
warnning.value = "";
|
warnning.value = "";
|
||||||
showModal.value = true;
|
showModal.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const closeLimitModal = () => {
|
||||||
|
showLimitModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const goVipPage = () => {
|
||||||
|
showLimitModal.value = false;
|
||||||
|
uni.navigateTo({
|
||||||
|
url: "/pages/member/be-vip",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadDailyCount = async () => {
|
||||||
|
if (!user.value.id) return dailyCount.value;
|
||||||
|
try {
|
||||||
|
const result = await getDailyCountAPI();
|
||||||
|
updateDailyCount(result);
|
||||||
|
return result || dailyCount.value;
|
||||||
|
} catch (error) {
|
||||||
|
console.log("load daily count error", error);
|
||||||
|
return dailyCount.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
const onSignin = () => {
|
const onSignin = () => {
|
||||||
if (roomID.value && user.value.id) enterRoom(roomID.value);
|
if (roomID.value && user.value.id) enterRoom(roomID.value);
|
||||||
showSignin.value = false;
|
showSignin.value = false;
|
||||||
@@ -83,7 +117,10 @@ const goMyRecord = () => {
|
|||||||
};
|
};
|
||||||
onShow(async () => {
|
onShow(async () => {
|
||||||
if (user.value.id) {
|
if (user.value.id) {
|
||||||
const result = await getBattleDataAPI();
|
const [result] = await Promise.all([
|
||||||
|
getBattleDataAPI(),
|
||||||
|
loadDailyCount(),
|
||||||
|
]);
|
||||||
data.value = result;
|
data.value = result;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -168,8 +205,8 @@ onLoad(async (options) => {
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view>
|
<view>
|
||||||
<view class="pp-text">
|
<view v-if="challengeLimitText" class="pp-text">
|
||||||
<!-- 今日约战次数:2/2 -->
|
{{ challengeLimitText }}
|
||||||
</view>
|
</view>
|
||||||
<SButton width="80%" :rounded="30" :onClick="() => $clickSound(onCreateRoom)">
|
<SButton width="80%" :rounded="30" :onClick="() => $clickSound(onCreateRoom)">
|
||||||
创建约战房
|
创建约战房
|
||||||
@@ -186,6 +223,14 @@ onLoad(async (options) => {
|
|||||||
<Signin :show="showSignin" :onClose="onSignin" />
|
<Signin :show="showSignin" :onClose="onSignin" />
|
||||||
</view>
|
</view>
|
||||||
</Container>
|
</Container>
|
||||||
|
<ModalDialog
|
||||||
|
:show="showLimitModal"
|
||||||
|
:content="'今日约战次数已经用完\n开通会员可增加次数'"
|
||||||
|
cancelText="知道了"
|
||||||
|
confirmText="去开通"
|
||||||
|
:onCancel="closeLimitModal"
|
||||||
|
:onConfirm="goVipPage"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -3,12 +3,15 @@ import { ref, onMounted, onBeforeUnmount } from "vue";
|
|||||||
import { onLoad, onShow, onHide } from "@dcloudio/uni-app";
|
import { onLoad, onShow, onHide } from "@dcloudio/uni-app";
|
||||||
import Container from "@/components/Container.vue";
|
import Container from "@/components/Container.vue";
|
||||||
import Matching from "@/components/Matching.vue";
|
import Matching from "@/components/Matching.vue";
|
||||||
|
import ModalDialog from "@/components/ModalDialog.vue";
|
||||||
import { matchGameAPI, getBattleAPI } from "@/apis";
|
import { matchGameAPI, getBattleAPI } from "@/apis";
|
||||||
import { MESSAGETYPESV2 } from "@/constants";
|
import { MESSAGETYPESV2 } from "@/constants";
|
||||||
|
import { isLimitError } from "@/util";
|
||||||
|
|
||||||
const gameType = ref(0);
|
const gameType = ref(0);
|
||||||
const teamSize = ref(0);
|
const teamSize = ref(0);
|
||||||
const onComplete = ref(null);
|
const onComplete = ref(null);
|
||||||
|
const showLimitModal = ref(false);
|
||||||
|
|
||||||
/** 匹配超时计时器,用于检测 WS 消息丢失或真正超时 */
|
/** 匹配超时计时器,用于检测 WS 消息丢失或真正超时 */
|
||||||
const matchTimeoutTimer = ref(null);
|
const matchTimeoutTimer = ref(null);
|
||||||
@@ -58,6 +61,18 @@ async function stopMatch() {
|
|||||||
uni.$showHint(3);
|
uni.$showHint(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const closeLimitModal = () => {
|
||||||
|
showLimitModal.value = false;
|
||||||
|
uni.navigateBack();
|
||||||
|
};
|
||||||
|
|
||||||
|
const goVipPage = () => {
|
||||||
|
showLimitModal.value = false;
|
||||||
|
uni.redirectTo({
|
||||||
|
url: "/pages/member/be-vip",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 取消匹配,带容错处理:
|
* 取消匹配,带容错处理:
|
||||||
* - 取消成功 → 返回大厅
|
* - 取消成功 → 返回大厅
|
||||||
@@ -136,10 +151,19 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
onShow(async () => {
|
onShow(async () => {
|
||||||
if (gameType.value && teamSize.value) {
|
if (gameType.value && teamSize.value) {
|
||||||
matchGameAPI(true, gameType.value, teamSize.value);
|
try {
|
||||||
|
await matchGameAPI(true, gameType.value, teamSize.value);
|
||||||
// 启动超时计时器,防止 WS 消息丢失或长时间无对手导致用户卡死
|
// 启动超时计时器,防止 WS 消息丢失或长时间无对手导致用户卡死
|
||||||
clearMatchTimeout();
|
clearMatchTimeout();
|
||||||
matchTimeoutTimer.value = setTimeout(handleMatchTimeout, MATCH_TIMEOUT_MS);
|
matchTimeoutTimer.value = setTimeout(handleMatchTimeout, MATCH_TIMEOUT_MS);
|
||||||
|
} catch (error) {
|
||||||
|
clearMatchTimeout();
|
||||||
|
if (isLimitError(error)) {
|
||||||
|
showLimitModal.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uni.navigateBack();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -154,6 +178,14 @@ onHide(() => {
|
|||||||
<Matching :stopMatch="stopMatch" :onComplete="onComplete" />
|
<Matching :stopMatch="stopMatch" :onComplete="onComplete" />
|
||||||
</view>
|
</view>
|
||||||
</Container>
|
</Container>
|
||||||
|
<ModalDialog
|
||||||
|
:show="showLimitModal"
|
||||||
|
:content="'今日排位赛次数已经用完\n开通会员可增加次数'"
|
||||||
|
cancelText="知道了"
|
||||||
|
confirmText="去开通"
|
||||||
|
:onCancel="closeLimitModal"
|
||||||
|
:onConfirm="goVipPage"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -81,9 +81,17 @@ const loading = ref(false);
|
|||||||
const shareImage = async () => {
|
const shareImage = async () => {
|
||||||
if (loading.value) return;
|
if (loading.value) return;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
try {
|
||||||
await generateShareImage("shareImageCanvas", record.value);
|
await generateShareImage("shareImageCanvas", record.value);
|
||||||
await wxShare("shareImageCanvas");
|
await wxShare("shareImageCanvas");
|
||||||
|
} catch (e) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "海报生成失败,请稍后重试",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onLoad(async (options) => {
|
onLoad(async (options) => {
|
||||||
|
|||||||
@@ -15,11 +15,22 @@ const list = ref([]);
|
|||||||
const mine = ref({
|
const mine = ref({
|
||||||
averageRing: 0,
|
averageRing: 0,
|
||||||
});
|
});
|
||||||
|
const sharing = ref(false);
|
||||||
|
|
||||||
const shareImage = async () => {
|
const shareImage = async () => {
|
||||||
if (!mine.value.id) return;
|
if (!mine.value.id || sharing.value) return;
|
||||||
|
sharing.value = true;
|
||||||
|
try {
|
||||||
await sharePointData("shareCanvas", mine.value);
|
await sharePointData("shareCanvas", mine.value);
|
||||||
await wxShare("shareCanvas");
|
await wxShare("shareCanvas");
|
||||||
|
} catch (e) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "海报生成失败,请稍后重试",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
sharing.value = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ const practiseId = ref("");
|
|||||||
const showGuide = ref(false);
|
const showGuide = ref(false);
|
||||||
const tips = ref("");
|
const tips = ref("");
|
||||||
const targetType = ref(1);
|
const targetType = ref(1);
|
||||||
|
const sharing = ref(false);
|
||||||
|
|
||||||
onLoad((options) => {
|
onLoad((options) => {
|
||||||
if (options.target) {
|
if (options.target) {
|
||||||
@@ -112,8 +113,19 @@ async function onComplete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onClickShare = debounce(async () => {
|
const onClickShare = debounce(async () => {
|
||||||
|
if (sharing.value) return;
|
||||||
|
sharing.value = true;
|
||||||
|
try {
|
||||||
await sharePractiseData("shareCanvas", 2, user.value, practiseResult.value);
|
await sharePractiseData("shareCanvas", 2, user.value, practiseResult.value);
|
||||||
await wxShare("shareCanvas");
|
await wxShare("shareCanvas");
|
||||||
|
} catch (e) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "海报生成失败,请稍后重试",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
sharing.value = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function onAudioEnded(s) {
|
function onAudioEnded(s) {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const practiseResult = ref({});
|
|||||||
const practiseId = ref("");
|
const practiseId = ref("");
|
||||||
const showGuide = ref(false);
|
const showGuide = ref(false);
|
||||||
const targetType = ref(1);
|
const targetType = ref(1);
|
||||||
|
const sharing = ref(false);
|
||||||
|
|
||||||
onLoad((options) => {
|
onLoad((options) => {
|
||||||
if (options.target) {
|
if (options.target) {
|
||||||
@@ -127,8 +128,19 @@ async function onComplete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onClickShare = debounce(async () => {
|
const onClickShare = debounce(async () => {
|
||||||
|
if (sharing.value) return;
|
||||||
|
sharing.value = true;
|
||||||
|
try {
|
||||||
await sharePractiseData("shareCanvas", 3, user.value, practiseResult.value);
|
await sharePractiseData("shareCanvas", 3, user.value, practiseResult.value);
|
||||||
await wxShare("shareCanvas");
|
await wxShare("shareCanvas");
|
||||||
|
} catch (e) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "海报生成失败,请稍后重试",
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
sharing.value = false;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|||||||
@@ -3,21 +3,23 @@ import { computed, ref } from "vue";
|
|||||||
import { onShow } from "@dcloudio/uni-app";
|
import { onShow } from "@dcloudio/uni-app";
|
||||||
import Container from "@/components/Container.vue";
|
import Container from "@/components/Container.vue";
|
||||||
import Avatar from "@/components/Avatar.vue";
|
import Avatar from "@/components/Avatar.vue";
|
||||||
|
import ModalDialog from "@/components/ModalDialog.vue";
|
||||||
import { topThreeColors } from "@/constants";
|
import { topThreeColors } from "@/constants";
|
||||||
import {
|
import {
|
||||||
|
getDailyCountAPI,
|
||||||
getSeasonList,
|
getSeasonList,
|
||||||
getSeasonStats,
|
getSeasonStats,
|
||||||
getScoreRankList,
|
getScoreRankList,
|
||||||
getTenRingRankList,
|
getTenRingRankList,
|
||||||
getMvpRankList,
|
getMvpRankList,
|
||||||
} from "@/apis";
|
} from "@/apis";
|
||||||
import { canEenter } from "@/util";
|
import { canEenter, getLimitCountText, isLimitReached } from "@/util";
|
||||||
import useStore from "@/store";
|
import useStore from "@/store";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const { user, device, online, game } = storeToRefs(store);
|
const { user, device, online, game, dailyCount } = storeToRefs(store);
|
||||||
const { getLvlName } = store;
|
const { getLvlName, updateDailyCount } = store;
|
||||||
|
|
||||||
const defaultSeasonStats = {
|
const defaultSeasonStats = {
|
||||||
nickName: "",
|
nickName: "",
|
||||||
@@ -55,8 +57,12 @@ const rankLoading = ref(false);
|
|||||||
const scoreRankList = ref([]);
|
const scoreRankList = ref([]);
|
||||||
const mvpRankList = ref([]);
|
const mvpRankList = ref([]);
|
||||||
const tenRingRankList = ref([]);
|
const tenRingRankList = ref([]);
|
||||||
|
const showLimitModal = ref(false);
|
||||||
const isSVip = computed(() => user.value.sVip === true);
|
const isSVip = computed(() => user.value.sVip === true);
|
||||||
const isVip = computed(() => user.value.vip === true && !isSVip.value);
|
const isVip = computed(() => user.value.vip === true && !isSVip.value);
|
||||||
|
const rankedLimitText = computed(() =>
|
||||||
|
getLimitCountText("排位", dailyCount.value.ranked)
|
||||||
|
);
|
||||||
|
|
||||||
const isMember = (item = {}) => item.vip === true || item.sVip === true;
|
const isMember = (item = {}) => item.vip === true || item.sVip === true;
|
||||||
|
|
||||||
@@ -115,12 +121,40 @@ const toMatchPage = async (gameType, teamSize) => {
|
|||||||
uni.$showHint(1);
|
uni.$showHint(1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const countData = await loadDailyCount();
|
||||||
|
if (isLimitReached(countData.ranked)) {
|
||||||
|
showLimitModal.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
await uni.$checkAudio();
|
await uni.$checkAudio();
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/match-page?gameType=${gameType}&teamSize=${teamSize}`,
|
url: `/pages/match-page?gameType=${gameType}&teamSize=${teamSize}`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const closeLimitModal = () => {
|
||||||
|
showLimitModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const goVipPage = () => {
|
||||||
|
showLimitModal.value = false;
|
||||||
|
uni.navigateTo({
|
||||||
|
url: "/pages/member/be-vip",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadDailyCount = async () => {
|
||||||
|
if (!user.value.id) return dailyCount.value;
|
||||||
|
try {
|
||||||
|
const result = await getDailyCountAPI();
|
||||||
|
updateDailyCount(result);
|
||||||
|
return result || dailyCount.value;
|
||||||
|
} catch (error) {
|
||||||
|
console.log("load daily count error", error);
|
||||||
|
return dailyCount.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const toMyGrowthPage = () => {
|
const toMyGrowthPage = () => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: "/pages/my-growth",
|
url: "/pages/my-growth",
|
||||||
@@ -246,7 +280,10 @@ const onChangeSeason = async (seasonId, name) => {
|
|||||||
// 页面显示时先拿赛季列表,再拉当前赛季统计和默认榜单数据。
|
// 页面显示时先拿赛季列表,再拉当前赛季统计和默认榜单数据。
|
||||||
onShow(async () => {
|
onShow(async () => {
|
||||||
try {
|
try {
|
||||||
const seasonResult = await getSeasonList();
|
const [seasonResult] = await Promise.all([
|
||||||
|
getSeasonList(),
|
||||||
|
loadDailyCount(),
|
||||||
|
]);
|
||||||
seasonData.value = seasonResult.list || [];
|
seasonData.value = seasonResult.list || [];
|
||||||
|
|
||||||
if (!seasonData.value.length) {
|
if (!seasonData.value.length) {
|
||||||
@@ -283,12 +320,12 @@ onShow(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Container
|
<Container
|
||||||
:title="'排位赛'"
|
:title="rankedLimitText ? rankedLimitText : '排位赛'"
|
||||||
|
:titleStyle="rankedLimitText ? { fontSize: '24rpx', fontWeight: 'normal' } : {}"
|
||||||
:showBackToGame="true"
|
:showBackToGame="true"
|
||||||
:bgType="6"
|
:bgType="6"
|
||||||
>
|
>
|
||||||
<view class="battle-types-box">
|
<view class="battle-types-box">
|
||||||
<!-- :title="'今日排位次数:2/2'" :titleStyle="{ fontSize: '24rpx', fontWeight: 'normal' }" -->
|
|
||||||
<view class="battle-types">
|
<view class="battle-types">
|
||||||
<view class="first">
|
<view class="first">
|
||||||
<image src="../static/rank/battle-choose.png" mode="widthFix" />
|
<image src="../static/rank/battle-choose.png" mode="widthFix" />
|
||||||
@@ -585,6 +622,14 @@ onShow(async () => {
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</Container>
|
</Container>
|
||||||
|
<ModalDialog
|
||||||
|
:show="showLimitModal"
|
||||||
|
:content="'今日排位赛次数已经用完\n开通会员可增加次数'"
|
||||||
|
cancelText="知道了"
|
||||||
|
confirmText="去开通"
|
||||||
|
:onCancel="closeLimitModal"
|
||||||
|
:onConfirm="goVipPage"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
19
src/store.js
19
src/store.js
@@ -26,6 +26,17 @@ const getDefaultGame = () => ({
|
|||||||
tips: "",
|
tips: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getDefaultDailyCount = () => ({
|
||||||
|
challenge: {
|
||||||
|
count: 0,
|
||||||
|
limit: -1,
|
||||||
|
},
|
||||||
|
ranked: {
|
||||||
|
count: 0,
|
||||||
|
limit: -1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const getLvlName = (rankLvl, rankList = []) => {
|
const getLvlName = (rankLvl, rankList = []) => {
|
||||||
if (!rankList) return;
|
if (!rankList) return;
|
||||||
let lvlName = "";
|
let lvlName = "";
|
||||||
@@ -99,6 +110,7 @@ export default defineStore("store", {
|
|||||||
totalShot: 0, // 轮次总箭数(用于 HeaderProgress 恢复状态)
|
totalShot: 0, // 轮次总箭数(用于 HeaderProgress 恢复状态)
|
||||||
tips: "", // 当前提示文案(用于 HeaderProgress 恢复状态,替代 uni.$emit 避免时序问题)
|
tips: "", // 当前提示文案(用于 HeaderProgress 恢复状态,替代 uni.$emit 避免时序问题)
|
||||||
},
|
},
|
||||||
|
dailyCount: getDefaultDailyCount(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// 计算属性
|
// 计算属性
|
||||||
@@ -167,12 +179,19 @@ export default defineStore("store", {
|
|||||||
updateRoomNumber(number) {
|
updateRoomNumber(number) {
|
||||||
this.game.roomNumber = number;
|
this.game.roomNumber = number;
|
||||||
},
|
},
|
||||||
|
updateDailyCount(data = {}) {
|
||||||
|
this.dailyCount = {
|
||||||
|
...getDefaultDailyCount(),
|
||||||
|
...(data || {}),
|
||||||
|
};
|
||||||
|
},
|
||||||
clearSessionState() {
|
clearSessionState() {
|
||||||
this.$patch({
|
this.$patch({
|
||||||
user: getDefaultUser(),
|
user: getDefaultUser(),
|
||||||
device: getDefaultDevice(),
|
device: getDefaultDevice(),
|
||||||
online: false,
|
online: false,
|
||||||
game: getDefaultGame(),
|
game: getDefaultGame(),
|
||||||
|
dailyCount: getDefaultDailyCount(),
|
||||||
});
|
});
|
||||||
uni.removeStorageSync(PERSISTED_STORE_KEY);
|
uni.removeStorageSync(PERSISTED_STORE_KEY);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
68
src/util.js
68
src/util.js
@@ -11,11 +11,13 @@ export const debounce = (fn, delay = 300) => {
|
|||||||
let timer = null;
|
let timer = null;
|
||||||
return async (...args) => {
|
return async (...args) => {
|
||||||
if (timer) clearTimeout(timer);
|
if (timer) clearTimeout(timer);
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve, reject) => {
|
||||||
timer = setTimeout(async () => {
|
timer = setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
const result = await fn(...args);
|
const result = await fn(...args);
|
||||||
resolve(result);
|
resolve(result);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
} finally {
|
} finally {
|
||||||
timer = null;
|
timer = null;
|
||||||
}
|
}
|
||||||
@@ -24,6 +26,28 @@ export const debounce = (fn, delay = 300) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isUnlimitedLimit = (item = {}) => Number(item.limit) === -1;
|
||||||
|
|
||||||
|
export const getLimitCountText = (label, item = {}) => {
|
||||||
|
if (!item || isUnlimitedLimit(item)) return "";
|
||||||
|
const count = Number(item.count || 0);
|
||||||
|
const limit = Number(item.limit || 0);
|
||||||
|
return `今日${label}次数:${count}/${limit}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isLimitReached = (item = {}) => {
|
||||||
|
if (!item || isUnlimitedLimit(item)) return false;
|
||||||
|
return Number(item.count || 0) >= Number(item.limit || 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isLimitError = (error) => {
|
||||||
|
const message = typeof error === "string" ? error : error?.message;
|
||||||
|
return String(message || "").includes("已达上限");
|
||||||
|
};
|
||||||
|
|
||||||
|
const isShareCancel = (error) =>
|
||||||
|
String(error?.errMsg || error || "").toLowerCase().includes("cancel");
|
||||||
|
|
||||||
export const wxShare = async (canvasId = "shareCanvas") => {
|
export const wxShare = async (canvasId = "shareCanvas") => {
|
||||||
try {
|
try {
|
||||||
// 先尝试按 id 查找 <canvas type="2d"> 节点
|
// 先尝试按 id 查找 <canvas type="2d"> 节点
|
||||||
@@ -54,35 +78,63 @@ export const wxShare = async (canvasId = "shareCanvas") => {
|
|||||||
quality: 1,
|
quality: 1,
|
||||||
success: (r) => {
|
success: (r) => {
|
||||||
const p = r.tempFilePath || r.apFilePath || r.filePath;
|
const p = r.tempFilePath || r.apFilePath || r.filePath;
|
||||||
|
if (!p) {
|
||||||
|
reject(new Error("share image path is empty"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
resolve(p);
|
resolve(p);
|
||||||
},
|
},
|
||||||
fail: reject,
|
fail: reject,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
wx.showShareImageMenu({
|
wx.showShareImageMenu({
|
||||||
entrancePath: "pages/index",
|
entrancePath: "pages/index",
|
||||||
path: tempPath,
|
path: tempPath,
|
||||||
|
success: resolve,
|
||||||
|
fail: (error) => {
|
||||||
|
if (isShareCancel(error)) {
|
||||||
|
resolve({ canceled: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reject(error);
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
return tempPath;
|
return tempPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 回退:旧版非 2D 画布(通过 canvasId 导出)
|
// 回退:旧版非 2D 画布(通过 canvasId 导出)
|
||||||
const res = await uni.canvasToTempFilePath({
|
const res = await new Promise((resolve, reject) => {
|
||||||
|
uni.canvasToTempFilePath({
|
||||||
canvasId,
|
canvasId,
|
||||||
fileType: "png",
|
fileType: "png",
|
||||||
quality: 1,
|
quality: 1,
|
||||||
|
success: resolve,
|
||||||
|
fail: reject,
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
const tempPath = res.tempFilePath || res.apFilePath || res.filePath;
|
||||||
|
if (!tempPath) {
|
||||||
|
throw new Error("share image path is empty");
|
||||||
|
}
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
wx.showShareImageMenu({
|
wx.showShareImageMenu({
|
||||||
entrancePath: "pages/index",
|
entrancePath: "pages/index",
|
||||||
path: res.tempFilePath,
|
path: tempPath,
|
||||||
|
success: resolve,
|
||||||
|
fail: (error) => {
|
||||||
|
if (isShareCancel(error)) {
|
||||||
|
resolve({ canceled: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reject(error);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return res.tempFilePath;
|
});
|
||||||
|
return tempPath;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("生成图片失败:", error);
|
console.log("生成图片失败:", error);
|
||||||
uni.showToast({
|
|
||||||
title: "生成图片失败",
|
|
||||||
icon: "error",
|
|
||||||
});
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user