This commit is contained in:
张成
2026-04-20 15:57:03 +08:00
parent 56fb3ade00
commit 66c5ea6284
7 changed files with 22779 additions and 2272 deletions

20688
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -70,11 +70,11 @@
"eslint-plugin-react": "^7.8.2",
"eslint-plugin-react-hooks": "^4.2.0",
"postcss": "^8.4.18",
"react-refresh": "^0.11.0",
"react-refresh": "^0.14.0",
"stylelint": "^14.4.0",
"ts-node": "^10.9.1",
"tsconfig-paths-webpack-plugin": "^4.0.1",
"typescript": "^5.1.0",
"webpack": "5.78.0"
"webpack": "5.91.0"
}
}

View File

@@ -2,7 +2,7 @@
"miniprogramRoot": "dist/",
"projectname": "playBallTogether",
"description": "playBallTogether",
"appid": "wx815b533167eb7b53",
"appid": "wx915ecf6c01bea4ec",
"setting": {
"urlCheck": true,
"es6": true,
@@ -40,10 +40,21 @@
"minifyWXML": true,
"swc": true,
"disableSWC": false,
"ignoreUploadUnusedFiles": true
"ignoreUploadUnusedFiles": true,
"compileWorklet": false,
"localPlugins": false,
"disableUseStrict": false,
"useCompilerPlugins": false,
"condition": false
},
"compileType": "miniprogram",
"simulatorType": "wechat",
"simulatorPluginLibVersion": {},
"condition": {}
}
"condition": {},
"libVersion": "3.9.0",
"packOptions": {
"ignore": [],
"include": []
},
"editorSetting": {}
}

View File

@@ -1,6 +1,6 @@
{
"libVersion": "3.9.0",
"projectname": "playBallTogether",
"libVersion": "3.15.1",
"projectname": "mini-programs",
"condition": {},
"setting": {
"urlCheck": false,

View File

@@ -30,6 +30,18 @@ import DownloadIcon from "@/static/ntrp/ntrp_download.svg";
import ReTestIcon from "@/static/ntrp/ntrp_re-action.svg";
import styles from "./index.module.scss";
/** 微信小程序码 scene 最长 32 字符 */
const WX_SCENE_MAX_LEN = 32;
/** 分享图/太阳码r={record_id},落地后在 NtrpEvaluate 归一化为 stage=result&id=&from_share=1 */
function buildNtrpShareScene(recordId: string | number): string {
const scene = `r=${recordId}`;
if (scene.length > WX_SCENE_MAX_LEN) {
console.warn("[ntrp-evaluate] share scene exceeds WeChat limit:", scene);
}
return scene;
}
const sourceTypeToTextMap = new Map([
[EvaluateScene.detail, "继续加入球局"],
[EvaluateScene.publish, "继续发布球局"],
@@ -485,7 +497,8 @@ function Test() {
function Result() {
const { params } = useRouter();
const { id } = params;
const { id, from_share } = params;
const fromShare = from_share === "1" || from_share === "true";
const userInfo = useUserInfo();
const { fetchUserInfo, updateUserInfo } = useUserActions();
const { type, next, clear } = useEvaluate();
@@ -514,13 +527,14 @@ function Result() {
init();
}, [id]);
// 获取二维码 - 调用接口生成分享二维码
// 获取二维码 - 太阳码携带当次 record_id扫码进入分享结果页
async function fetchQRCode() {
try {
// 调用接口生成二维码,分享当前页面
const recordId = id != null && id !== "" ? String(id) : "";
if (!recordId) return;
const qrCodeUrlRes = await DetailService.getQrCodeUrl({
page: "other_pages/ntrp-evaluate/index",
scene: `stage=${StageType.INTRO}`,
scene: buildNtrpShareScene(recordId),
});
setQrCodeUrl(qrCodeUrlRes.data.ossPath);
// if (qrCodeUrlRes.code === 0 && qrCodeUrlRes.data?.qr_code_base64) {
@@ -536,29 +550,57 @@ function Result() {
}
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 recordId = Number(id);
if (!id || Number.isNaN(recordId) || recordId <= 0) {
Taro.showToast({ title: "链接无效", icon: "none" });
Taro.redirectTo({
url: `/other_pages/ntrp-evaluate/index?stage=${StageType.INTRO}`,
});
return;
}
try {
const res = await evaluateService.getTestResult(
{
record_id: recordId,
...(fromShare ? { from_share: true } : {}),
},
fromShare ? { showToast: false } : undefined,
);
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,
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];
const nextRadarData: [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);
]);
setRadarData(nextRadarData);
if (!fromShare) {
updateUserLevel(res.data.record_id, res.data.ntrp_level);
}
}
} catch (e: any) {
if (fromShare) {
Taro.showToast({
title: e?.message || "分享已失效或无权查看",
icon: "none",
});
Taro.redirectTo({
url: `/other_pages/ntrp-evaluate/index?stage=${StageType.INTRO}`,
});
}
// 本人结果页失败时 httpService 已 toast此处不再重复
}
}
@@ -615,10 +657,11 @@ function Result() {
// 确保二维码已获取,如果没有则重新获取
let finalQrCodeUrl = qrCodeUrl;
if (!finalQrCodeUrl) {
// 直接调用接口获取二维码
const recordId = id != null && id !== "" ? String(id) : "";
if (!recordId) throw new Error("缺少测评记录");
const qrCodeUrlRes = await DetailService.getQrCodeUrl({
page: "other_pages/ntrp-evaluate/index",
scene: `stage=${StageType.INTRO}`,
scene: buildNtrpShareScene(recordId),
});
finalQrCodeUrl = qrCodeUrlRes.data.ossPath;
// if (qrCodeUrlRes.code === 0 && qrCodeUrlRes.data?.qr_code_base64) {
@@ -628,16 +671,20 @@ function Result() {
// }
}
// 使用 RadarV2 的 generateFullImage 方法生成完整图片
const userNickname = (userInfo as any)?.nickname;
const titleText = userNickname
? `${userNickname}的 NTRP 测试结果为`
const posterNickname = fromShare
? result?.sharer_nickname || "好友"
: (userInfo as any)?.nickname;
const titleText = posterNickname
? `${posterNickname}的 NTRP 测试结果为`
: "你的 NTRP 测试结果为";
const posterAvatar = fromShare
? result?.sharer_avatar_url || (userInfo as any)?.avatar_url
: (userInfo as any)?.avatar_url;
const imageUrl = await radarV2Ref.current?.generateFullImage({
title: titleText,
ntrpLevel: result?.ntrp_level,
levelDescription: result?.level_description,
avatarUrl: (userInfo as any)?.avatar_url,
avatarUrl: posterAvatar,
qrCodeUrl: finalQrCodeUrl,
bottomText: "长按识别二维码,快来加入,有你就有场!",
width: 750, // 设计稿宽度
@@ -651,8 +698,7 @@ function Result() {
}
async function handleSaveImage() {
console.log(userInfo);
if (!userInfo?.phone) {
if (!fromShare && !userInfo?.phone) {
handleAuth();
return;
}
@@ -691,12 +737,18 @@ function Result() {
});
}
useShareAppMessage(async (res) => {
console.log("res", result);
useShareAppMessage(async () => {
const rid = result?.record_id ?? Number(id);
const sharePath =
rid && !Number.isNaN(Number(rid))
? `/other_pages/ntrp-evaluate/index?stage=${StageType.RESULT}&id=${rid}&from_share=1`
: `/other_pages/ntrp-evaluate/index?stage=${StageType.INTRO}`;
return {
title: "来测一测你的NTRP等级吧",
title: result?.ntrp_level
? `来看看 NTRP ${formatNtrpDisplay(result.ntrp_level)} 的测评结果`
: "来测一测你的NTRP等级吧",
imageUrl: result?.level_img || undefined,
path: `/other_pages/ntrp-evaluate/index?stage=${StageType.INTRO}`,
path: sharePath,
};
});
@@ -714,6 +766,18 @@ function Result() {
function handleGo() {}
const cardTitleText = fromShare
? result?.sharer_nickname
? `${result.sharer_nickname}的 NTRP 测试结果为`
: "好友分享的 NTRP 测试结果为"
: (userInfo as any)?.nickname
? `${(userInfo as any).nickname}的 NTRP 测试结果为`
: "你的 NTRP 测试结果为";
const cardAvatarUrl = fromShare
? result?.sharer_avatar_url || ""
: userInfo?.avatar_url || "";
return (
<View className={styles.resultContainer}>
<CommonGuideBar />
@@ -727,7 +791,7 @@ function Result() {
<View className={styles.avatar}>
<Image
className={styles.avatarUrl}
src={userInfo?.avatar_url || ""}
src={cardAvatarUrl}
mode="aspectFill"
/>
</View>
@@ -742,11 +806,7 @@ function Result() {
</View>
<View className={styles.desc}>
<View className={styles.tip}>
<Text>
{(userInfo as any)?.nickname
? `${(userInfo as any).nickname}的 NTRP 测试结果为`
: "你的 NTRP 测试结果为"}
</Text>
<Text>{cardTitleText}</Text>
</View>
<View className={styles.levelWrap}>
<Text>NTRP</Text>
@@ -767,7 +827,7 @@ function Result() {
<Text></Text>
</View>
</View>
{userInfo?.phone ? (
{userInfo?.phone && !fromShare ? (
<View className={styles.updateTip}>
<Text>
NTRP {" "}
@@ -775,11 +835,17 @@ function Result() {
</Text>
<Text className={styles.grayTip}>()</Text>
</View>
) : (
) : !userInfo?.phone ? (
<View className={styles.updateTip}>
<Text></Text>
</View>
)}
) : fromShare ? (
<View className={styles.updateTip}>
<Text className={styles.grayTip}>
NTRP
</Text>
</View>
) : null}
<View className={styles.actions}>
<View className={styles.viewGame} onClick={handleGoon}>
<Button className={styles.viewGameBtn}>
@@ -821,14 +887,10 @@ function Result() {
<RadarChartV2
ref={radarV2Ref}
data={radarData}
title={
(userInfo as any)?.nickname
? `${(userInfo as any).nickname}的 NTRP 测试结果为`
: "你的 NTRP 测试结果为"
}
title={cardTitleText}
ntrpLevel={result?.ntrp_level}
levelDescription={result?.level_description}
avatarUrl={(userInfo as any)?.avatar_url}
avatarUrl={cardAvatarUrl}
qrCodeUrl={qrCodeUrl}
bottomText="长按识别二维码,快来加入,有你就有场!"
/>
@@ -844,9 +906,21 @@ const ComponentsMap = {
};
function NtrpEvaluate() {
// const { updateUserInfo } = useUserActions();
const { params } = useRouter();
// const { redirect } = params;
// 太阳码 scene 仅支持 32 字符,用 r=id 落地后归一化为结果页 + from_share
useEffect(() => {
const r = params.r;
if (r && !params.stage) {
Taro.redirectTo({
url: `/other_pages/ntrp-evaluate/index?stage=${StageType.RESULT}&id=${encodeURIComponent(String(r))}&from_share=1`,
});
}
}, [params.r, params.stage]);
if (params.r && !params.stage) {
return null;
}
const stage = params.stage as StageType;

View File

@@ -1,5 +1,5 @@
import httpService from "./httpService";
import type { ApiResponse } from "./httpService";
import type { ApiResponse, RequestConfig } from "./httpService";
export enum StageType {
INTRO = "intro",
@@ -59,6 +59,9 @@ export interface TestResultData {
radar_data: RadarData;
answers: Answer[];
sort?: string[]; // 雷达图能力项排序,如 ["正手球质", "正手控制", ...]
/** 分享查看时后端可返回测评归属用户,用于展示头像昵称 */
sharer_nickname?: string;
sharer_avatar_url?: string;
}
// 单条测试记录
@@ -118,11 +121,15 @@ class EvaluateService {
return httpService.post("/ntrp/history", {}, { showLoading: true });
}
// 获取测试详情
async getTestResult(req: {
record_id: number;
}): Promise<ApiResponse<TestResultData>> {
return httpService.post("/ntrp/detail", req, { showLoading: true });
// 获取测试详情from_share 为 true 时表示扫码/分享查看他人当次结果,需后端 /ntrp/detail 按 record_id 放行只读)
async getTestResult(
req: { record_id: number; from_share?: boolean },
httpConfig?: Partial<RequestConfig>,
): Promise<ApiResponse<TestResultData>> {
return httpService.post("/ntrp/detail", req, {
showLoading: true,
...httpConfig,
});
}
// 获取最后一次(最新)测试结果

4133
yarn.lock

File diff suppressed because it is too large Load Diff