feat: test result
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user