feat: test result

This commit is contained in:
2025-09-30 09:52:28 +08:00
parent 288b845270
commit 9fef267cf6
7 changed files with 386 additions and 16 deletions

View File

@@ -564,5 +564,123 @@
line-height: normal;
}
}
.retest {
position: absolute;
right: 12px;
top: 12px;
display: flex;
padding: 6px 10px;
justify-content: center;
align-items: center;
gap: 6px;
border-radius: 12px;
border: 0.5px solid rgba(0, 0, 0, 0.12);
background: #FFF;
box-shadow: 0 8px 64px 0 rgba(0, 0, 0, 0.10);
color: rgba(0, 0, 0, 0.85);
font-feature-settings: 'liga' off, 'clig' off;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 20px;
.re_actIcon {
width: 12px;
height: 12px;
}
}
}
.updateTip {
color: #000;
font-feature-settings: 'liga' off, 'clig' off;
font-family: "PingFang SC";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 24px;
text-align: center;
padding: 24px 0;
.grayTip {
color: rgba(60, 60, 67, 0.60);
}
}
.actions {
padding: 0 28px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
gap: 10px;
.viewGame {
border-radius: 16px;
border: 1px solid rgba(0, 0, 0, 0.06);
width: 100%;
overflow: hidden;
.viewGameBtn {
width: 100%;
height: 50px;
background: #000;
color: #FFF;
font-feature-settings: 'liga' off, 'clig' off;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
}
}
.otherActions {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
.share, .saveImage {
width: 50%;
height: 50px;
border-radius: 16px;
border: 1px solid rgba(0, 0, 0, 0.06);
overflow: hidden;
.shareBtn, .saveImageBtn {
background: #FFF;
width: 100%;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
color: #000;
font-feature-settings: 'liga' off, 'clig' off;
font-family: "PingFang SC";
font-size: 16px;
font-style: normal;
font-weight: 600;
line-height: normal;
.downloadIcon {
width: 20px;
height: 20px;
}
.wechatIcon {
width: 24px;
height: 24px;
}
}
}
}
}
}

View File

@@ -1,6 +1,6 @@
import { useState, useEffect, useRef } from "react";
import { View, Text, Image, Button } from "@tarojs/components";
import Taro, { useRouter } from "@tarojs/taro";
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";
@@ -10,13 +10,16 @@ import evaluateService, {
TestResultData,
} from "@/services/evaluateService";
import { useUserInfo, useUserActions } from "@/store/userStore";
import { delay } from "@/utils";
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 {
@@ -25,6 +28,48 @@ enum StageType {
RESULT = "result",
}
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();
@@ -197,7 +242,7 @@ function Intro(props) {
function Test(props) {
const { redirect } = props;
const [disabled, setDisabled] = useState(false);
const [index, setIndex] = useState(9);
const [index, setIndex] = useState(0);
const [questions, setQuestions] = useState<
(Question & { choosen: number })[]
>([]);
@@ -323,14 +368,18 @@ function Test(props) {
);
}
function Result() {
function Result(props) {
const { redirect } = props;
const { params } = useRouter();
const { id } = params;
const userInfo = useUserInfo();
const { fetchUserInfo } = useUserActions();
const radarRef = useRef();
const [result, setResult] = useState<TestResultData>();
const [radarData, setRadarData] = useState<[propName: string, prop: number][]>([])
const [radarData, setRadarData] = useState<
[propName: string, prop: number][]
>([]);
useEffect(() => {
getResultById();
@@ -341,11 +390,121 @@ function Result() {
const res = await evaluateService.getTestResult({ record_id: Number(id) });
if (res.code === 0) {
setResult(res.data);
setRadarData(Object.entries(res.data.radar_data.abilities).map(([key, value]) => [key, value.current_score]))
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);
}
}
console.log(result, "result");
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",
});
}
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 (
<View className={styles.resultContainer}>
@@ -381,9 +540,52 @@ function Result() {
</View>
</View>
<View>
<RadarChart data={radarData} />
<RadarChart ref={radarRef} data={radarData} />
</View>
<View className={styles.retest} onClick={handleReTest}>
<Image className={styles.re_actIcon} src={ReTestIcon} />
<Text></Text>
</View>
</View>
{userInfo.id ? (
<View className={styles.updateTip}>
<Text> NTRP {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={handleViewGames}>
<Button className={styles.viewGameBtn}></Button>
</View>
<View className={styles.otherActions}>
<View className={styles.share}>
<Button className={styles.shareBtn} openType={userInfo.id ? 'share' : undefined} onClick={handleAuth}>
<Image className={styles.wechatIcon} src={WechatIcon} />
<Text></Text>
</Button>
</View>
<View className={styles.saveImage} onClick={handleSaveImage}>
<Button className={styles.saveImageBtn}>
<Image className={styles.downloadIcon} src={DownloadIcon} />
<Text></Text>
</Button>
</View>
</View>
</View>
<Canvas
type="2d"
id="exportCanvas"
style={{
width: "0px",
height: "0px",
position: "absolute",
left: "-9999px",
}}
/>
</View>
);
}