import { getUserGameState, getGameAPI } from "@/apis"; export const formatTimestamp = (timestamp) => { const date = new Date(timestamp * 1000); const year = date.getFullYear(); const month = date.getMonth() + 1; const day = date.getDate(); return `${year}-${month}-${day}`; }; export const debounce = (fn, delay = 300) => { let timer = null; return async (...args) => { if (timer) clearTimeout(timer); return new Promise((resolve) => { timer = setTimeout(async () => { try { const result = await fn(...args); resolve(result); } finally { timer = null; } }, delay); }); }; }; export const wxShare = async (canvasId = "shareCanvas") => { try { // 先尝试按 id 查找 节点 const { pixelRatio: dpr = 1 } = uni.getSystemInfoSync() || {}; const info = await new Promise((resolve) => { const query = uni.createSelectorQuery(); query .select(`#${canvasId}`) .fields({ node: true, size: true }) .exec((res) => resolve(res && res[0])); }); const canvas = info?.node; const cssWidth = info?.width; const cssHeight = info?.height; if (canvas && typeof canvas.getContext === "function") { // 2D 画布导出:传入 canvas 节点 const tempPath = await new Promise((resolve, reject) => { uni.canvasToTempFilePath({ canvas, width: cssWidth, height: cssHeight, // 按 DPR 导出更清晰的图片 destWidth: Math.round(cssWidth * dpr), destHeight: Math.round(cssHeight * dpr), fileType: "png", quality: 1, success: (r) => { const p = r.tempFilePath || r.apFilePath || r.filePath; resolve(p); }, fail: reject, }); }); wx.showShareImageMenu({ entrancePath: "pages/index", path: tempPath, }); return tempPath; } // 回退:旧版非 2D 画布(通过 canvasId 导出) const res = await uni.canvasToTempFilePath({ canvasId, fileType: "png", quality: 1, }); wx.showShareImageMenu({ entrancePath: "pages/index", path: res.tempFilePath, }); return res.tempFilePath; } catch (error) { console.log("生成图片失败:", error); uni.showToast({ title: "生成图片失败", icon: "error", }); throw error; } }; export const isGameEnded = async (battleId) => { const state = await getUserGameState(); if (!state.gaming) { const result = await getGameAPI(battleId); if (result.mode) { uni.redirectTo({ url: `/pages/battle-result?battleId=${battleId}`, }); } else { uni.showToast({ title: "比赛已结束", icon: "none", }); setTimeout(() => { uni.navigateBack(); }, 1000); } } return !state.gaming; }; // 获取元素尺寸和位置信息 export const getElementRect = (classname) => { return new Promise((resolve) => { const query = uni.createSelectorQuery(); query .select(classname) .boundingClientRect((rect) => { resolve(rect); }) .exec(); }); }; const calcNormalBowTarget = (x, y, diameter, arrowRadius) => { // 将弓箭左上角坐标转换为圆心坐标 const arrowCenterX = x + arrowRadius; const arrowCenterY = y + arrowRadius; // 计算靶心坐标(靶纸中心) const centerX = (diameter + arrowRadius * 2) / 2; const centerY = (diameter + arrowRadius * 2) / 2; // 计算弓箭圆心到靶心的距离 const deltaX = arrowCenterX - centerX; const deltaY = arrowCenterY - centerY; const distanceToCenter = Math.sqrt(deltaX * deltaX + deltaY * deltaY); // 计算弓箭边缘到靶心的最近距离 const distance = Math.max(0, distanceToCenter - arrowRadius); // 计算靶纸半径(取宽高中较小值的一半) const targetRadius = diameter / 2; // 计算相对距离(0-1之间) let relativeDistance = distance / targetRadius; // 全环靶有10个环,每个环占半径的10% // 从外到内:1环到10环 // 距离越近靶心,环数越高 if (relativeDistance <= 0.05) return "X"; if (relativeDistance <= 0.1) return 10; if (relativeDistance <= 0.2) return 9; if (relativeDistance <= 0.3) return 8; if (relativeDistance <= 0.4) return 7; if (relativeDistance <= 0.5) return 6; if (relativeDistance <= 0.6) return 5; if (relativeDistance <= 0.7) return 4; if (relativeDistance <= 0.8) return 3; if (relativeDistance <= 0.9) return 2; if (relativeDistance <= 1) return 1; return 0; // 脱靶 }; const calcHalfBowTarget = (x, y, diameter, arrowRadius, noX = false) => { // 将弓箭左上角坐标转换为圆心坐标 const arrowCenterX = x + arrowRadius; const arrowCenterY = y + arrowRadius; // 计算靶心坐标(靶纸中心) const centerX = (diameter + arrowRadius * 2) / 2; const centerY = (diameter + arrowRadius * 2) / 2; // 计算弓箭圆心到靶心的距离 const deltaX = arrowCenterX - centerX; const deltaY = arrowCenterY - centerY; const distanceToCenter = Math.sqrt(deltaX * deltaX + deltaY * deltaY); // 计算弓箭边缘到靶心的最近距离 const distance = Math.max(0, distanceToCenter - arrowRadius); // 计算靶纸半径(取宽高中较小值的一半) const targetRadius = diameter / 2; // 计算相对距离(0-1之间) let relativeDistance = distance / targetRadius; if (relativeDistance <= 0.1) return noX ? 10 : "X"; if (relativeDistance <= 0.2) return noX ? 9 : 10; if (relativeDistance <= 0.4) return 9; if (relativeDistance <= 0.6) return 8; if (relativeDistance <= 0.8) return 7; if (relativeDistance <= 1) return 6; return 0; // 脱靶 }; export const calcTripleBowTarget = ( x, y, diameter, arrowRadius, noX = false ) => { const side = diameter * 0.325; if (y / diameter >= 0.661) { return calcHalfBowTarget( x - diameter * 0.336, y - diameter * 0.675, side, arrowRadius, noX ); } if (y / diameter >= 0.323) { return calcHalfBowTarget( x - diameter * 0.336, y - diameter * 0.337, side, arrowRadius, noX ); } if (y / diameter >= -0.026) { return calcHalfBowTarget( x - diameter * 0.336, y - diameter * 0.0, side, arrowRadius, noX ); } return 0; }; export const calcPinBowTarget = (x, y, diameter, arrowRadius, noX = false) => { const side = diameter * 0.482; let r1 = 0; let r2 = 0; let r3 = 0; if (x / diameter >= 0.23 && y / diameter >= 0.005) { r1 = calcHalfBowTarget( x - diameter * 0.26, y - diameter * 0.034, side, arrowRadius, noX ); } if (x / diameter >= -0.03 && y / diameter >= 0.456) { r2 = calcHalfBowTarget(x, y - diameter * 0.484, side, arrowRadius, noX); } if (x / diameter >= 0.49 && y / diameter >= 0.456) { r3 = calcHalfBowTarget( x - diameter * 0.52, y - diameter * 0.485, side, arrowRadius, noX ); } return r1 || r2 || r3; }; export const calcRing = (bowtargetId, x, y, diameter, arrowRadius = 5) => { if (bowtargetId < 4) { return calcNormalBowTarget(x, y, diameter, arrowRadius); } else if (bowtargetId < 7) { return calcHalfBowTarget(x, y, diameter, arrowRadius); } else if (bowtargetId === 7) { return calcTripleBowTarget(x, y, diameter, arrowRadius); } else if (bowtargetId === 8) { return calcPinBowTarget(x, y, diameter, arrowRadius); } else if (bowtargetId === 9) { return calcTripleBowTarget(x, y, diameter, arrowRadius, true); } else if (bowtargetId === 10) { return calcPinBowTarget(x, y, diameter, arrowRadius, true); } return 0; }; export const getDirectionText = (angle = 0) => { if (angle < 0) return ""; if (angle >= 337.5 || angle < 22.5) { return "下"; } else if (angle >= 22.5 && angle < 67.5) { return "左下"; } else if (angle >= 67.5 && angle < 112.5) { return "左"; } else if (angle >= 112.5 && angle < 157.5) { return "左上"; } else if (angle >= 157.5 && angle < 202.5) { return "上"; } else if (angle >= 202.5 && angle < 247.5) { return "右上"; } else if (angle >= 247.5 && angle < 292.5) { return "右"; } else if (angle >= 292.5 && angle < 337.5) { return "右下"; } }; export const wxLogin = () => { return new Promise((resolve, reject) => { uni.login({ provider: "weixin", success: resolve, fail: (err) => { uni.showToast({ title: "登录失败", icon: "none", }); console.error("登录失败:", err); reject(err); }, }); }); }; export const canEenter = (user, device, online, page = "") => { const { miniProgram } = uni.getAccountInfoSync(); if (!device.deviceId) { uni.showToast({ title: "请先绑定设备", icon: "none", }); return false; } if (miniProgram.envVersion !== "release") return true; if (!online) { uni.showToast({ title: "智能弓未连接", icon: "none", }); return false; } if (!user.trio && page !== "/pages/first-try") { uni.showToast({ title: "请先完成新手试炼", icon: "none", }); return false; } return true; }; export const capsuleHeight = uni.getMenuButtonBoundingClientRect().top - 9;