874 lines
27 KiB
TypeScript
874 lines
27 KiB
TypeScript
import { useState, useEffect, useRef, useId, useMemo } from "react";
|
||
import { View, Text, Image, Button, Canvas } from "@tarojs/components";
|
||
import Taro, { useRouter, useShareAppMessage } from "@tarojs/taro";
|
||
import dayjs from "dayjs";
|
||
import classnames from "classnames";
|
||
import { withAuth, RadarChart, RadarChartV2 } from "@/components";
|
||
import evaluateService, {
|
||
LastTimeTestResult,
|
||
Question,
|
||
TestResultData,
|
||
StageType,
|
||
} from "@/services/evaluateService";
|
||
import { useUserInfo, useUserActions } from "@/store/userStore";
|
||
import { useEvaluate, EvaluateScene } from "@/store/evaluateStore";
|
||
import { useGlobalState } from "@/store/global";
|
||
import { delay, getCurrentFullPath } from "@/utils";
|
||
import { formatNtrpDisplay } from "@/utils/helper";
|
||
import { waitForAuthInit } from "@/utils/authInit";
|
||
// import httpService from "@/services/httpService";
|
||
import DetailService from "@/services/detailService";
|
||
import { OSS_BASE } from "@/config/api";
|
||
import CloseIcon from "@/static/ntrp/ntrp_close_icon.svg";
|
||
import DocCopy from "@/static/ntrp/ntrp_doc_copy.svg";
|
||
import ArrowRight from "@/static/ntrp/ntrp_arrow_right.svg";
|
||
import ArrowBack from "@/static/ntrp/ntrp_arrow_back.svg";
|
||
import CircleChecked from "@/static/ntrp/ntrp_circle_checked.svg";
|
||
import CircleUnChecked from "@/static/ntrp/ntrp_circle_unchecked.svg";
|
||
import WechatIcon from "@/static/ntrp/ntrp_wechat.svg";
|
||
import DownloadIcon from "@/static/ntrp/ntrp_download.svg";
|
||
import ReTestIcon from "@/static/ntrp/ntrp_re-action.svg";
|
||
import styles from "./index.module.scss";
|
||
|
||
const sourceTypeToTextMap = new Map([
|
||
[EvaluateScene.detail, "继续加入球局"],
|
||
[EvaluateScene.publish, "继续发布球局"],
|
||
]);
|
||
|
||
function adjustRadarLabels(
|
||
source: [string, number][],
|
||
topK: number = 4, // 默认挑前4个最长的标签保护
|
||
): [string, number][] {
|
||
if (source.length === 0) return source;
|
||
|
||
// 复制并按长度排序(降序)
|
||
let sorted = [...source].sort((a, b) => b[0].length - a[0].length);
|
||
|
||
// 取出前 K 个最长标签
|
||
let protectedLabels = sorted.slice(0, topK);
|
||
// 其他标签(保持原始顺序,但排除掉 protected)
|
||
let protectedSet = new Set(protectedLabels.map(([l]) => l));
|
||
let others = source.filter(([l]) => !protectedSet.has(l));
|
||
|
||
let n = source.length;
|
||
let result: ([string, number] | undefined)[] = new Array(n);
|
||
|
||
// 放首尾
|
||
result[0] = protectedLabels.shift() || others.shift();
|
||
result[n - 1] = protectedLabels.shift() || others.shift();
|
||
|
||
// 放中间(支持偶数两个位置)
|
||
if (n % 2 === 0) {
|
||
let mid1 = n / 2 - 1;
|
||
let mid2 = n / 2;
|
||
result[mid1] = protectedLabels.shift() || others.shift();
|
||
result[mid2] = protectedLabels.shift() || others.shift();
|
||
} else {
|
||
let mid = Math.floor(n / 2);
|
||
result[mid] = protectedLabels.shift() || others.shift();
|
||
}
|
||
|
||
// 把剩余标签按顺序塞进空位
|
||
let pool = [...protectedLabels, ...others];
|
||
for (let i = 0; i < n; i++) {
|
||
if (!result[i]) result[i] = pool.shift();
|
||
}
|
||
|
||
return result as [string, number][];
|
||
}
|
||
|
||
function isOnCancelEmpty(onCancelFunc) {
|
||
if (typeof onCancelFunc !== "function") {
|
||
console.log("onCancel 不是函数");
|
||
return false;
|
||
}
|
||
|
||
try {
|
||
const funcString = onCancelFunc.toString().trim();
|
||
|
||
// 常见空函数模式
|
||
const emptyFunctionPatterns = [
|
||
"functiononCancel(){}",
|
||
"function onCancel() {}",
|
||
"onCancel(){}",
|
||
"()=>{}",
|
||
"functiononCancel(){ }",
|
||
"() => {}",
|
||
];
|
||
|
||
const normalized = funcString.replace(/\s/g, "");
|
||
return emptyFunctionPatterns.includes(normalized);
|
||
} catch (error) {
|
||
console.error("检查 onCancel 函数时出错:", error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function CommonGuideBar(props) {
|
||
const { title, confirm } = props;
|
||
const { onCancel } = useEvaluate();
|
||
const { statusNavbarHeightInfo } = useGlobalState();
|
||
const { statusBarHeight, navBarHeight } = statusNavbarHeightInfo;
|
||
// const userInfo = useUserInfo()
|
||
|
||
// 判断是否从分享链接进入:页面栈只有1个页面时,说明是从分享直接进入的
|
||
const isFromShare = () => {
|
||
try {
|
||
const pages = (Taro as any).getCurrentPages();
|
||
return pages && pages.length === 1;
|
||
} catch {
|
||
return false;
|
||
}
|
||
};
|
||
|
||
function handleClose() {
|
||
// 如果是从分享链接进入,直接跳转首页
|
||
if (isFromShare()) {
|
||
(Taro as any).redirectTo({ url: "/main_pages/index" });
|
||
return;
|
||
}
|
||
|
||
// 否则执行原来的逻辑(跳转球局或执行 onCancel)
|
||
//TODO: 二次确认
|
||
if (confirm) {
|
||
}
|
||
try {
|
||
console.log(onCancel, isOnCancelEmpty(onCancel));
|
||
if (isOnCancelEmpty(onCancel)) {
|
||
(Taro as any).redirectTo({ url: "/main_pages/index" });
|
||
}
|
||
onCancel();
|
||
} catch {
|
||
(Taro as any).redirectTo({ url: "/main_pages/index" });
|
||
}
|
||
}
|
||
|
||
return (
|
||
<View
|
||
className={styles.header}
|
||
style={{
|
||
height: navBarHeight + "px",
|
||
paddingTop: statusBarHeight + "px",
|
||
}}
|
||
>
|
||
<View className={styles.closeIcon} onClick={handleClose}>
|
||
<Image className={styles.closeImg} src={CloseIcon} />
|
||
</View>
|
||
<View className={styles.title}>
|
||
<Text>{title}</Text>
|
||
</View>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
function Intro() {
|
||
const [ntrpData, setNtrpData] = useState<LastTimeTestResult>();
|
||
const userInfo = useUserInfo();
|
||
const { fetchUserInfo } = useUserActions();
|
||
const [ready, setReady] = useState(false);
|
||
const { setCallback } = useEvaluate();
|
||
|
||
const { last_test_result = null } = ntrpData || {};
|
||
const { ntrp_level, create_time, id, level_description } =
|
||
last_test_result || {};
|
||
const lastTestTime = create_time
|
||
? dayjs(create_time).format("YYYY年M月D日")
|
||
: "";
|
||
|
||
useEffect(() => {
|
||
const init = async () => {
|
||
// 先等待静默登录完成
|
||
await waitForAuthInit();
|
||
// 然后再调用接口
|
||
await getLastResult();
|
||
};
|
||
init();
|
||
}, []);
|
||
|
||
async function getLastResult() {
|
||
const res = await evaluateService.getLastResult();
|
||
if (res.code === 0) {
|
||
setNtrpData(res.data);
|
||
if (res.data.has_ntrp_level) {
|
||
// 确保用户信息已加载
|
||
if (!userInfo || Object.keys(userInfo).length === 0) {
|
||
await fetchUserInfo();
|
||
}
|
||
}
|
||
setReady(true);
|
||
}
|
||
}
|
||
if (!ready) {
|
||
return "";
|
||
}
|
||
|
||
function handleNext(type) {
|
||
if (!id) {
|
||
setCallback({
|
||
type: EvaluateScene.share,
|
||
next: () => {
|
||
Taro.redirectTo({ url: "/main_pages/index" });
|
||
},
|
||
onCancel: () => {
|
||
Taro.redirectTo({ url: "/main_pages/index" });
|
||
},
|
||
});
|
||
}
|
||
Taro.redirectTo({
|
||
url: `/other_pages/ntrp-evaluate/index?stage=${type}${
|
||
type === StageType.RESULT ? `&id=${id}` : ""
|
||
}`,
|
||
});
|
||
}
|
||
|
||
return (
|
||
<View
|
||
className={styles.introContainer}
|
||
style={{
|
||
backgroundImage: `url(${OSS_BASE}/front/ball/images/215f1ce1-be52-4a92-8250-5a4a69e7f2b3.png)`,
|
||
}}
|
||
>
|
||
<CommonGuideBar />
|
||
{ntrpData?.has_test_record ? (
|
||
<View className={styles.result}>
|
||
<View className={styles.avatarWrap}>
|
||
<View className={styles.avatar}>
|
||
<Image
|
||
className={styles.avatarUrl}
|
||
src={userInfo?.avatar_url || ""}
|
||
mode="aspectFill"
|
||
/>
|
||
</View>
|
||
{/* avatar side */}
|
||
<View className={styles.addonImage}>
|
||
<Image
|
||
className={styles.docImage}
|
||
src={DocCopy}
|
||
mode="aspectFill"
|
||
/>
|
||
</View>
|
||
</View>
|
||
{/* tip */}
|
||
<View className={styles.tip}>
|
||
<Image
|
||
className={styles.tipImage}
|
||
src={`${OSS_BASE}/front/ball/images/b7cb47aa-b609-4112-899f-3fde02ed2431.png`}
|
||
mode="aspectFit"
|
||
/>
|
||
</View>
|
||
<View
|
||
className={styles.lastResult}
|
||
onClick={() => handleNext(StageType.RESULT)}
|
||
>
|
||
<View className={styles.tipAndTime}>
|
||
<Text>上次测试结果</Text>
|
||
<Text>{lastTestTime}</Text>
|
||
</View>
|
||
<View className={styles.levelWrap}>
|
||
<Text>NTRP</Text>
|
||
<Text className={styles.level}>
|
||
{formatNtrpDisplay(ntrp_level || "")}
|
||
</Text>
|
||
</View>
|
||
<View className={styles.slogan}>
|
||
<Text>{level_description}</Text>
|
||
</View>
|
||
</View>
|
||
<View className={styles.actions}>
|
||
<View className={classnames(styles.buttonWrap, styles.customBtn)}>
|
||
<Button
|
||
className={classnames(styles.button, styles.primary)}
|
||
type="primary"
|
||
onClick={() => handleNext(StageType.TEST)}
|
||
>
|
||
<Text>再次测试</Text>
|
||
<Image className={styles.arrowImage} src={ArrowRight} />
|
||
</Button>
|
||
<View
|
||
className={classnames(styles.customBtnCover, styles.primary)}
|
||
>
|
||
<Text>再次测试</Text>
|
||
<Image className={styles.arrowImage} src={ArrowRight} />
|
||
</View>
|
||
</View>
|
||
<View className={classnames(styles.buttonWrap, styles.customBtn)}>
|
||
<Button
|
||
className={styles.button}
|
||
onClick={() => handleNext(StageType.RESULT)}
|
||
>
|
||
<Text>继续使用上次测试结果</Text>
|
||
</Button>
|
||
<View className={styles.customBtnCover}>
|
||
<Text>继续使用上次测试结果</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
) : (
|
||
<View className={styles.guide}>
|
||
{/* tip */}
|
||
<View className={styles.tip}>
|
||
<Image
|
||
className={styles.tipImage}
|
||
src={`${OSS_BASE}/front/ball/images/b7cb47aa-b609-4112-899f-3fde02ed2431.png`}
|
||
mode="aspectFit"
|
||
/>
|
||
</View>
|
||
{/* radar */}
|
||
<View className={styles.radar}>
|
||
<Image
|
||
className={styles.radarImage}
|
||
src={`${OSS_BASE}/front/ball/images/a2e1b639-82a9-4ab8-b767-8605556eafcb.png`}
|
||
mode="aspectFit"
|
||
/>
|
||
</View>
|
||
<View className={styles.desc}>
|
||
<Text>
|
||
NTRP(National Tennis Rating
|
||
Program)是一种常用的网球水平分级系统,这不是绝对精准的“分数”,而是一个参考标准,能够帮助你更清晰地了解自己的网球水平,从而在训练、比赛或娱乐活动中找到「难度合适」的球友,避免过度碾压或被碾压。
|
||
</Text>
|
||
</View>
|
||
<View className={styles.actions}>
|
||
<View className={styles.buttonWrap}>
|
||
<Button
|
||
className={classnames(styles.button, styles.primary)}
|
||
type="primary"
|
||
onClick={() => handleNext(StageType.TEST)}
|
||
>
|
||
<Text>开始测试</Text>
|
||
<Image className={styles.arrowImage} src={ArrowRight} />
|
||
</Button>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
)}
|
||
</View>
|
||
);
|
||
}
|
||
|
||
function Test() {
|
||
const [disabled, setDisabled] = useState(false);
|
||
const [index, setIndex] = useState(0);
|
||
const [questions, setQuestions] = useState<
|
||
(Question & { choosen: number })[]
|
||
>([]);
|
||
const startTimeRef = useRef<number>(0);
|
||
|
||
useEffect(() => {
|
||
startTimeRef.current = Date.now();
|
||
getQUestions();
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
setDisabled(questions[index]?.choosen === -1);
|
||
}, [index, questions]);
|
||
|
||
const doneFlag = useMemo(() => {
|
||
const [q1, q2, q3] = questions;
|
||
return [q1, q2, q3].every((q) => q?.choosen === 0);
|
||
}, [questions]);
|
||
|
||
async function getQUestions() {
|
||
const res = await evaluateService.getQuestions();
|
||
if (res.code === 0) {
|
||
setQuestions(res.data.map((item) => ({ ...item, choosen: -1 })));
|
||
}
|
||
}
|
||
|
||
function handleSelect(i) {
|
||
setQuestions((prev) =>
|
||
prev.map((item, pIndex) => ({
|
||
...item,
|
||
...(pIndex === index ? { choosen: i } : {}),
|
||
})),
|
||
);
|
||
}
|
||
|
||
async function handleSubmit() {
|
||
setDisabled(true);
|
||
try {
|
||
const res = await evaluateService.submit({
|
||
answers: questions
|
||
.filter((item) => item.choosen >= 0)
|
||
.map((item) => ({
|
||
question_id: item.id,
|
||
answer_index: item.choosen,
|
||
})),
|
||
test_duration: (Date.now() - startTimeRef.current) / 1000,
|
||
});
|
||
if (res.code === 0) {
|
||
Taro.redirectTo({
|
||
url: `/other_pages/ntrp-evaluate/index?stage=${StageType.RESULT}&id=${res.data.record_id}`,
|
||
});
|
||
}
|
||
} catch (e) {
|
||
Taro.showToast({ title: e.message, icon: "error" });
|
||
} finally {
|
||
setDisabled(false);
|
||
}
|
||
}
|
||
|
||
function handIndexChange(direction) {
|
||
if (disabled && direction > 0) {
|
||
return;
|
||
}
|
||
if ((index === questions.length - 1 || doneFlag) && direction > 0) {
|
||
handleSubmit();
|
||
return;
|
||
}
|
||
setIndex((prev) => prev + direction);
|
||
}
|
||
|
||
const question = questions[index];
|
||
if (!question) {
|
||
return "";
|
||
}
|
||
return (
|
||
<View
|
||
className={styles.testContainer}
|
||
style={{
|
||
backgroundImage: `url(${OSS_BASE}/front/ball/images/215f1ce1-be52-4a92-8250-5a4a69e7f2b3.png)`,
|
||
}}
|
||
>
|
||
<CommonGuideBar confirm title={`${index + 1} / ${questions.length}`} />
|
||
<View className={styles.bar}>
|
||
<View
|
||
className={styles.progressBar}
|
||
style={{ width: `${100 * ((index + 1) / questions.length)}%` }}
|
||
/>
|
||
</View>
|
||
<View className={styles.notice}>
|
||
<Text>根据近3个月实际表现勾选最符合项</Text>
|
||
</View>
|
||
<View className={styles.question}>
|
||
<View className={styles.content}>{question.question_content}</View>
|
||
<View className={styles.options}>
|
||
{question.options.map((item, i) => {
|
||
const checked = question.choosen === i;
|
||
return (
|
||
<View
|
||
key={i}
|
||
className={styles.optionItem}
|
||
onClick={() => handleSelect(i)}
|
||
>
|
||
<View className={styles.optionText}>{item.text}</View>
|
||
<View className={styles.optionIcon}>
|
||
<Image
|
||
className={styles.icon}
|
||
src={checked ? CircleChecked : CircleUnChecked}
|
||
/>
|
||
</View>
|
||
</View>
|
||
);
|
||
})}
|
||
</View>
|
||
</View>
|
||
<View className={styles.actions}>
|
||
<View
|
||
className={classnames(styles.next, disabled ? styles.disabled : "")}
|
||
onClick={() => handIndexChange(1)}
|
||
>
|
||
<Button className={styles.nextBtn} type="primary">
|
||
{index === questions.length - 1 || doneFlag ? "完成测试" : "继续"}
|
||
</Button>
|
||
</View>
|
||
{index !== 0 && (
|
||
<View className={styles.prev} onClick={() => handIndexChange(-1)}>
|
||
<Image className={styles.backIcon} src={ArrowBack} />
|
||
<Text>返回</Text>
|
||
</View>
|
||
)}
|
||
</View>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
function Result() {
|
||
const { params } = useRouter();
|
||
const { id } = params;
|
||
const userInfo = useUserInfo();
|
||
const { fetchUserInfo, updateUserInfo } = useUserActions();
|
||
const { type, next, clear } = useEvaluate();
|
||
const radarRef = useRef<any>();
|
||
const radarV2Ref = useRef<any>();
|
||
|
||
const [result, setResult] = useState<TestResultData>();
|
||
const [radarData, setRadarData] = useState<
|
||
[propName: string, prop: number][]
|
||
>([]);
|
||
const [qrCodeUrl, setQrCodeUrl] = useState<string>("");
|
||
|
||
useEffect(() => {
|
||
const init = async () => {
|
||
// 先等待静默登录完成
|
||
await waitForAuthInit();
|
||
// 然后再调用接口
|
||
await getResultById();
|
||
// 确保用户信息已加载
|
||
if (!userInfo || Object.keys(userInfo).length === 0) {
|
||
await fetchUserInfo();
|
||
}
|
||
// 获取二维码
|
||
await fetchQRCode();
|
||
};
|
||
init();
|
||
}, [id]);
|
||
|
||
// 获取二维码 - 调用接口生成分享二维码
|
||
async function fetchQRCode() {
|
||
try {
|
||
// 调用接口生成二维码,分享当前页面
|
||
const qrCodeUrlRes = await DetailService.getQrCodeUrl({
|
||
page: "other_pages/ntrp-evaluate/index",
|
||
scene: `stage=${StageType.INTRO}`,
|
||
});
|
||
setQrCodeUrl(qrCodeUrlRes.data.ossPath);
|
||
// if (qrCodeUrlRes.code === 0 && qrCodeUrlRes.data?.qr_code_base64) {
|
||
// // 将 base64 转换为临时文件路径
|
||
// const tempFilePath = await base64ToTempFilePath(
|
||
// qrCodeUrlRes.data.qr_code_base64
|
||
// );
|
||
// setQrCodeUrl(tempFilePath);
|
||
// }
|
||
} catch (error) {
|
||
console.error("获取二维码失败:", error);
|
||
}
|
||
}
|
||
|
||
async function getResultById() {
|
||
const res = await evaluateService.getTestResult({ record_id: Number(id) });
|
||
if (res.code === 0) {
|
||
setResult(res.data);
|
||
|
||
const sortOrder = res.data.sort || [];
|
||
const abilities = res.data.radar_data.abilities;
|
||
const sortedKeys = sortOrder.filter((k) => k in abilities);
|
||
const remainingKeys = Object.keys(abilities).filter(
|
||
(k) => !sortOrder.includes(k),
|
||
);
|
||
const allKeys = [...sortedKeys, ...remainingKeys];
|
||
let radarData: [string, number][] = allKeys.map((key) => [
|
||
key,
|
||
Math.min(
|
||
100,
|
||
Math.floor(
|
||
(abilities[key].current_score / abilities[key].max_score) * 100,
|
||
),
|
||
),
|
||
]);
|
||
// 直接使用接口 sort 顺序,不经过 adjustRadarLabels 重新排序
|
||
setRadarData(radarData);
|
||
updateUserLevel(res.data.record_id, res.data.ntrp_level);
|
||
}
|
||
}
|
||
|
||
function updateUserLevel(record_id, ntrp_level) {
|
||
try {
|
||
evaluateService.updateNtrp({
|
||
record_id,
|
||
ntrp_level,
|
||
update_type: "test_result",
|
||
});
|
||
updateUserInfo({ ntrp_level: ntrp_level });
|
||
} catch (e) {
|
||
Taro.showToast({ title: e.message, icon: "none" });
|
||
}
|
||
}
|
||
|
||
function handleReTest() {
|
||
if (!userInfo.phone) {
|
||
handleAuth();
|
||
return false;
|
||
}
|
||
Taro.redirectTo({
|
||
url: `/other_pages/ntrp-evaluate/index?stage=${StageType.TEST}`,
|
||
});
|
||
}
|
||
|
||
function handleViewGames() {
|
||
Taro.redirectTo({
|
||
url: "/main_pages/index",
|
||
});
|
||
}
|
||
|
||
async function handleGoon() {
|
||
if (!userInfo?.phone) {
|
||
Taro.redirectTo({
|
||
url: `/login_pages/index/index?redirect=${encodeURIComponent(
|
||
`/main_pages/index`,
|
||
)}`,
|
||
});
|
||
clear();
|
||
return;
|
||
}
|
||
if (type) {
|
||
next({ flag: false, score: result?.ntrp_level as string });
|
||
await delay(1500);
|
||
clear();
|
||
} else {
|
||
handleViewGames();
|
||
}
|
||
}
|
||
|
||
async function genCardImage() {
|
||
try {
|
||
// 确保二维码已获取,如果没有则重新获取
|
||
let finalQrCodeUrl = qrCodeUrl;
|
||
if (!finalQrCodeUrl) {
|
||
// 直接调用接口获取二维码
|
||
const qrCodeUrlRes = await DetailService.getQrCodeUrl({
|
||
page: "other_pages/ntrp-evaluate/index",
|
||
scene: `stage=${StageType.INTRO}`,
|
||
});
|
||
finalQrCodeUrl = qrCodeUrlRes.data.ossPath;
|
||
// if (qrCodeUrlRes.code === 0 && qrCodeUrlRes.data?.qr_code_base64) {
|
||
// finalQrCodeUrl = await base64ToTempFilePath(
|
||
// qrCodeUrlRes.data.qr_code_base64
|
||
// );
|
||
// }
|
||
}
|
||
|
||
// 使用 RadarV2 的 generateFullImage 方法生成完整图片
|
||
const userNickname = (userInfo as any)?.nickname;
|
||
const titleText = userNickname
|
||
? `${userNickname}的 NTRP 测试结果为`
|
||
: "你的 NTRP 测试结果为";
|
||
const imageUrl = await radarV2Ref.current?.generateFullImage({
|
||
title: titleText,
|
||
ntrpLevel: result?.ntrp_level,
|
||
levelDescription: result?.level_description,
|
||
avatarUrl: (userInfo as any)?.avatar_url,
|
||
qrCodeUrl: finalQrCodeUrl,
|
||
bottomText: "长按识别二维码,快来加入,有你就有场!",
|
||
width: 750, // 设计稿宽度
|
||
height: 1334, // 设计稿高度
|
||
});
|
||
return imageUrl;
|
||
} catch (error) {
|
||
console.error("生成图片失败:", error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
async function handleSaveImage() {
|
||
console.log(userInfo);
|
||
if (!userInfo?.phone) {
|
||
handleAuth();
|
||
return;
|
||
}
|
||
Taro.getSetting().then(async (res) => {
|
||
if (!res.authSetting["scope.writePhotosAlbum"]) {
|
||
Taro.authorize({
|
||
scope: "scope.writePhotosAlbum",
|
||
success: async () => {
|
||
try {
|
||
const url = await genCardImage();
|
||
Taro.saveImageToPhotosAlbum({ filePath: url });
|
||
Taro.showToast({ title: "保存成功" });
|
||
} catch (e) {
|
||
Taro.showToast({ title: "图片保存失败", icon: "none" });
|
||
}
|
||
},
|
||
fail: () => {
|
||
Taro.showModal({
|
||
title: "提示",
|
||
content: "需要开启相册权限才能保存图片",
|
||
success: (r) => {
|
||
if (r.confirm) Taro.openSetting();
|
||
},
|
||
});
|
||
},
|
||
});
|
||
} else {
|
||
try {
|
||
const url = await genCardImage();
|
||
Taro.saveImageToPhotosAlbum({ filePath: url });
|
||
Taro.showToast({ title: "保存成功" });
|
||
} catch (e) {
|
||
Taro.showToast({ title: "图片保存失败", icon: "none" });
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
useShareAppMessage(async (res) => {
|
||
console.log("res", result);
|
||
return {
|
||
title: "来测一测你的NTRP等级吧",
|
||
imageUrl: result?.level_img || undefined,
|
||
path: `/other_pages/ntrp-evaluate/index?stage=${StageType.INTRO}`,
|
||
};
|
||
});
|
||
|
||
function handleAuth() {
|
||
if (userInfo?.phone) {
|
||
return true;
|
||
}
|
||
const currentPage = getCurrentFullPath();
|
||
Taro.redirectTo({
|
||
url: `/login_pages/index/index${
|
||
currentPage ? `?redirect=${encodeURIComponent(currentPage)}` : ""
|
||
}`,
|
||
});
|
||
}
|
||
|
||
function handleGo() {}
|
||
|
||
return (
|
||
<View className={styles.resultContainer}>
|
||
<CommonGuideBar />
|
||
<View
|
||
className={styles.card}
|
||
style={{
|
||
backgroundImage: `url(${OSS_BASE}/front/ball/images/f5b45cea-5015-41d6-aaf4-83b2e76678e1.png)`,
|
||
}}
|
||
>
|
||
<View className={styles.avatarWrap}>
|
||
<View className={styles.avatar}>
|
||
<Image
|
||
className={styles.avatarUrl}
|
||
src={userInfo?.avatar_url || ""}
|
||
mode="aspectFill"
|
||
/>
|
||
</View>
|
||
{/* avatar side */}
|
||
<View className={styles.addonImage}>
|
||
<Image
|
||
className={styles.docImage}
|
||
src={DocCopy}
|
||
mode="aspectFill"
|
||
/>
|
||
</View>
|
||
</View>
|
||
<View className={styles.desc}>
|
||
<View className={styles.tip}>
|
||
<Text>
|
||
{(userInfo as any)?.nickname
|
||
? `${(userInfo as any).nickname}的 NTRP 测试结果为`
|
||
: "你的 NTRP 测试结果为"}
|
||
</Text>
|
||
</View>
|
||
<View className={styles.levelWrap}>
|
||
<Text>NTRP</Text>
|
||
<Text className={styles.level}>
|
||
{formatNtrpDisplay(result?.ntrp_level)}
|
||
</Text>
|
||
</View>
|
||
<View className={styles.slogan}>
|
||
{/* <Text>变线+网前,下一步就是赢比赛!</Text> */}
|
||
<Text>{result?.level_description}</Text>
|
||
</View>
|
||
</View>
|
||
<View>
|
||
<RadarChart ref={radarRef} data={radarData} />
|
||
</View>
|
||
<View className={styles.retest} onClick={handleReTest}>
|
||
<Image className={styles.re_actIcon} src={ReTestIcon} />
|
||
<Text>重新测试</Text>
|
||
</View>
|
||
</View>
|
||
{userInfo?.phone ? (
|
||
<View className={styles.updateTip}>
|
||
<Text>
|
||
你的 NTRP 水平已更新为{" "}
|
||
{formatNtrpDisplay(result?.ntrp_level || "")}{" "}
|
||
</Text>
|
||
<Text className={styles.grayTip}>(可在个人信息中修改)</Text>
|
||
</View>
|
||
) : (
|
||
<View className={styles.updateTip}>
|
||
<Text>登录「有场」小程序,查看匹配你的球局</Text>
|
||
</View>
|
||
)}
|
||
<View className={styles.actions}>
|
||
<View className={styles.viewGame} onClick={handleGoon}>
|
||
<Button className={styles.viewGameBtn}>
|
||
{sourceTypeToTextMap.get(type) || "去看看适合你的球局"}
|
||
</Button>
|
||
</View>
|
||
<View className={styles.otherActions}>
|
||
<View className={styles.share}>
|
||
<Button
|
||
className={styles.shareBtn}
|
||
openType={userInfo?.phone ? "share" : undefined}
|
||
onClick={handleAuth}
|
||
></Button>
|
||
<View className={styles.shareBtnCover}>
|
||
<Image className={styles.wechatIcon} src={WechatIcon} />
|
||
<Text>邀请好友测试</Text>
|
||
</View>
|
||
</View>
|
||
<View className={styles.saveImage} onClick={handleSaveImage}>
|
||
<Button className={styles.saveImageBtn}></Button>
|
||
<View className={styles.saveImageBtnCover}>
|
||
<Image className={styles.downloadIcon} src={DownloadIcon} />
|
||
<Text>保存图片</Text>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
{/* 隐藏的 RadarV2 用于生成完整图片,不显示在界面上 */}
|
||
<View
|
||
style={{
|
||
position: "absolute",
|
||
top: "-9999px",
|
||
left: "-9999px",
|
||
width: "0px",
|
||
height: "0px",
|
||
overflow: "hidden",
|
||
}}
|
||
>
|
||
<RadarChartV2
|
||
ref={radarV2Ref}
|
||
data={radarData}
|
||
title={
|
||
(userInfo as any)?.nickname
|
||
? `${(userInfo as any).nickname}的 NTRP 测试结果为`
|
||
: "你的 NTRP 测试结果为"
|
||
}
|
||
ntrpLevel={result?.ntrp_level}
|
||
levelDescription={result?.level_description}
|
||
avatarUrl={(userInfo as any)?.avatar_url}
|
||
qrCodeUrl={qrCodeUrl}
|
||
bottomText="长按识别二维码,快来加入,有你就有场!"
|
||
/>
|
||
</View>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
const ComponentsMap = {
|
||
[StageType.INTRO]: Intro,
|
||
[StageType.TEST]: Test,
|
||
[StageType.RESULT]: Result,
|
||
};
|
||
|
||
function NtrpEvaluate() {
|
||
// const { updateUserInfo } = useUserActions();
|
||
const { params } = useRouter();
|
||
// const { redirect } = params;
|
||
|
||
const stage = params.stage as StageType;
|
||
|
||
// async function handleUpdateNtrp() {
|
||
// await updateUserInfo({
|
||
// ntrp_level: "4.0",
|
||
// });
|
||
// Taro.showToast({
|
||
// title: "更新成功",
|
||
// icon: "success",
|
||
// duration: 2000,
|
||
// });
|
||
// await delay(2000);
|
||
// if (redirect) {
|
||
// Taro.redirectTo({ url: decodeURIComponent(redirect) });
|
||
// }
|
||
// }
|
||
|
||
const Component = ComponentsMap[stage];
|
||
|
||
return <Component />;
|
||
}
|
||
|
||
export default withAuth(NtrpEvaluate);
|