import { useState, useEffect, useRef, useId } 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 } from "@/components";
import evaluateService, {
LastTimeTestResult,
Question,
TestResultData,
} from "@/services/evaluateService";
import { useUserInfo, useUserActions } from "@/store/userStore";
import { delay, getCurrentFullPath } from "@/utils";
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";
enum StageType {
INTRO = "intro",
TEST = "test",
RESULT = "result",
}
enum SourceType {
DETAIL = 'detail',
PUBLISH = 'publish',
}
const sourceTypeToTextMap = new Map([
[SourceType.DETAIL, '继续加入球局'],
[SourceType.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 CommonGuideBar(props) {
const { title, confirm } = props;
const { params } = useRouter();
const { redirect } = params;
function handleClose() {
//TODO: 二次确认
if (confirm) {
}
Taro.redirectTo({
url: redirect ? redirect : "/game_pages/list/index",
});
}
return (
{title}
);
}
function Intro(props) {
const { redirect } = props;
const [ntrpData, setNtrpData] = useState();
const userInfo = useUserInfo();
const { fetchUserInfo } = useUserActions();
const [ready, setReady] = useState(false);
const { last_test_result: { ntrp_level, create_time, id } = {} } =
ntrpData || {};
const lastTestTime = dayjs(create_time).format("YYYY年M月D日");
useEffect(() => {
getLastResult();
}, []);
async function getLastResult() {
const res = await evaluateService.getLastResult();
if (res.code === 0) {
setNtrpData(res.data);
if (res.data.has_ntrp_level) {
fetchUserInfo();
}
setReady(true);
}
}
if (!ready) {
return "";
}
function handleNext(type) {
Taro.redirectTo({
url: `/other_pages/ntrp-evaluate/index?stage=${type}${
type === StageType.RESULT ? `&id=${id}` : ""
}${redirect ? `&redirect=${redirect}` : ""}`,
});
}
return (
{ntrpData?.has_ntrp_level ? (
{/* avatar side */}
{/* tip */}
上次测试结果
{lastTestTime}
NTRP
{ntrp_level}
变线+网前,下一步就是赢比赛!
) : (
{/* tip */}
{/* radar */}
NTRP(National Tennis Rating
Program)是一种常用的网球水平分级系统,这不是绝对精准的“分数”,而是一个参考标准,能够帮助你更清晰地了解自己的网球水平,从而在训练、比赛或娱乐活动中找到「难度合适」的球友,避免过度碾压或被碾压。
)}
);
}
function Test(props) {
const { redirect } = props;
const [disabled, setDisabled] = useState(false);
const [index, setIndex] = useState(0);
const [questions, setQuestions] = useState<
(Question & { choosen: number })[]
>([]);
const startTimeRef = useRef(0);
useEffect(() => {
startTimeRef.current = Date.now();
getQUestions();
}, []);
useEffect(() => {
setDisabled(questions[index]?.choosen === -1);
}, [index, questions]);
async function getQUestions() {
const res = await evaluateService.getQuestions();
if (res.code === 0) {
setQuestions(res.data.map((item) => ({ ...item, choosen: 3 })));
}
}
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.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
}${redirect ? `&redirect=${redirect}` : ""}`,
});
}
} catch (e) {
Taro.showToast({ title: e.message, icon: "error" });
} finally {
setDisabled(false);
}
}
function handIndexChange(direction) {
console.log(disabled, direction);
if (disabled && direction > 0) {
return;
}
if (index === questions.length - 1 && direction > 0) {
handleSubmit();
return;
}
setIndex((prev) => prev + direction);
}
const question = questions[index];
if (!question) {
return "";
}
return (
根据近3个月实际表现勾选最符合项
{question.question_content}
{question.options.map((item, i) => {
const checked = question.choosen === i;
return (
handleSelect(i)}
>
{item.text}
);
})}
handIndexChange(1)}
>
{index !== 0 && (
handIndexChange(-1)}>
返回
)}
);
}
function Result(props) {
const { params } = useRouter();
const { id, type, redirect } = params;
const userInfo = useUserInfo();
const { fetchUserInfo } = useUserActions();
const radarRef = useRef();
const [result, setResult] = useState();
const [radarData, setRadarData] = useState<
[propName: string, prop: number][]
>([]);
useEffect(() => {
getResultById();
fetchUserInfo();
}, []);
async function getResultById() {
const res = await evaluateService.getTestResult({ record_id: Number(id) });
if (res.code === 0) {
setResult(res.data);
setRadarData(
adjustRadarLabels(
Object.entries(res.data.radar_data.abilities).map(([key, value]) => [
key,
value.current_score,
])
)
);
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",
});
} catch (e) {
Taro.showToast({ title: e.message, icon: "none" });
}
}
function handleReTest() {
Taro.redirectTo({
url: `/other_pages/ntrp-evaluate/index?stage=${StageType.TEST}${
redirect ? `&redirect=${redirect}` : ""
}`,
});
}
function handleViewGames() {
Taro.redirectTo({
url: "/game_pages/list/index",
});
}
function handleGoon () {
if (type) {
Taro.redirectTo({ url: redirect })
} else {
handleViewGames()
}
}
async function genCardImage() {
return new Promise(async (resolve, reject) => {
const url = await radarRef.current.generateImage();
const query = Taro.createSelectorQuery();
query
.select("#exportCanvas")
.fields({ node: true, size: true })
.exec((res2) => {
const canvas = res2[0].node;
const ctx = canvas.getContext("2d");
const dpr = Taro.getSystemInfoSync().pixelRatio;
const width = 300;
const height = 400;
canvas.width = width * dpr;
canvas.height = height * dpr;
ctx.scale(dpr, dpr);
// 背景
ctx.fillStyle = "#e9fdf8";
ctx.fillRect(0, 0, width, height);
// 标题文字
ctx.fillStyle = "#000";
ctx.font = "16px sans-serif";
ctx.fillText("你的 NTRP 测试结果为", 20, 40);
ctx.fillStyle = "#00E5AD";
ctx.font = "bold 22px sans-serif";
ctx.fillText(`NTRP ${result?.ntrp_level}`, 20, 70);
// 绘制雷达图
const img = canvas.createImage();
img.src = url;
img.onload = () => {
ctx.drawImage(img, 20, 100, 260, 260);
// 第三步:导出最终卡片
Taro.canvasToTempFilePath({
canvas,
success: (res3) => {
console.log("导出成功:", res3.tempFilePath);
resolve(res3.tempFilePath);
},
});
};
});
});
}
async function handleSaveImage() {
if (!userInfo.id) {
return
}
const url = await genCardImage();
Taro.saveImageToPhotosAlbum({ filePath: url });
}
useShareAppMessage(async (res) => {
const url = await genCardImage();
console.log(res, "res");
return {
title: "分享",
imageUrl: url,
path: `/other_pages/ntrp-evaluate/index?stage=${StageType.INTRO}`,
};
});
function handleAuth () {
if (userInfo.id) {
return true
}
const currentPage = getCurrentFullPath()
Taro.redirectTo({
url: `/login_pages/index/index${
currentPage ? `?redirect=${encodeURIComponent(currentPage)}` : ""
}`,
});
}
return (
{/* avatar side */}
你的 NTRP 测试结果为
NTRP
{result?.ntrp_level}
变线+网前,下一步就是赢比赛!
重新测试
{userInfo.id ? (
你的 NTRP 水平已更新为 {result?.ntrp_level}
(可在个人信息中修改)
) : (
登录「有场」小程序,查看匹配你的球局
)}
);
}
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 ;
}
export default withAuth(NtrpEvaluate);