feat: 问卷调查
This commit is contained in:
@@ -1,22 +1,388 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { View, Text, Image, Button } from "@tarojs/components";
|
||||
import Taro, { useRouter } from "@tarojs/taro";
|
||||
import { withAuth } from "@/components";
|
||||
import evaluateService from "@/services/evaluateService";
|
||||
import { useUserActions } from "@/store/userStore";
|
||||
import dayjs from "dayjs";
|
||||
import classnames from "classnames";
|
||||
import { withAuth, RadarChart } from "@/components";
|
||||
import evaluateService, {
|
||||
LastTimeTestResult,
|
||||
Question,
|
||||
} from "@/services/evaluateService";
|
||||
import { useUserInfo, useUserActions } from "@/store/userStore";
|
||||
import { delay } 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 styles from "./index.module.scss";
|
||||
|
||||
enum StageType {
|
||||
INTRO = "intro",
|
||||
TEST = "test",
|
||||
RESULT = "result",
|
||||
}
|
||||
|
||||
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 (
|
||||
<View className={styles.header}>
|
||||
<View className={styles.closeIcon} onClick={handleClose}>
|
||||
<Image className={styles.closeImg} src={CloseIcon} />
|
||||
</View>
|
||||
<View className={styles.title}>
|
||||
<Text>{title}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function Intro(props) {
|
||||
const { redirect } = props;
|
||||
const [ntrpData, setNtrpData] = useState<LastTimeTestResult>();
|
||||
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 (
|
||||
<View className={styles.introContainer}>
|
||||
<CommonGuideBar />
|
||||
{ntrpData?.has_ntrp_level ? (
|
||||
<View className={styles.result}>
|
||||
<View className={styles.avatarWrap}>
|
||||
<View className={styles.avatar}>
|
||||
<Image
|
||||
className={styles.avatarUrl}
|
||||
src={userInfo.avatar_url}
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</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="http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/b7cb47aa-b609-4112-899f-3fde02ed2431.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</View>
|
||||
<View className={styles.lastResult}>
|
||||
<View className={styles.tipAndTime}>
|
||||
<Text>上次测试结果</Text>
|
||||
<Text>{lastTestTime}</Text>
|
||||
</View>
|
||||
<View className={styles.levelWrap}>
|
||||
<Text>NTRP</Text>
|
||||
<Text className={styles.level}>{ntrp_level}</Text>
|
||||
</View>
|
||||
<View className={styles.slogan}>
|
||||
<Text>变线+网前,下一步就是赢比赛!</Text>
|
||||
</View>
|
||||
</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 className={styles.buttonWrap}>
|
||||
<Button
|
||||
className={styles.button}
|
||||
onClick={() => handleNext(StageType.RESULT)}
|
||||
>
|
||||
<Text>继续使用上次测试结果</Text>
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<View className={styles.guide}>
|
||||
{/* tip */}
|
||||
<View className={styles.tip}>
|
||||
<Image
|
||||
className={styles.tipImage}
|
||||
src="http://bimwe.oss-cn-shanghai.aliyuncs.com/front/ball/images/b7cb47aa-b609-4112-899f-3fde02ed2431.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</View>
|
||||
{/* radar */}
|
||||
<View className={styles.radar}>
|
||||
<Image
|
||||
className={styles.radarImage}
|
||||
src="http://bimwe.oss-cn-shanghai.aliyuncs.com/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(props) {
|
||||
const { redirect } = props;
|
||||
const [disabled, setDisabled] = useState(false);
|
||||
const [index, setIndex] = useState(9);
|
||||
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]);
|
||||
|
||||
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 (
|
||||
<View className={styles.testContainer}>
|
||||
<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 ? "完成测试" : "继续"}
|
||||
</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 userInfo = useUserInfo();
|
||||
const { fetchUserInfo } = useUserActions();
|
||||
|
||||
useEffect(() => {
|
||||
fetchUserInfo()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View className={styles.resultContainer}>
|
||||
<CommonGuideBar />
|
||||
<View className={styles.card}>
|
||||
<View className={styles.avatarWrap}>
|
||||
<View className={styles.avatar}>
|
||||
<Image
|
||||
className={styles.avatarUrl}
|
||||
src={userInfo.avatar_url}
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</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>你的 NTRP 测试结果为</Text>
|
||||
</View>
|
||||
<View className={styles.levelWrap}>
|
||||
<Text>NTRP</Text>
|
||||
<Text className={styles.level}>{1.1}</Text>
|
||||
</View>
|
||||
<View className={styles.slogan}>
|
||||
<Text>变线+网前,下一步就是赢比赛!</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View>
|
||||
<RadarChart />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const ComponentsMap = {
|
||||
[StageType.INTRO]: Intro,
|
||||
[StageType.TEST]: Test,
|
||||
[StageType.RESULT]: Result,
|
||||
};
|
||||
|
||||
function NtrpEvaluate() {
|
||||
const { updateUserInfo } = useUserActions();
|
||||
const { params } = useRouter();
|
||||
const { redirect } = params;
|
||||
|
||||
useEffect(() => {
|
||||
evaluateService.getEvaluateQuestions().then((data) => {
|
||||
console.log(data);
|
||||
});
|
||||
}, []);
|
||||
const stage = params.stage as StageType;
|
||||
|
||||
async function handleUpdateNtrp() {
|
||||
await updateUserInfo({
|
||||
@@ -33,21 +399,9 @@ function NtrpEvaluate() {
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View className={styles.container}>
|
||||
<View className={styles.title}>NTRP评分</View>
|
||||
<View className={styles.content}>
|
||||
<Image
|
||||
className={styles.image}
|
||||
src="https://img.yzcdn.cn/vant/cat.jpeg"
|
||||
/>
|
||||
<Text className={styles.description}>您的NTRP评分是 4.0 分。</Text>
|
||||
</View>
|
||||
<Button className={styles.button} onClick={handleUpdateNtrp}>
|
||||
评测通过
|
||||
</Button>
|
||||
</View>
|
||||
);
|
||||
const Component = ComponentsMap[stage];
|
||||
|
||||
return <Component redirect={redirect} />;
|
||||
}
|
||||
|
||||
export default withAuth(NtrpEvaluate);
|
||||
|
||||
Reference in New Issue
Block a user