Ver código fonte

常规视康训练

周玉佂 1 semana atrás
pai
commit
bfac4421dc
16 arquivos alterados com 2718 adições e 124 exclusões
  1. 1 1
      .env.development
  2. 366 0
      src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/Words/index.vue
  3. 56 0
      src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/Words/topics.json
  4. 366 0
      src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/diagramMap/index.vue
  5. 56 0
      src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/diagramMap/topics.json
  6. 366 0
      src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/essay/index.vue
  7. 56 0
      src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/essay/topics.json
  8. 366 0
      src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/execute/index.vue
  9. 56 0
      src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/execute/topics.json
  10. 366 0
      src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/mapWord/index.vue
  11. 56 0
      src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/mapWord/topics.json
  12. 366 0
      src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/wordMap/index.vue
  13. 56 0
      src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/wordMap/topics.json
  14. 128 115
      src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/wordWord/index.vue
  15. 46 2
      src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/wordWord/topics.json
  16. 11 6
      src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/index.vue

+ 1 - 1
.env.development

@@ -12,7 +12,7 @@ VITE_APP_BASE_API = '/dev-api'
 # VITE_APP_API_URL = 'http://192.168.1.8:8001'
 
 # 公司本地
-VITE_APP_API_URL = 'http://192.168.1.10:8107'
+VITE_APP_API_URL = 'http://192.168.1.4:8107'
 
 # 家里本地
 # VITE_APP_API_URL = 'http://192.168.1.11:8107'

+ 366 - 0
src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/Words/index.vue

@@ -0,0 +1,366 @@
+<template>
+  <!--  <my-full-screen-dialog ref="openDialogRef" close-color="#0F4DD8" @close-dialog="handleClose">-->
+  <div class="adl-container w-full h-full flex-center text-[#134FA4]">
+    <div class="center-area w-[90%] h-[90%]">
+      <div
+        class="child-container bg-[#ffffff] bor-radius-[10] w-[1360px] h-[860px] mt-[100px] flex flex-col justify-center items-center gap-y-[30px] absolute left-[500px]"
+      >
+        <div class="bg-1">
+          <div class="bg-1 text-[68px] w-[80%] absolute top-[30px] left-[550px] color-[#333333]">
+            <div class="question-img w-[290px] h-[290px] text-[186px]">
+              {{ onceData.question }}
+            </div>
+          </div>
+        </div>
+        <div
+          id="myButton"
+          class="divMain absolute left-[165px] top-[305px] flex flex-wrap flex-row gap-x-[20px] mt-[50px] w-[1100px]"
+          :class="onceData.choices && onceData.choices.length < 3 ? 'flex-center' : ''"
+        >
+          <div v-for="(it, index) in onceData.choices" :key="it">
+            <div class="text-[80px] w-[350px] flex flex-wrap flex-row justify-between">
+              <!--              <div class="w-[450px] sab ml-[115px] mt-[30px] flex flex-wrap flex-row justify-between">-->
+              <div
+                class="bg-[#000] w-[120px] h-[120px] flex-center"
+                :class="[it.active ? 'active' : 'normal']"
+                @click="checkItemFn(it, index)"
+              ></div>
+              <span class="color-[#000] fw-700 line-height-[155px]">{{ chengList[index] }}.</span
+              ><span class="color-[#333333] line-height-[155px] fw-700 mr-[60px]"> {{ it.name }}</span>
+            </div>
+            <!--            </div>-->
+            <div
+              class="question-imgStart w-[290px] h-[90px] text-[186px] absolute bottom-[-170px] left-[405px]"
+              @click="handleClick(it, index)"
+            ></div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!--  <div class="dialog-footer text-right absolute bottom-[0px] right-[0px]">-->
+  <!--    <el-button type="text" @click="handleSubmit">结束测验</el-button>-->
+  <!--  </div>-->
+  <VoiceImp ref="VoiceImpRef" />
+  <!--  </my-full-screen-dialog>-->
+</template>
+
+<script setup lang="ts">
+/*
+ * 组件名: wordWord
+ * 组件用途: 字字匹配
+ * 创建日期: 2024/9/9
+ * 编写者: JutarryWu
+ */
+import { getCurrentInstance, ref } from 'vue'
+import { getRandomInt } from '@/utils'
+import AchievementAPI from '@/api/tester/rehabilitation/training/achievement'
+import { RTPlanMgrQuery } from '@/api/tester/rehabilitation/training/plain'
+import { useUserStore } from '@/store'
+import Topics from './topics.json'
+interface LevelData {
+  level: number
+  correct: boolean
+  score: number
+  responseStartTime: number
+  responseEndTime: number
+}
+interface OnceItem {
+  question: string
+  active: boolean
+  questions: string[]
+  choices: string[]
+  answer: string
+}
+const gameData = ref(Topics as OnceItem[][])
+// 当前游戏等级
+let currentLevel = 0
+// 得分列表
+const scoreList = [10, 30, 60, 100, 150, 210]
+
+const chengList = ref(['1', '2', '3', '4'])
+// 响应开始时间戳
+let responseStartTime = 0
+// 游戏收集的数据
+let gameList: LevelData[] = []
+// 游戏开始时间
+let gameStartTime = 0
+let gameStartTime_record = 0
+// 游戏结束时间
+let gameEndTime = 0
+// 最大游戏等级 6
+const maxLevel = 2
+// 当前试次
+let currentNumber = 0
+// 每个等级的最大试次 15
+const maxNumber = 3
+let timerId: any
+const $emits = defineEmits(['close'])
+const openDialogRef = ref()
+const VoiceImpRef = ref()
+
+const handleClose = (done: () => void) => {
+  $emits('close', '')
+}
+
+const instance = getCurrentInstance()
+const planInfo = ref<RTPlanMgrQuery>()
+const userStore = useUserStore()
+// 当前试次的游戏图片数据
+const onceData = ref({} as OnceItem)
+const handleClick = (answer: string) => {
+  console.log(answer)
+  const correct = false
+  // const correct = onceData.value.answer === answer
+  // VoiceImpRef.value.videoPlay('click')
+  // setTimeout(() => {
+  //   if (correct) {
+  //     console.log(VoiceImpRef.value, 'hbbbbbbbbbbbbbbbbbbbbbbbbbdf=============>')
+  //     if (VoiceImpRef.value) {
+  //       VoiceImpRef.value.videoPlay('right')
+  //     }
+  //   } else {
+  //     if (VoiceImpRef.value) {
+  //       VoiceImpRef.value.videoPlay('error')
+  //     }
+  //   }
+  // }, 200)
+  // 收集当前试次的用户操作数据
+  gameList.push({
+    level: currentLevel,
+    correct: correct,
+    score: correct ? scoreList[currentLevel] : 0,
+    responseEndTime: performance.now(),
+    responseStartTime: responseStartTime
+  })
+  console.log(gameList)
+
+  // 开始下一次
+  nextOnce()
+}
+const checkItemFn = (item: any, index: number) => {
+  // if (modeSelect.value === 0) {
+  onceData.value.choices.forEach((item2) => {
+    item2.active = false
+  })
+  //   VoiceImpRef.value.videoPlay()
+  console.log(item)
+  item.active = !item.active
+  //   itemActive.value = item.key
+  // }
+}
+const nextOnce = () => {
+  console.log(onceData.value.choices, 'onceData392')
+  gameEndTime = performance.now()
+  const gameDuration = gameEndTime - gameStartTime
+  console.log(gameDuration, 'gameDuration')
+  console.log(gameEndTime, 'gameEndTime')
+  console.log(gameStartTime, 'gameStartTime')
+  const currentLevelList = gameList.filter((it) => it.level === currentLevel)
+  // 当前等级进行 10 个试次以上时,计算正确率
+  if (currentLevelList.length >= 3) {
+    // 当前等级正确试次个数
+    const count = currentLevelList.reduce((acc, curr) => {
+      if (curr.correct) {
+        acc++
+      }
+      return acc
+    }, 0)
+    // 当前等级正确率
+    const accuracy = (count / currentLevelList.length) * 100
+    // 升降级规则:正确率达到80%难度升级,低于40%降级
+    if (accuracy >= 80) {
+      if (currentLevel < maxLevel) {
+        currentLevel++
+        // 删除升级之后的已有数据
+        gameList = gameList.filter((it) => it.level !== currentLevel)
+        // 重置当前等级试次索引
+        currentNumber = 0
+        console.log('80%难度升级')
+      }
+    } else if (accuracy < 40) {
+      sendData()
+      return
+    }
+  }
+
+  // 如果当前等级的试次小于最大试次,则试次加一,否则应该进入下一等级
+  if (currentNumber < maxNumber) {
+    currentNumber++
+  } else {
+    // 如果当前等级小于最大等级,则等级加一,否则游戏结束
+    if (currentLevel < maxLevel) {
+      currentLevel++
+      // 删除升级之后的已有数据
+      gameList = gameList.filter((it) => it.level !== currentLevel)
+      // 重置当前等级试次索引
+      currentNumber = 0
+    } else {
+      // 游戏结束
+      console.log('游戏结束2')
+      sendData()
+      return
+    }
+  }
+
+  // 根据当前等级和当前试次改变游戏素材
+  setOnceData()
+}
+// 发送请求
+const sendData = () => {
+  if (planInfo.value) {
+    // 计算正确反应数和总反应时
+    let { correctCount, totalResponseTime, totalScore } = gameList.reduce(
+      (obj, curr) => {
+        if (curr.correct) {
+          obj.correctCount++
+        }
+        obj.totalScore += curr.score
+        obj.totalResponseTime += curr.responseEndTime - curr.responseStartTime
+        return obj
+      },
+      { correctCount: 0, totalResponseTime: 0, totalScore: 0 }
+    )
+
+    // 平均反应时 = 总反应时 / 总试次
+    const avrResponseTime = `${(totalResponseTime / gameList.length).toFixed(2)}ms`
+
+    // 如果在60秒内猜中50个物品得30分
+    if (correctCount >= 50) {
+      totalScore += 50
+    }
+
+    const { gameId, gameName, id: planId } = planInfo.value
+    const data = {
+      finish: '1',
+      gameId,
+      gameName,
+      paramList: [
+        {
+          code: 'score',
+          name: '得分',
+          value: totalScore
+        },
+        {
+          code: 'correctCount',
+          name: '正确反应数',
+          value: correctCount
+        },
+        {
+          code: 'avrResponseTime',
+          name: '平均反应时',
+          value: avrResponseTime
+        },
+        {
+          code: 'avrResponseTime',
+          name: '最大难度',
+          value: currentLevel + 1
+        },
+        {
+          code: 'gameTotalTime',
+          name: '游戏总时长',
+          value: Date.now() - gameStartTime_record / 1000
+        }
+      ],
+      planId,
+      gamelevel: currentLevel + 1,
+      score: totalScore,
+      userId: userStore.user.id
+    }
+    console.log('发送数据', data)
+
+    AchievementAPI.add(data).then(() => {
+      ElMessage.success('本次训练已结束')
+      clearInterval(timerId)
+
+      handleClose(() => {})
+
+      instance?.proxy?.$Bus.emit('trainList-refresh')
+    })
+  } else {
+    ElMessage.success('本次训练已结束')
+    clearInterval(timerId)
+
+    handleClose(() => {})
+    instance?.proxy?.$Bus.emit('trainList-refresh')
+  }
+}
+async function exec() {
+  // openDialogRef.value.openDialog()
+  const tempPlan = sessionStorage.getItem('currentPlanInfo')
+  if (tempPlan) {
+    planInfo.value = JSON.parse(tempPlan)
+  }
+  await nextTick(() => {
+    // 记录游戏开始时间戳
+    gameStartTime = performance.now()
+    gameStartTime_record = Date.now()
+    console.log(gameStartTime_record, '总开始时间')
+    // 重置游戏结束时间戳
+    gameEndTime = 0
+    // 重置当前等级
+    currentLevel = 0
+    // 重置当前试次
+    currentNumber = 0
+    // 清空游戏数据
+    gameList = []
+    // 设置第一个试次的游戏素材
+    setOnceData()
+  })
+}
+const setOnceData = () => {
+  // currentLevel
+  // currentNumber
+  // gameMode
+  // 记录响应开始时间
+  responseStartTime = performance.now()
+  onceData.value = gameData.value[currentLevel][currentNumber]
+  console.log(currentLevel, currentNumber, '1.2')
+  onceData.value.choices = shuffleArray(onceData.value.choices)
+}
+// 随机排列数组
+function shuffleArray(array: any) {
+  const newArray = [...array]
+  for (let i = newArray.length - 1; i > 0; i--) {
+    const j = Math.floor(Math.random() * (i + 1))
+    ;[newArray[i], newArray[j]] = [newArray[j], newArray[i]]
+  }
+  return newArray
+}
+onMounted(() => {
+  exec()
+})
+</script>
+
+<style scoped lang="scss">
+.adl-container {
+  background-size: 100% 100%;
+  background-position: center center;
+  background-repeat: repeat;
+  .question-img {
+    background-image: url('/static/image/cognitiveAbility/SpeechTraining/Visual/title.png');
+    background-size: 100% 100%;
+    background-position: center center;
+    background-repeat: repeat;
+    //font-family: 楷体;
+    text-align: center;
+  }
+  .question-imgStart {
+    background-image: url('/static/image/cognitiveAbility/SpeechTraining/Visual/verify-bg.png');
+    background-size: 100% 100%;
+    background-position: center center;
+    background-repeat: repeat;
+    //font-family: 楷体;
+    text-align: center;
+  }
+  .active {
+    background: url('/static/image/cognitiveAbility/SpeechTraining/Visual/Options-right.png') no-repeat 0 0 / 100% 100%;
+    //background-position: 90% 54%; /* 可选,让图片居中对齐 */
+  }
+  .normal {
+    background: url('/static/image/cognitiveAbility/SpeechTraining/Visual/Options-Blank.png') no-repeat 0 0 / 70% 70%;
+    background-position: 10% 90%; /* 可选,让图片居中对齐 */
+  }
+}
+</style>

+ 56 - 0
src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/Words/topics.json

@@ -0,0 +1,56 @@
+[
+  [
+    {
+      "question": "手",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"手",
+          "active": false
+        }, {
+          "name":"脚",
+          "active": false
+        }],
+      "answer": "手"
+    },
+    {
+      "question": "梨",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"柚",
+          "active": false
+        }, {
+          "name":"香",
+          "active": false
+        }, {
+          "name":"梨",
+          "active": false
+        }],
+      "answer": "梨"
+    },
+    {
+      "question": "纸",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"笔",
+          "active": false
+        }, {
+          "name":"纸",
+          "active": false
+        }, {
+          "name":"本",
+          "active": false
+        }, {
+          "name":"书",
+          "active": false
+        }],
+      "answer": "纸"
+    }
+  ]
+]
+

+ 366 - 0
src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/diagramMap/index.vue

@@ -0,0 +1,366 @@
+<template>
+  <!--  <my-full-screen-dialog ref="openDialogRef" close-color="#0F4DD8" @close-dialog="handleClose">-->
+  <div class="adl-container w-full h-full flex-center text-[#134FA4]">
+    <div class="center-area w-[90%] h-[90%]">
+      <div
+        class="child-container bg-[#ffffff] bor-radius-[10] w-[1360px] h-[860px] mt-[100px] flex flex-col justify-center items-center gap-y-[30px] absolute left-[500px]"
+      >
+        <div class="bg-1">
+          <div class="bg-1 text-[68px] w-[80%] absolute top-[30px] left-[550px] color-[#333333]">
+            <div class="question-img w-[290px] h-[290px] text-[186px]">
+              {{ onceData.question }}
+            </div>
+          </div>
+        </div>
+        <div
+          id="myButton"
+          class="divMain absolute left-[165px] top-[305px] flex flex-wrap flex-row gap-x-[20px] mt-[50px] w-[1100px]"
+          :class="onceData.choices && onceData.choices.length < 3 ? 'flex-center' : ''"
+        >
+          <div v-for="(it, index) in onceData.choices" :key="it">
+            <div class="text-[80px] w-[350px] flex flex-wrap flex-row justify-between">
+              <!--              <div class="w-[450px] sab ml-[115px] mt-[30px] flex flex-wrap flex-row justify-between">-->
+              <div
+                class="bg-[#000] w-[120px] h-[120px] flex-center"
+                :class="[it.active ? 'active' : 'normal']"
+                @click="checkItemFn(it, index)"
+              ></div>
+              <span class="color-[#000] fw-700 line-height-[155px]">{{ chengList[index] }}.</span
+              ><span class="color-[#333333] line-height-[155px] fw-700 mr-[60px]"> {{ it.name }}</span>
+            </div>
+            <!--            </div>-->
+            <div
+              class="question-imgStart w-[290px] h-[90px] text-[186px] absolute bottom-[-170px] left-[405px]"
+              @click="handleClick(it, index)"
+            ></div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!--  <div class="dialog-footer text-right absolute bottom-[0px] right-[0px]">-->
+  <!--    <el-button type="text" @click="handleSubmit">结束测验</el-button>-->
+  <!--  </div>-->
+  <VoiceImp ref="VoiceImpRef" />
+  <!--  </my-full-screen-dialog>-->
+</template>
+
+<script setup lang="ts">
+/*
+ * 组件名: wordWord
+ * 组件用途: 字字匹配
+ * 创建日期: 2024/9/9
+ * 编写者: JutarryWu
+ */
+import { getCurrentInstance, ref } from 'vue'
+import { getRandomInt } from '@/utils'
+import AchievementAPI from '@/api/tester/rehabilitation/training/achievement'
+import { RTPlanMgrQuery } from '@/api/tester/rehabilitation/training/plain'
+import { useUserStore } from '@/store'
+import Topics from './topics.json'
+interface LevelData {
+  level: number
+  correct: boolean
+  score: number
+  responseStartTime: number
+  responseEndTime: number
+}
+interface OnceItem {
+  question: string
+  active: boolean
+  questions: string[]
+  choices: string[]
+  answer: string
+}
+const gameData = ref(Topics as OnceItem[][])
+// 当前游戏等级
+let currentLevel = 0
+// 得分列表
+const scoreList = [10, 30, 60, 100, 150, 210]
+
+const chengList = ref(['1', '2', '3', '4'])
+// 响应开始时间戳
+let responseStartTime = 0
+// 游戏收集的数据
+let gameList: LevelData[] = []
+// 游戏开始时间
+let gameStartTime = 0
+let gameStartTime_record = 0
+// 游戏结束时间
+let gameEndTime = 0
+// 最大游戏等级 6
+const maxLevel = 2
+// 当前试次
+let currentNumber = 0
+// 每个等级的最大试次 15
+const maxNumber = 3
+let timerId: any
+const $emits = defineEmits(['close'])
+const openDialogRef = ref()
+const VoiceImpRef = ref()
+
+const handleClose = (done: () => void) => {
+  $emits('close', '')
+}
+
+const instance = getCurrentInstance()
+const planInfo = ref<RTPlanMgrQuery>()
+const userStore = useUserStore()
+// 当前试次的游戏图片数据
+const onceData = ref({} as OnceItem)
+const handleClick = (answer: string) => {
+  console.log(answer)
+  const correct = false
+  // const correct = onceData.value.answer === answer
+  // VoiceImpRef.value.videoPlay('click')
+  // setTimeout(() => {
+  //   if (correct) {
+  //     console.log(VoiceImpRef.value, 'hbbbbbbbbbbbbbbbbbbbbbbbbbdf=============>')
+  //     if (VoiceImpRef.value) {
+  //       VoiceImpRef.value.videoPlay('right')
+  //     }
+  //   } else {
+  //     if (VoiceImpRef.value) {
+  //       VoiceImpRef.value.videoPlay('error')
+  //     }
+  //   }
+  // }, 200)
+  // 收集当前试次的用户操作数据
+  gameList.push({
+    level: currentLevel,
+    correct: correct,
+    score: correct ? scoreList[currentLevel] : 0,
+    responseEndTime: performance.now(),
+    responseStartTime: responseStartTime
+  })
+  console.log(gameList)
+
+  // 开始下一次
+  nextOnce()
+}
+const checkItemFn = (item: any, index: number) => {
+  // if (modeSelect.value === 0) {
+  onceData.value.choices.forEach((item2) => {
+    item2.active = false
+  })
+  //   VoiceImpRef.value.videoPlay()
+  console.log(item)
+  item.active = !item.active
+  //   itemActive.value = item.key
+  // }
+}
+const nextOnce = () => {
+  console.log(onceData.value.choices, 'onceData392')
+  gameEndTime = performance.now()
+  const gameDuration = gameEndTime - gameStartTime
+  console.log(gameDuration, 'gameDuration')
+  console.log(gameEndTime, 'gameEndTime')
+  console.log(gameStartTime, 'gameStartTime')
+  const currentLevelList = gameList.filter((it) => it.level === currentLevel)
+  // 当前等级进行 10 个试次以上时,计算正确率
+  if (currentLevelList.length >= 3) {
+    // 当前等级正确试次个数
+    const count = currentLevelList.reduce((acc, curr) => {
+      if (curr.correct) {
+        acc++
+      }
+      return acc
+    }, 0)
+    // 当前等级正确率
+    const accuracy = (count / currentLevelList.length) * 100
+    // 升降级规则:正确率达到80%难度升级,低于40%降级
+    if (accuracy >= 80) {
+      if (currentLevel < maxLevel) {
+        currentLevel++
+        // 删除升级之后的已有数据
+        gameList = gameList.filter((it) => it.level !== currentLevel)
+        // 重置当前等级试次索引
+        currentNumber = 0
+        console.log('80%难度升级')
+      }
+    } else if (accuracy < 40) {
+      sendData()
+      return
+    }
+  }
+
+  // 如果当前等级的试次小于最大试次,则试次加一,否则应该进入下一等级
+  if (currentNumber < maxNumber) {
+    currentNumber++
+  } else {
+    // 如果当前等级小于最大等级,则等级加一,否则游戏结束
+    if (currentLevel < maxLevel) {
+      currentLevel++
+      // 删除升级之后的已有数据
+      gameList = gameList.filter((it) => it.level !== currentLevel)
+      // 重置当前等级试次索引
+      currentNumber = 0
+    } else {
+      // 游戏结束
+      console.log('游戏结束2')
+      sendData()
+      return
+    }
+  }
+
+  // 根据当前等级和当前试次改变游戏素材
+  setOnceData()
+}
+// 发送请求
+const sendData = () => {
+  if (planInfo.value) {
+    // 计算正确反应数和总反应时
+    let { correctCount, totalResponseTime, totalScore } = gameList.reduce(
+      (obj, curr) => {
+        if (curr.correct) {
+          obj.correctCount++
+        }
+        obj.totalScore += curr.score
+        obj.totalResponseTime += curr.responseEndTime - curr.responseStartTime
+        return obj
+      },
+      { correctCount: 0, totalResponseTime: 0, totalScore: 0 }
+    )
+
+    // 平均反应时 = 总反应时 / 总试次
+    const avrResponseTime = `${(totalResponseTime / gameList.length).toFixed(2)}ms`
+
+    // 如果在60秒内猜中50个物品得30分
+    if (correctCount >= 50) {
+      totalScore += 50
+    }
+
+    const { gameId, gameName, id: planId } = planInfo.value
+    const data = {
+      finish: '1',
+      gameId,
+      gameName,
+      paramList: [
+        {
+          code: 'score',
+          name: '得分',
+          value: totalScore
+        },
+        {
+          code: 'correctCount',
+          name: '正确反应数',
+          value: correctCount
+        },
+        {
+          code: 'avrResponseTime',
+          name: '平均反应时',
+          value: avrResponseTime
+        },
+        {
+          code: 'avrResponseTime',
+          name: '最大难度',
+          value: currentLevel + 1
+        },
+        {
+          code: 'gameTotalTime',
+          name: '游戏总时长',
+          value: Date.now() - gameStartTime_record / 1000
+        }
+      ],
+      planId,
+      gamelevel: currentLevel + 1,
+      score: totalScore,
+      userId: userStore.user.id
+    }
+    console.log('发送数据', data)
+
+    AchievementAPI.add(data).then(() => {
+      ElMessage.success('本次训练已结束')
+      clearInterval(timerId)
+
+      handleClose(() => {})
+
+      instance?.proxy?.$Bus.emit('trainList-refresh')
+    })
+  } else {
+    ElMessage.success('本次训练已结束')
+    clearInterval(timerId)
+
+    handleClose(() => {})
+    instance?.proxy?.$Bus.emit('trainList-refresh')
+  }
+}
+async function exec() {
+  // openDialogRef.value.openDialog()
+  const tempPlan = sessionStorage.getItem('currentPlanInfo')
+  if (tempPlan) {
+    planInfo.value = JSON.parse(tempPlan)
+  }
+  await nextTick(() => {
+    // 记录游戏开始时间戳
+    gameStartTime = performance.now()
+    gameStartTime_record = Date.now()
+    console.log(gameStartTime_record, '总开始时间')
+    // 重置游戏结束时间戳
+    gameEndTime = 0
+    // 重置当前等级
+    currentLevel = 0
+    // 重置当前试次
+    currentNumber = 0
+    // 清空游戏数据
+    gameList = []
+    // 设置第一个试次的游戏素材
+    setOnceData()
+  })
+}
+const setOnceData = () => {
+  // currentLevel
+  // currentNumber
+  // gameMode
+  // 记录响应开始时间
+  responseStartTime = performance.now()
+  onceData.value = gameData.value[currentLevel][currentNumber]
+  console.log(currentLevel, currentNumber, '1.2')
+  onceData.value.choices = shuffleArray(onceData.value.choices)
+}
+// 随机排列数组
+function shuffleArray(array: any) {
+  const newArray = [...array]
+  for (let i = newArray.length - 1; i > 0; i--) {
+    const j = Math.floor(Math.random() * (i + 1))
+    ;[newArray[i], newArray[j]] = [newArray[j], newArray[i]]
+  }
+  return newArray
+}
+onMounted(() => {
+  exec()
+})
+</script>
+
+<style scoped lang="scss">
+.adl-container {
+  background-size: 100% 100%;
+  background-position: center center;
+  background-repeat: repeat;
+  .question-img {
+    background-image: url('/static/image/cognitiveAbility/SpeechTraining/Visual/title.png');
+    background-size: 100% 100%;
+    background-position: center center;
+    background-repeat: repeat;
+    //font-family: 楷体;
+    text-align: center;
+  }
+  .question-imgStart {
+    background-image: url('/static/image/cognitiveAbility/SpeechTraining/Visual/verify-bg.png');
+    background-size: 100% 100%;
+    background-position: center center;
+    background-repeat: repeat;
+    //font-family: 楷体;
+    text-align: center;
+  }
+  .active {
+    background: url('/static/image/cognitiveAbility/SpeechTraining/Visual/Options-right.png') no-repeat 0 0 / 100% 100%;
+    //background-position: 90% 54%; /* 可选,让图片居中对齐 */
+  }
+  .normal {
+    background: url('/static/image/cognitiveAbility/SpeechTraining/Visual/Options-Blank.png') no-repeat 0 0 / 70% 70%;
+    background-position: 10% 90%; /* 可选,让图片居中对齐 */
+  }
+}
+</style>

+ 56 - 0
src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/diagramMap/topics.json

@@ -0,0 +1,56 @@
+[
+  [
+    {
+      "question": "手",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"手",
+          "active": false
+        }, {
+          "name":"脚",
+          "active": false
+        }],
+      "answer": "手"
+    },
+    {
+      "question": "梨",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"柚",
+          "active": false
+        }, {
+          "name":"香",
+          "active": false
+        }, {
+          "name":"梨",
+          "active": false
+        }],
+      "answer": "梨"
+    },
+    {
+      "question": "纸",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"笔",
+          "active": false
+        }, {
+          "name":"纸",
+          "active": false
+        }, {
+          "name":"本",
+          "active": false
+        }, {
+          "name":"书",
+          "active": false
+        }],
+      "answer": "纸"
+    }
+  ]
+]
+

+ 366 - 0
src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/essay/index.vue

@@ -0,0 +1,366 @@
+<template>
+  <!--  <my-full-screen-dialog ref="openDialogRef" close-color="#0F4DD8" @close-dialog="handleClose">-->
+  <div class="adl-container w-full h-full flex-center text-[#134FA4]">
+    <div class="center-area w-[90%] h-[90%]">
+      <div
+        class="child-container bg-[#ffffff] bor-radius-[10] w-[1360px] h-[860px] mt-[100px] flex flex-col justify-center items-center gap-y-[30px] absolute left-[500px]"
+      >
+        <div class="bg-1">
+          <div class="bg-1 text-[68px] w-[80%] absolute top-[30px] left-[550px] color-[#333333]">
+            <div class="question-img w-[290px] h-[290px] text-[186px]">
+              {{ onceData.question }}
+            </div>
+          </div>
+        </div>
+        <div
+          id="myButton"
+          class="divMain absolute left-[165px] top-[305px] flex flex-wrap flex-row gap-x-[20px] mt-[50px] w-[1100px]"
+          :class="onceData.choices && onceData.choices.length < 3 ? 'flex-center' : ''"
+        >
+          <div v-for="(it, index) in onceData.choices" :key="it">
+            <div class="text-[80px] w-[350px] flex flex-wrap flex-row justify-between">
+              <!--              <div class="w-[450px] sab ml-[115px] mt-[30px] flex flex-wrap flex-row justify-between">-->
+              <div
+                class="bg-[#000] w-[120px] h-[120px] flex-center"
+                :class="[it.active ? 'active' : 'normal']"
+                @click="checkItemFn(it, index)"
+              ></div>
+              <span class="color-[#000] fw-700 line-height-[155px]">{{ chengList[index] }}.</span
+              ><span class="color-[#333333] line-height-[155px] fw-700 mr-[60px]"> {{ it.name }}</span>
+            </div>
+            <!--            </div>-->
+            <div
+              class="question-imgStart w-[290px] h-[90px] text-[186px] absolute bottom-[-170px] left-[405px]"
+              @click="handleClick(it, index)"
+            ></div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!--  <div class="dialog-footer text-right absolute bottom-[0px] right-[0px]">-->
+  <!--    <el-button type="text" @click="handleSubmit">结束测验</el-button>-->
+  <!--  </div>-->
+  <VoiceImp ref="VoiceImpRef" />
+  <!--  </my-full-screen-dialog>-->
+</template>
+
+<script setup lang="ts">
+/*
+ * 组件名: wordWord
+ * 组件用途: 字字匹配
+ * 创建日期: 2024/9/9
+ * 编写者: JutarryWu
+ */
+import { getCurrentInstance, ref } from 'vue'
+import { getRandomInt } from '@/utils'
+import AchievementAPI from '@/api/tester/rehabilitation/training/achievement'
+import { RTPlanMgrQuery } from '@/api/tester/rehabilitation/training/plain'
+import { useUserStore } from '@/store'
+import Topics from './topics.json'
+interface LevelData {
+  level: number
+  correct: boolean
+  score: number
+  responseStartTime: number
+  responseEndTime: number
+}
+interface OnceItem {
+  question: string
+  active: boolean
+  questions: string[]
+  choices: string[]
+  answer: string
+}
+const gameData = ref(Topics as OnceItem[][])
+// 当前游戏等级
+let currentLevel = 0
+// 得分列表
+const scoreList = [10, 30, 60, 100, 150, 210]
+
+const chengList = ref(['1', '2', '3', '4'])
+// 响应开始时间戳
+let responseStartTime = 0
+// 游戏收集的数据
+let gameList: LevelData[] = []
+// 游戏开始时间
+let gameStartTime = 0
+let gameStartTime_record = 0
+// 游戏结束时间
+let gameEndTime = 0
+// 最大游戏等级 6
+const maxLevel = 2
+// 当前试次
+let currentNumber = 0
+// 每个等级的最大试次 15
+const maxNumber = 3
+let timerId: any
+const $emits = defineEmits(['close'])
+const openDialogRef = ref()
+const VoiceImpRef = ref()
+
+const handleClose = (done: () => void) => {
+  $emits('close', '')
+}
+
+const instance = getCurrentInstance()
+const planInfo = ref<RTPlanMgrQuery>()
+const userStore = useUserStore()
+// 当前试次的游戏图片数据
+const onceData = ref({} as OnceItem)
+const handleClick = (answer: string) => {
+  console.log(answer)
+  const correct = false
+  // const correct = onceData.value.answer === answer
+  // VoiceImpRef.value.videoPlay('click')
+  // setTimeout(() => {
+  //   if (correct) {
+  //     console.log(VoiceImpRef.value, 'hbbbbbbbbbbbbbbbbbbbbbbbbbdf=============>')
+  //     if (VoiceImpRef.value) {
+  //       VoiceImpRef.value.videoPlay('right')
+  //     }
+  //   } else {
+  //     if (VoiceImpRef.value) {
+  //       VoiceImpRef.value.videoPlay('error')
+  //     }
+  //   }
+  // }, 200)
+  // 收集当前试次的用户操作数据
+  gameList.push({
+    level: currentLevel,
+    correct: correct,
+    score: correct ? scoreList[currentLevel] : 0,
+    responseEndTime: performance.now(),
+    responseStartTime: responseStartTime
+  })
+  console.log(gameList)
+
+  // 开始下一次
+  nextOnce()
+}
+const checkItemFn = (item: any, index: number) => {
+  // if (modeSelect.value === 0) {
+  onceData.value.choices.forEach((item2) => {
+    item2.active = false
+  })
+  //   VoiceImpRef.value.videoPlay()
+  console.log(item)
+  item.active = !item.active
+  //   itemActive.value = item.key
+  // }
+}
+const nextOnce = () => {
+  console.log(onceData.value.choices, 'onceData392')
+  gameEndTime = performance.now()
+  const gameDuration = gameEndTime - gameStartTime
+  console.log(gameDuration, 'gameDuration')
+  console.log(gameEndTime, 'gameEndTime')
+  console.log(gameStartTime, 'gameStartTime')
+  const currentLevelList = gameList.filter((it) => it.level === currentLevel)
+  // 当前等级进行 10 个试次以上时,计算正确率
+  if (currentLevelList.length >= 3) {
+    // 当前等级正确试次个数
+    const count = currentLevelList.reduce((acc, curr) => {
+      if (curr.correct) {
+        acc++
+      }
+      return acc
+    }, 0)
+    // 当前等级正确率
+    const accuracy = (count / currentLevelList.length) * 100
+    // 升降级规则:正确率达到80%难度升级,低于40%降级
+    if (accuracy >= 80) {
+      if (currentLevel < maxLevel) {
+        currentLevel++
+        // 删除升级之后的已有数据
+        gameList = gameList.filter((it) => it.level !== currentLevel)
+        // 重置当前等级试次索引
+        currentNumber = 0
+        console.log('80%难度升级')
+      }
+    } else if (accuracy < 40) {
+      sendData()
+      return
+    }
+  }
+
+  // 如果当前等级的试次小于最大试次,则试次加一,否则应该进入下一等级
+  if (currentNumber < maxNumber) {
+    currentNumber++
+  } else {
+    // 如果当前等级小于最大等级,则等级加一,否则游戏结束
+    if (currentLevel < maxLevel) {
+      currentLevel++
+      // 删除升级之后的已有数据
+      gameList = gameList.filter((it) => it.level !== currentLevel)
+      // 重置当前等级试次索引
+      currentNumber = 0
+    } else {
+      // 游戏结束
+      console.log('游戏结束2')
+      sendData()
+      return
+    }
+  }
+
+  // 根据当前等级和当前试次改变游戏素材
+  setOnceData()
+}
+// 发送请求
+const sendData = () => {
+  if (planInfo.value) {
+    // 计算正确反应数和总反应时
+    let { correctCount, totalResponseTime, totalScore } = gameList.reduce(
+      (obj, curr) => {
+        if (curr.correct) {
+          obj.correctCount++
+        }
+        obj.totalScore += curr.score
+        obj.totalResponseTime += curr.responseEndTime - curr.responseStartTime
+        return obj
+      },
+      { correctCount: 0, totalResponseTime: 0, totalScore: 0 }
+    )
+
+    // 平均反应时 = 总反应时 / 总试次
+    const avrResponseTime = `${(totalResponseTime / gameList.length).toFixed(2)}ms`
+
+    // 如果在60秒内猜中50个物品得30分
+    if (correctCount >= 50) {
+      totalScore += 50
+    }
+
+    const { gameId, gameName, id: planId } = planInfo.value
+    const data = {
+      finish: '1',
+      gameId,
+      gameName,
+      paramList: [
+        {
+          code: 'score',
+          name: '得分',
+          value: totalScore
+        },
+        {
+          code: 'correctCount',
+          name: '正确反应数',
+          value: correctCount
+        },
+        {
+          code: 'avrResponseTime',
+          name: '平均反应时',
+          value: avrResponseTime
+        },
+        {
+          code: 'avrResponseTime',
+          name: '最大难度',
+          value: currentLevel + 1
+        },
+        {
+          code: 'gameTotalTime',
+          name: '游戏总时长',
+          value: Date.now() - gameStartTime_record / 1000
+        }
+      ],
+      planId,
+      gamelevel: currentLevel + 1,
+      score: totalScore,
+      userId: userStore.user.id
+    }
+    console.log('发送数据', data)
+
+    AchievementAPI.add(data).then(() => {
+      ElMessage.success('本次训练已结束')
+      clearInterval(timerId)
+
+      handleClose(() => {})
+
+      instance?.proxy?.$Bus.emit('trainList-refresh')
+    })
+  } else {
+    ElMessage.success('本次训练已结束')
+    clearInterval(timerId)
+
+    handleClose(() => {})
+    instance?.proxy?.$Bus.emit('trainList-refresh')
+  }
+}
+async function exec() {
+  // openDialogRef.value.openDialog()
+  const tempPlan = sessionStorage.getItem('currentPlanInfo')
+  if (tempPlan) {
+    planInfo.value = JSON.parse(tempPlan)
+  }
+  await nextTick(() => {
+    // 记录游戏开始时间戳
+    gameStartTime = performance.now()
+    gameStartTime_record = Date.now()
+    console.log(gameStartTime_record, '总开始时间')
+    // 重置游戏结束时间戳
+    gameEndTime = 0
+    // 重置当前等级
+    currentLevel = 0
+    // 重置当前试次
+    currentNumber = 0
+    // 清空游戏数据
+    gameList = []
+    // 设置第一个试次的游戏素材
+    setOnceData()
+  })
+}
+const setOnceData = () => {
+  // currentLevel
+  // currentNumber
+  // gameMode
+  // 记录响应开始时间
+  responseStartTime = performance.now()
+  onceData.value = gameData.value[currentLevel][currentNumber]
+  console.log(currentLevel, currentNumber, '1.2')
+  onceData.value.choices = shuffleArray(onceData.value.choices)
+}
+// 随机排列数组
+function shuffleArray(array: any) {
+  const newArray = [...array]
+  for (let i = newArray.length - 1; i > 0; i--) {
+    const j = Math.floor(Math.random() * (i + 1))
+    ;[newArray[i], newArray[j]] = [newArray[j], newArray[i]]
+  }
+  return newArray
+}
+onMounted(() => {
+  exec()
+})
+</script>
+
+<style scoped lang="scss">
+.adl-container {
+  background-size: 100% 100%;
+  background-position: center center;
+  background-repeat: repeat;
+  .question-img {
+    background-image: url('/static/image/cognitiveAbility/SpeechTraining/Visual/title.png');
+    background-size: 100% 100%;
+    background-position: center center;
+    background-repeat: repeat;
+    //font-family: 楷体;
+    text-align: center;
+  }
+  .question-imgStart {
+    background-image: url('/static/image/cognitiveAbility/SpeechTraining/Visual/verify-bg.png');
+    background-size: 100% 100%;
+    background-position: center center;
+    background-repeat: repeat;
+    //font-family: 楷体;
+    text-align: center;
+  }
+  .active {
+    background: url('/static/image/cognitiveAbility/SpeechTraining/Visual/Options-right.png') no-repeat 0 0 / 100% 100%;
+    //background-position: 90% 54%; /* 可选,让图片居中对齐 */
+  }
+  .normal {
+    background: url('/static/image/cognitiveAbility/SpeechTraining/Visual/Options-Blank.png') no-repeat 0 0 / 70% 70%;
+    background-position: 10% 90%; /* 可选,让图片居中对齐 */
+  }
+}
+</style>

+ 56 - 0
src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/essay/topics.json

@@ -0,0 +1,56 @@
+[
+  [
+    {
+      "question": "手",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"手",
+          "active": false
+        }, {
+          "name":"脚",
+          "active": false
+        }],
+      "answer": "手"
+    },
+    {
+      "question": "梨",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"柚",
+          "active": false
+        }, {
+          "name":"香",
+          "active": false
+        }, {
+          "name":"梨",
+          "active": false
+        }],
+      "answer": "梨"
+    },
+    {
+      "question": "纸",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"笔",
+          "active": false
+        }, {
+          "name":"纸",
+          "active": false
+        }, {
+          "name":"本",
+          "active": false
+        }, {
+          "name":"书",
+          "active": false
+        }],
+      "answer": "纸"
+    }
+  ]
+]
+

+ 366 - 0
src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/execute/index.vue

@@ -0,0 +1,366 @@
+<template>
+  <!--  <my-full-screen-dialog ref="openDialogRef" close-color="#0F4DD8" @close-dialog="handleClose">-->
+  <div class="adl-container w-full h-full flex-center text-[#134FA4]">
+    <div class="center-area w-[90%] h-[90%]">
+      <div
+        class="child-container bg-[#ffffff] bor-radius-[10] w-[1360px] h-[860px] mt-[100px] flex flex-col justify-center items-center gap-y-[30px] absolute left-[500px]"
+      >
+        <div class="bg-1">
+          <div class="bg-1 text-[68px] w-[80%] absolute top-[30px] left-[550px] color-[#333333]">
+            <div class="question-img w-[290px] h-[290px] text-[186px]">
+              {{ onceData.question }}
+            </div>
+          </div>
+        </div>
+        <div
+          id="myButton"
+          class="divMain absolute left-[165px] top-[305px] flex flex-wrap flex-row gap-x-[20px] mt-[50px] w-[1100px]"
+          :class="onceData.choices && onceData.choices.length < 3 ? 'flex-center' : ''"
+        >
+          <div v-for="(it, index) in onceData.choices" :key="it">
+            <div class="text-[80px] w-[350px] flex flex-wrap flex-row justify-between">
+              <!--              <div class="w-[450px] sab ml-[115px] mt-[30px] flex flex-wrap flex-row justify-between">-->
+              <div
+                class="bg-[#000] w-[120px] h-[120px] flex-center"
+                :class="[it.active ? 'active' : 'normal']"
+                @click="checkItemFn(it, index)"
+              ></div>
+              <span class="color-[#000] fw-700 line-height-[155px]">{{ chengList[index] }}.</span
+              ><span class="color-[#333333] line-height-[155px] fw-700 mr-[60px]"> {{ it.name }}</span>
+            </div>
+            <!--            </div>-->
+            <div
+              class="question-imgStart w-[290px] h-[90px] text-[186px] absolute bottom-[-170px] left-[405px]"
+              @click="handleClick(it, index)"
+            ></div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!--  <div class="dialog-footer text-right absolute bottom-[0px] right-[0px]">-->
+  <!--    <el-button type="text" @click="handleSubmit">结束测验</el-button>-->
+  <!--  </div>-->
+  <VoiceImp ref="VoiceImpRef" />
+  <!--  </my-full-screen-dialog>-->
+</template>
+
+<script setup lang="ts">
+/*
+ * 组件名: wordWord
+ * 组件用途: 字字匹配
+ * 创建日期: 2024/9/9
+ * 编写者: JutarryWu
+ */
+import { getCurrentInstance, ref } from 'vue'
+import { getRandomInt } from '@/utils'
+import AchievementAPI from '@/api/tester/rehabilitation/training/achievement'
+import { RTPlanMgrQuery } from '@/api/tester/rehabilitation/training/plain'
+import { useUserStore } from '@/store'
+import Topics from './topics.json'
+interface LevelData {
+  level: number
+  correct: boolean
+  score: number
+  responseStartTime: number
+  responseEndTime: number
+}
+interface OnceItem {
+  question: string
+  active: boolean
+  questions: string[]
+  choices: string[]
+  answer: string
+}
+const gameData = ref(Topics as OnceItem[][])
+// 当前游戏等级
+let currentLevel = 0
+// 得分列表
+const scoreList = [10, 30, 60, 100, 150, 210]
+
+const chengList = ref(['1', '2', '3', '4'])
+// 响应开始时间戳
+let responseStartTime = 0
+// 游戏收集的数据
+let gameList: LevelData[] = []
+// 游戏开始时间
+let gameStartTime = 0
+let gameStartTime_record = 0
+// 游戏结束时间
+let gameEndTime = 0
+// 最大游戏等级 6
+const maxLevel = 2
+// 当前试次
+let currentNumber = 0
+// 每个等级的最大试次 15
+const maxNumber = 3
+let timerId: any
+const $emits = defineEmits(['close'])
+const openDialogRef = ref()
+const VoiceImpRef = ref()
+
+const handleClose = (done: () => void) => {
+  $emits('close', '')
+}
+
+const instance = getCurrentInstance()
+const planInfo = ref<RTPlanMgrQuery>()
+const userStore = useUserStore()
+// 当前试次的游戏图片数据
+const onceData = ref({} as OnceItem)
+const handleClick = (answer: string) => {
+  console.log(answer)
+  const correct = false
+  // const correct = onceData.value.answer === answer
+  // VoiceImpRef.value.videoPlay('click')
+  // setTimeout(() => {
+  //   if (correct) {
+  //     console.log(VoiceImpRef.value, 'hbbbbbbbbbbbbbbbbbbbbbbbbbdf=============>')
+  //     if (VoiceImpRef.value) {
+  //       VoiceImpRef.value.videoPlay('right')
+  //     }
+  //   } else {
+  //     if (VoiceImpRef.value) {
+  //       VoiceImpRef.value.videoPlay('error')
+  //     }
+  //   }
+  // }, 200)
+  // 收集当前试次的用户操作数据
+  gameList.push({
+    level: currentLevel,
+    correct: correct,
+    score: correct ? scoreList[currentLevel] : 0,
+    responseEndTime: performance.now(),
+    responseStartTime: responseStartTime
+  })
+  console.log(gameList)
+
+  // 开始下一次
+  nextOnce()
+}
+const checkItemFn = (item: any, index: number) => {
+  // if (modeSelect.value === 0) {
+  onceData.value.choices.forEach((item2) => {
+    item2.active = false
+  })
+  //   VoiceImpRef.value.videoPlay()
+  console.log(item)
+  item.active = !item.active
+  //   itemActive.value = item.key
+  // }
+}
+const nextOnce = () => {
+  console.log(onceData.value.choices, 'onceData392')
+  gameEndTime = performance.now()
+  const gameDuration = gameEndTime - gameStartTime
+  console.log(gameDuration, 'gameDuration')
+  console.log(gameEndTime, 'gameEndTime')
+  console.log(gameStartTime, 'gameStartTime')
+  const currentLevelList = gameList.filter((it) => it.level === currentLevel)
+  // 当前等级进行 10 个试次以上时,计算正确率
+  if (currentLevelList.length >= 3) {
+    // 当前等级正确试次个数
+    const count = currentLevelList.reduce((acc, curr) => {
+      if (curr.correct) {
+        acc++
+      }
+      return acc
+    }, 0)
+    // 当前等级正确率
+    const accuracy = (count / currentLevelList.length) * 100
+    // 升降级规则:正确率达到80%难度升级,低于40%降级
+    if (accuracy >= 80) {
+      if (currentLevel < maxLevel) {
+        currentLevel++
+        // 删除升级之后的已有数据
+        gameList = gameList.filter((it) => it.level !== currentLevel)
+        // 重置当前等级试次索引
+        currentNumber = 0
+        console.log('80%难度升级')
+      }
+    } else if (accuracy < 40) {
+      sendData()
+      return
+    }
+  }
+
+  // 如果当前等级的试次小于最大试次,则试次加一,否则应该进入下一等级
+  if (currentNumber < maxNumber) {
+    currentNumber++
+  } else {
+    // 如果当前等级小于最大等级,则等级加一,否则游戏结束
+    if (currentLevel < maxLevel) {
+      currentLevel++
+      // 删除升级之后的已有数据
+      gameList = gameList.filter((it) => it.level !== currentLevel)
+      // 重置当前等级试次索引
+      currentNumber = 0
+    } else {
+      // 游戏结束
+      console.log('游戏结束2')
+      sendData()
+      return
+    }
+  }
+
+  // 根据当前等级和当前试次改变游戏素材
+  setOnceData()
+}
+// 发送请求
+const sendData = () => {
+  if (planInfo.value) {
+    // 计算正确反应数和总反应时
+    let { correctCount, totalResponseTime, totalScore } = gameList.reduce(
+      (obj, curr) => {
+        if (curr.correct) {
+          obj.correctCount++
+        }
+        obj.totalScore += curr.score
+        obj.totalResponseTime += curr.responseEndTime - curr.responseStartTime
+        return obj
+      },
+      { correctCount: 0, totalResponseTime: 0, totalScore: 0 }
+    )
+
+    // 平均反应时 = 总反应时 / 总试次
+    const avrResponseTime = `${(totalResponseTime / gameList.length).toFixed(2)}ms`
+
+    // 如果在60秒内猜中50个物品得30分
+    if (correctCount >= 50) {
+      totalScore += 50
+    }
+
+    const { gameId, gameName, id: planId } = planInfo.value
+    const data = {
+      finish: '1',
+      gameId,
+      gameName,
+      paramList: [
+        {
+          code: 'score',
+          name: '得分',
+          value: totalScore
+        },
+        {
+          code: 'correctCount',
+          name: '正确反应数',
+          value: correctCount
+        },
+        {
+          code: 'avrResponseTime',
+          name: '平均反应时',
+          value: avrResponseTime
+        },
+        {
+          code: 'avrResponseTime',
+          name: '最大难度',
+          value: currentLevel + 1
+        },
+        {
+          code: 'gameTotalTime',
+          name: '游戏总时长',
+          value: Date.now() - gameStartTime_record / 1000
+        }
+      ],
+      planId,
+      gamelevel: currentLevel + 1,
+      score: totalScore,
+      userId: userStore.user.id
+    }
+    console.log('发送数据', data)
+
+    AchievementAPI.add(data).then(() => {
+      ElMessage.success('本次训练已结束')
+      clearInterval(timerId)
+
+      handleClose(() => {})
+
+      instance?.proxy?.$Bus.emit('trainList-refresh')
+    })
+  } else {
+    ElMessage.success('本次训练已结束')
+    clearInterval(timerId)
+
+    handleClose(() => {})
+    instance?.proxy?.$Bus.emit('trainList-refresh')
+  }
+}
+async function exec() {
+  // openDialogRef.value.openDialog()
+  const tempPlan = sessionStorage.getItem('currentPlanInfo')
+  if (tempPlan) {
+    planInfo.value = JSON.parse(tempPlan)
+  }
+  await nextTick(() => {
+    // 记录游戏开始时间戳
+    gameStartTime = performance.now()
+    gameStartTime_record = Date.now()
+    console.log(gameStartTime_record, '总开始时间')
+    // 重置游戏结束时间戳
+    gameEndTime = 0
+    // 重置当前等级
+    currentLevel = 0
+    // 重置当前试次
+    currentNumber = 0
+    // 清空游戏数据
+    gameList = []
+    // 设置第一个试次的游戏素材
+    setOnceData()
+  })
+}
+const setOnceData = () => {
+  // currentLevel
+  // currentNumber
+  // gameMode
+  // 记录响应开始时间
+  responseStartTime = performance.now()
+  onceData.value = gameData.value[currentLevel][currentNumber]
+  console.log(currentLevel, currentNumber, '1.2')
+  onceData.value.choices = shuffleArray(onceData.value.choices)
+}
+// 随机排列数组
+function shuffleArray(array: any) {
+  const newArray = [...array]
+  for (let i = newArray.length - 1; i > 0; i--) {
+    const j = Math.floor(Math.random() * (i + 1))
+    ;[newArray[i], newArray[j]] = [newArray[j], newArray[i]]
+  }
+  return newArray
+}
+onMounted(() => {
+  exec()
+})
+</script>
+
+<style scoped lang="scss">
+.adl-container {
+  background-size: 100% 100%;
+  background-position: center center;
+  background-repeat: repeat;
+  .question-img {
+    background-image: url('/static/image/cognitiveAbility/SpeechTraining/Visual/title.png');
+    background-size: 100% 100%;
+    background-position: center center;
+    background-repeat: repeat;
+    //font-family: 楷体;
+    text-align: center;
+  }
+  .question-imgStart {
+    background-image: url('/static/image/cognitiveAbility/SpeechTraining/Visual/verify-bg.png');
+    background-size: 100% 100%;
+    background-position: center center;
+    background-repeat: repeat;
+    //font-family: 楷体;
+    text-align: center;
+  }
+  .active {
+    background: url('/static/image/cognitiveAbility/SpeechTraining/Visual/Options-right.png') no-repeat 0 0 / 100% 100%;
+    //background-position: 90% 54%; /* 可选,让图片居中对齐 */
+  }
+  .normal {
+    background: url('/static/image/cognitiveAbility/SpeechTraining/Visual/Options-Blank.png') no-repeat 0 0 / 70% 70%;
+    background-position: 10% 90%; /* 可选,让图片居中对齐 */
+  }
+}
+</style>

+ 56 - 0
src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/execute/topics.json

@@ -0,0 +1,56 @@
+[
+  [
+    {
+      "question": "手",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"手",
+          "active": false
+        }, {
+          "name":"脚",
+          "active": false
+        }],
+      "answer": "手"
+    },
+    {
+      "question": "梨",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"柚",
+          "active": false
+        }, {
+          "name":"香",
+          "active": false
+        }, {
+          "name":"梨",
+          "active": false
+        }],
+      "answer": "梨"
+    },
+    {
+      "question": "纸",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"笔",
+          "active": false
+        }, {
+          "name":"纸",
+          "active": false
+        }, {
+          "name":"本",
+          "active": false
+        }, {
+          "name":"书",
+          "active": false
+        }],
+      "answer": "纸"
+    }
+  ]
+]
+

+ 366 - 0
src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/mapWord/index.vue

@@ -0,0 +1,366 @@
+<template>
+  <!--  <my-full-screen-dialog ref="openDialogRef" close-color="#0F4DD8" @close-dialog="handleClose">-->
+  <div class="adl-container w-full h-full flex-center text-[#134FA4]">
+    <div class="center-area w-[90%] h-[90%]">
+      <div
+        class="child-container bg-[#ffffff] bor-radius-[10] w-[1360px] h-[860px] mt-[100px] flex flex-col justify-center items-center gap-y-[30px] absolute left-[500px]"
+      >
+        <div class="bg-1">
+          <div class="bg-1 text-[68px] w-[80%] absolute top-[30px] left-[550px] color-[#333333]">
+            <div class="question-img w-[290px] h-[290px] text-[186px]">
+              {{ onceData.question }}
+            </div>
+          </div>
+        </div>
+        <div
+          id="myButton"
+          class="divMain absolute left-[165px] top-[305px] flex flex-wrap flex-row gap-x-[20px] mt-[50px] w-[1100px]"
+          :class="onceData.choices && onceData.choices.length < 3 ? 'flex-center' : ''"
+        >
+          <div v-for="(it, index) in onceData.choices" :key="it">
+            <div class="text-[80px] w-[350px] flex flex-wrap flex-row justify-between">
+              <!--              <div class="w-[450px] sab ml-[115px] mt-[30px] flex flex-wrap flex-row justify-between">-->
+              <div
+                class="bg-[#000] w-[120px] h-[120px] flex-center"
+                :class="[it.active ? 'active' : 'normal']"
+                @click="checkItemFn(it, index)"
+              ></div>
+              <span class="color-[#000] fw-700 line-height-[155px]">{{ chengList[index] }}.</span
+              ><span class="color-[#333333] line-height-[155px] fw-700 mr-[60px]"> {{ it.name }}</span>
+            </div>
+            <!--            </div>-->
+            <div
+              class="question-imgStart w-[290px] h-[90px] text-[186px] absolute bottom-[-170px] left-[405px]"
+              @click="handleClick(it, index)"
+            ></div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!--  <div class="dialog-footer text-right absolute bottom-[0px] right-[0px]">-->
+  <!--    <el-button type="text" @click="handleSubmit">结束测验</el-button>-->
+  <!--  </div>-->
+  <VoiceImp ref="VoiceImpRef" />
+  <!--  </my-full-screen-dialog>-->
+</template>
+
+<script setup lang="ts">
+/*
+ * 组件名: wordWord
+ * 组件用途: 字字匹配
+ * 创建日期: 2024/9/9
+ * 编写者: JutarryWu
+ */
+import { getCurrentInstance, ref } from 'vue'
+import { getRandomInt } from '@/utils'
+import AchievementAPI from '@/api/tester/rehabilitation/training/achievement'
+import { RTPlanMgrQuery } from '@/api/tester/rehabilitation/training/plain'
+import { useUserStore } from '@/store'
+import Topics from './topics.json'
+interface LevelData {
+  level: number
+  correct: boolean
+  score: number
+  responseStartTime: number
+  responseEndTime: number
+}
+interface OnceItem {
+  question: string
+  active: boolean
+  questions: string[]
+  choices: string[]
+  answer: string
+}
+const gameData = ref(Topics as OnceItem[][])
+// 当前游戏等级
+let currentLevel = 0
+// 得分列表
+const scoreList = [10, 30, 60, 100, 150, 210]
+
+const chengList = ref(['1', '2', '3', '4'])
+// 响应开始时间戳
+let responseStartTime = 0
+// 游戏收集的数据
+let gameList: LevelData[] = []
+// 游戏开始时间
+let gameStartTime = 0
+let gameStartTime_record = 0
+// 游戏结束时间
+let gameEndTime = 0
+// 最大游戏等级 6
+const maxLevel = 2
+// 当前试次
+let currentNumber = 0
+// 每个等级的最大试次 15
+const maxNumber = 3
+let timerId: any
+const $emits = defineEmits(['close'])
+const openDialogRef = ref()
+const VoiceImpRef = ref()
+
+const handleClose = (done: () => void) => {
+  $emits('close', '')
+}
+
+const instance = getCurrentInstance()
+const planInfo = ref<RTPlanMgrQuery>()
+const userStore = useUserStore()
+// 当前试次的游戏图片数据
+const onceData = ref({} as OnceItem)
+const handleClick = (answer: string) => {
+  console.log(answer)
+  const correct = false
+  // const correct = onceData.value.answer === answer
+  // VoiceImpRef.value.videoPlay('click')
+  // setTimeout(() => {
+  //   if (correct) {
+  //     console.log(VoiceImpRef.value, 'hbbbbbbbbbbbbbbbbbbbbbbbbbdf=============>')
+  //     if (VoiceImpRef.value) {
+  //       VoiceImpRef.value.videoPlay('right')
+  //     }
+  //   } else {
+  //     if (VoiceImpRef.value) {
+  //       VoiceImpRef.value.videoPlay('error')
+  //     }
+  //   }
+  // }, 200)
+  // 收集当前试次的用户操作数据
+  gameList.push({
+    level: currentLevel,
+    correct: correct,
+    score: correct ? scoreList[currentLevel] : 0,
+    responseEndTime: performance.now(),
+    responseStartTime: responseStartTime
+  })
+  console.log(gameList)
+
+  // 开始下一次
+  nextOnce()
+}
+const checkItemFn = (item: any, index: number) => {
+  // if (modeSelect.value === 0) {
+  onceData.value.choices.forEach((item2) => {
+    item2.active = false
+  })
+  //   VoiceImpRef.value.videoPlay()
+  console.log(item)
+  item.active = !item.active
+  //   itemActive.value = item.key
+  // }
+}
+const nextOnce = () => {
+  console.log(onceData.value.choices, 'onceData392')
+  gameEndTime = performance.now()
+  const gameDuration = gameEndTime - gameStartTime
+  console.log(gameDuration, 'gameDuration')
+  console.log(gameEndTime, 'gameEndTime')
+  console.log(gameStartTime, 'gameStartTime')
+  const currentLevelList = gameList.filter((it) => it.level === currentLevel)
+  // 当前等级进行 10 个试次以上时,计算正确率
+  if (currentLevelList.length >= 3) {
+    // 当前等级正确试次个数
+    const count = currentLevelList.reduce((acc, curr) => {
+      if (curr.correct) {
+        acc++
+      }
+      return acc
+    }, 0)
+    // 当前等级正确率
+    const accuracy = (count / currentLevelList.length) * 100
+    // 升降级规则:正确率达到80%难度升级,低于40%降级
+    if (accuracy >= 80) {
+      if (currentLevel < maxLevel) {
+        currentLevel++
+        // 删除升级之后的已有数据
+        gameList = gameList.filter((it) => it.level !== currentLevel)
+        // 重置当前等级试次索引
+        currentNumber = 0
+        console.log('80%难度升级')
+      }
+    } else if (accuracy < 40) {
+      sendData()
+      return
+    }
+  }
+
+  // 如果当前等级的试次小于最大试次,则试次加一,否则应该进入下一等级
+  if (currentNumber < maxNumber) {
+    currentNumber++
+  } else {
+    // 如果当前等级小于最大等级,则等级加一,否则游戏结束
+    if (currentLevel < maxLevel) {
+      currentLevel++
+      // 删除升级之后的已有数据
+      gameList = gameList.filter((it) => it.level !== currentLevel)
+      // 重置当前等级试次索引
+      currentNumber = 0
+    } else {
+      // 游戏结束
+      console.log('游戏结束2')
+      sendData()
+      return
+    }
+  }
+
+  // 根据当前等级和当前试次改变游戏素材
+  setOnceData()
+}
+// 发送请求
+const sendData = () => {
+  if (planInfo.value) {
+    // 计算正确反应数和总反应时
+    let { correctCount, totalResponseTime, totalScore } = gameList.reduce(
+      (obj, curr) => {
+        if (curr.correct) {
+          obj.correctCount++
+        }
+        obj.totalScore += curr.score
+        obj.totalResponseTime += curr.responseEndTime - curr.responseStartTime
+        return obj
+      },
+      { correctCount: 0, totalResponseTime: 0, totalScore: 0 }
+    )
+
+    // 平均反应时 = 总反应时 / 总试次
+    const avrResponseTime = `${(totalResponseTime / gameList.length).toFixed(2)}ms`
+
+    // 如果在60秒内猜中50个物品得30分
+    if (correctCount >= 50) {
+      totalScore += 50
+    }
+
+    const { gameId, gameName, id: planId } = planInfo.value
+    const data = {
+      finish: '1',
+      gameId,
+      gameName,
+      paramList: [
+        {
+          code: 'score',
+          name: '得分',
+          value: totalScore
+        },
+        {
+          code: 'correctCount',
+          name: '正确反应数',
+          value: correctCount
+        },
+        {
+          code: 'avrResponseTime',
+          name: '平均反应时',
+          value: avrResponseTime
+        },
+        {
+          code: 'avrResponseTime',
+          name: '最大难度',
+          value: currentLevel + 1
+        },
+        {
+          code: 'gameTotalTime',
+          name: '游戏总时长',
+          value: Date.now() - gameStartTime_record / 1000
+        }
+      ],
+      planId,
+      gamelevel: currentLevel + 1,
+      score: totalScore,
+      userId: userStore.user.id
+    }
+    console.log('发送数据', data)
+
+    AchievementAPI.add(data).then(() => {
+      ElMessage.success('本次训练已结束')
+      clearInterval(timerId)
+
+      handleClose(() => {})
+
+      instance?.proxy?.$Bus.emit('trainList-refresh')
+    })
+  } else {
+    ElMessage.success('本次训练已结束')
+    clearInterval(timerId)
+
+    handleClose(() => {})
+    instance?.proxy?.$Bus.emit('trainList-refresh')
+  }
+}
+async function exec() {
+  // openDialogRef.value.openDialog()
+  const tempPlan = sessionStorage.getItem('currentPlanInfo')
+  if (tempPlan) {
+    planInfo.value = JSON.parse(tempPlan)
+  }
+  await nextTick(() => {
+    // 记录游戏开始时间戳
+    gameStartTime = performance.now()
+    gameStartTime_record = Date.now()
+    console.log(gameStartTime_record, '总开始时间')
+    // 重置游戏结束时间戳
+    gameEndTime = 0
+    // 重置当前等级
+    currentLevel = 0
+    // 重置当前试次
+    currentNumber = 0
+    // 清空游戏数据
+    gameList = []
+    // 设置第一个试次的游戏素材
+    setOnceData()
+  })
+}
+const setOnceData = () => {
+  // currentLevel
+  // currentNumber
+  // gameMode
+  // 记录响应开始时间
+  responseStartTime = performance.now()
+  onceData.value = gameData.value[currentLevel][currentNumber]
+  console.log(currentLevel, currentNumber, '1.2')
+  onceData.value.choices = shuffleArray(onceData.value.choices)
+}
+// 随机排列数组
+function shuffleArray(array: any) {
+  const newArray = [...array]
+  for (let i = newArray.length - 1; i > 0; i--) {
+    const j = Math.floor(Math.random() * (i + 1))
+    ;[newArray[i], newArray[j]] = [newArray[j], newArray[i]]
+  }
+  return newArray
+}
+onMounted(() => {
+  exec()
+})
+</script>
+
+<style scoped lang="scss">
+.adl-container {
+  background-size: 100% 100%;
+  background-position: center center;
+  background-repeat: repeat;
+  .question-img {
+    background-image: url('/static/image/cognitiveAbility/SpeechTraining/Visual/title.png');
+    background-size: 100% 100%;
+    background-position: center center;
+    background-repeat: repeat;
+    //font-family: 楷体;
+    text-align: center;
+  }
+  .question-imgStart {
+    background-image: url('/static/image/cognitiveAbility/SpeechTraining/Visual/verify-bg.png');
+    background-size: 100% 100%;
+    background-position: center center;
+    background-repeat: repeat;
+    //font-family: 楷体;
+    text-align: center;
+  }
+  .active {
+    background: url('/static/image/cognitiveAbility/SpeechTraining/Visual/Options-right.png') no-repeat 0 0 / 100% 100%;
+    //background-position: 90% 54%; /* 可选,让图片居中对齐 */
+  }
+  .normal {
+    background: url('/static/image/cognitiveAbility/SpeechTraining/Visual/Options-Blank.png') no-repeat 0 0 / 70% 70%;
+    background-position: 10% 90%; /* 可选,让图片居中对齐 */
+  }
+}
+</style>

+ 56 - 0
src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/mapWord/topics.json

@@ -0,0 +1,56 @@
+[
+  [
+    {
+      "question": "手",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"手",
+          "active": false
+        }, {
+          "name":"脚",
+          "active": false
+        }],
+      "answer": "手"
+    },
+    {
+      "question": "梨",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"柚",
+          "active": false
+        }, {
+          "name":"香",
+          "active": false
+        }, {
+          "name":"梨",
+          "active": false
+        }],
+      "answer": "梨"
+    },
+    {
+      "question": "纸",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"笔",
+          "active": false
+        }, {
+          "name":"纸",
+          "active": false
+        }, {
+          "name":"本",
+          "active": false
+        }, {
+          "name":"书",
+          "active": false
+        }],
+      "answer": "纸"
+    }
+  ]
+]
+

+ 366 - 0
src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/wordMap/index.vue

@@ -0,0 +1,366 @@
+<template>
+  <!--  <my-full-screen-dialog ref="openDialogRef" close-color="#0F4DD8" @close-dialog="handleClose">-->
+  <div class="adl-container w-full h-full flex-center text-[#134FA4]">
+    <div class="center-area w-[90%] h-[90%]">
+      <div
+        class="child-container bg-[#ffffff] bor-radius-[10] w-[1360px] h-[860px] mt-[100px] flex flex-col justify-center items-center gap-y-[30px] absolute left-[500px]"
+      >
+        <div class="bg-1">
+          <div class="bg-1 text-[68px] w-[80%] absolute top-[30px] left-[550px] color-[#333333]">
+            <div class="question-img w-[290px] h-[290px] text-[186px]">
+              {{ onceData.question }}
+            </div>
+          </div>
+        </div>
+        <div
+          id="myButton"
+          class="divMain absolute left-[165px] top-[305px] flex flex-wrap flex-row gap-x-[20px] mt-[50px] w-[1100px]"
+          :class="onceData.choices && onceData.choices.length < 3 ? 'flex-center' : ''"
+        >
+          <div v-for="(it, index) in onceData.choices" :key="it">
+            <div class="text-[80px] w-[350px] flex flex-wrap flex-row justify-between">
+              <!--              <div class="w-[450px] sab ml-[115px] mt-[30px] flex flex-wrap flex-row justify-between">-->
+              <div
+                class="bg-[#000] w-[120px] h-[120px] flex-center"
+                :class="[it.active ? 'active' : 'normal']"
+                @click="checkItemFn(it, index)"
+              ></div>
+              <span class="color-[#000] fw-700 line-height-[155px]">{{ chengList[index] }}.</span
+              ><span class="color-[#333333] line-height-[155px] fw-700 mr-[60px]"> {{ it.name }}</span>
+            </div>
+            <!--            </div>-->
+            <div
+              class="question-imgStart w-[290px] h-[90px] text-[186px] absolute bottom-[-170px] left-[405px]"
+              @click="handleClick(it, index)"
+            ></div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!--  <div class="dialog-footer text-right absolute bottom-[0px] right-[0px]">-->
+  <!--    <el-button type="text" @click="handleSubmit">结束测验</el-button>-->
+  <!--  </div>-->
+  <VoiceImp ref="VoiceImpRef" />
+  <!--  </my-full-screen-dialog>-->
+</template>
+
+<script setup lang="ts">
+/*
+ * 组件名: wordWord
+ * 组件用途: 字字匹配
+ * 创建日期: 2024/9/9
+ * 编写者: JutarryWu
+ */
+import { getCurrentInstance, ref } from 'vue'
+import { getRandomInt } from '@/utils'
+import AchievementAPI from '@/api/tester/rehabilitation/training/achievement'
+import { RTPlanMgrQuery } from '@/api/tester/rehabilitation/training/plain'
+import { useUserStore } from '@/store'
+import Topics from './topics.json'
+interface LevelData {
+  level: number
+  correct: boolean
+  score: number
+  responseStartTime: number
+  responseEndTime: number
+}
+interface OnceItem {
+  question: string
+  active: boolean
+  questions: string[]
+  choices: string[]
+  answer: string
+}
+const gameData = ref(Topics as OnceItem[][])
+// 当前游戏等级
+let currentLevel = 0
+// 得分列表
+const scoreList = [10, 30, 60, 100, 150, 210]
+
+const chengList = ref(['1', '2', '3', '4'])
+// 响应开始时间戳
+let responseStartTime = 0
+// 游戏收集的数据
+let gameList: LevelData[] = []
+// 游戏开始时间
+let gameStartTime = 0
+let gameStartTime_record = 0
+// 游戏结束时间
+let gameEndTime = 0
+// 最大游戏等级 6
+const maxLevel = 2
+// 当前试次
+let currentNumber = 0
+// 每个等级的最大试次 15
+const maxNumber = 3
+let timerId: any
+const $emits = defineEmits(['close'])
+const openDialogRef = ref()
+const VoiceImpRef = ref()
+
+const handleClose = (done: () => void) => {
+  $emits('close', '')
+}
+
+const instance = getCurrentInstance()
+const planInfo = ref<RTPlanMgrQuery>()
+const userStore = useUserStore()
+// 当前试次的游戏图片数据
+const onceData = ref({} as OnceItem)
+const handleClick = (answer: string) => {
+  console.log(answer)
+  const correct = false
+  // const correct = onceData.value.answer === answer
+  // VoiceImpRef.value.videoPlay('click')
+  // setTimeout(() => {
+  //   if (correct) {
+  //     console.log(VoiceImpRef.value, 'hbbbbbbbbbbbbbbbbbbbbbbbbbdf=============>')
+  //     if (VoiceImpRef.value) {
+  //       VoiceImpRef.value.videoPlay('right')
+  //     }
+  //   } else {
+  //     if (VoiceImpRef.value) {
+  //       VoiceImpRef.value.videoPlay('error')
+  //     }
+  //   }
+  // }, 200)
+  // 收集当前试次的用户操作数据
+  gameList.push({
+    level: currentLevel,
+    correct: correct,
+    score: correct ? scoreList[currentLevel] : 0,
+    responseEndTime: performance.now(),
+    responseStartTime: responseStartTime
+  })
+  console.log(gameList)
+
+  // 开始下一次
+  nextOnce()
+}
+const checkItemFn = (item: any, index: number) => {
+  // if (modeSelect.value === 0) {
+  onceData.value.choices.forEach((item2) => {
+    item2.active = false
+  })
+  //   VoiceImpRef.value.videoPlay()
+  console.log(item)
+  item.active = !item.active
+  //   itemActive.value = item.key
+  // }
+}
+const nextOnce = () => {
+  console.log(onceData.value.choices, 'onceData392')
+  gameEndTime = performance.now()
+  const gameDuration = gameEndTime - gameStartTime
+  console.log(gameDuration, 'gameDuration')
+  console.log(gameEndTime, 'gameEndTime')
+  console.log(gameStartTime, 'gameStartTime')
+  const currentLevelList = gameList.filter((it) => it.level === currentLevel)
+  // 当前等级进行 10 个试次以上时,计算正确率
+  if (currentLevelList.length >= 3) {
+    // 当前等级正确试次个数
+    const count = currentLevelList.reduce((acc, curr) => {
+      if (curr.correct) {
+        acc++
+      }
+      return acc
+    }, 0)
+    // 当前等级正确率
+    const accuracy = (count / currentLevelList.length) * 100
+    // 升降级规则:正确率达到80%难度升级,低于40%降级
+    if (accuracy >= 80) {
+      if (currentLevel < maxLevel) {
+        currentLevel++
+        // 删除升级之后的已有数据
+        gameList = gameList.filter((it) => it.level !== currentLevel)
+        // 重置当前等级试次索引
+        currentNumber = 0
+        console.log('80%难度升级')
+      }
+    } else if (accuracy < 40) {
+      sendData()
+      return
+    }
+  }
+
+  // 如果当前等级的试次小于最大试次,则试次加一,否则应该进入下一等级
+  if (currentNumber < maxNumber) {
+    currentNumber++
+  } else {
+    // 如果当前等级小于最大等级,则等级加一,否则游戏结束
+    if (currentLevel < maxLevel) {
+      currentLevel++
+      // 删除升级之后的已有数据
+      gameList = gameList.filter((it) => it.level !== currentLevel)
+      // 重置当前等级试次索引
+      currentNumber = 0
+    } else {
+      // 游戏结束
+      console.log('游戏结束2')
+      sendData()
+      return
+    }
+  }
+
+  // 根据当前等级和当前试次改变游戏素材
+  setOnceData()
+}
+// 发送请求
+const sendData = () => {
+  if (planInfo.value) {
+    // 计算正确反应数和总反应时
+    let { correctCount, totalResponseTime, totalScore } = gameList.reduce(
+      (obj, curr) => {
+        if (curr.correct) {
+          obj.correctCount++
+        }
+        obj.totalScore += curr.score
+        obj.totalResponseTime += curr.responseEndTime - curr.responseStartTime
+        return obj
+      },
+      { correctCount: 0, totalResponseTime: 0, totalScore: 0 }
+    )
+
+    // 平均反应时 = 总反应时 / 总试次
+    const avrResponseTime = `${(totalResponseTime / gameList.length).toFixed(2)}ms`
+
+    // 如果在60秒内猜中50个物品得30分
+    if (correctCount >= 50) {
+      totalScore += 50
+    }
+
+    const { gameId, gameName, id: planId } = planInfo.value
+    const data = {
+      finish: '1',
+      gameId,
+      gameName,
+      paramList: [
+        {
+          code: 'score',
+          name: '得分',
+          value: totalScore
+        },
+        {
+          code: 'correctCount',
+          name: '正确反应数',
+          value: correctCount
+        },
+        {
+          code: 'avrResponseTime',
+          name: '平均反应时',
+          value: avrResponseTime
+        },
+        {
+          code: 'avrResponseTime',
+          name: '最大难度',
+          value: currentLevel + 1
+        },
+        {
+          code: 'gameTotalTime',
+          name: '游戏总时长',
+          value: Date.now() - gameStartTime_record / 1000
+        }
+      ],
+      planId,
+      gamelevel: currentLevel + 1,
+      score: totalScore,
+      userId: userStore.user.id
+    }
+    console.log('发送数据', data)
+
+    AchievementAPI.add(data).then(() => {
+      ElMessage.success('本次训练已结束')
+      clearInterval(timerId)
+
+      handleClose(() => {})
+
+      instance?.proxy?.$Bus.emit('trainList-refresh')
+    })
+  } else {
+    ElMessage.success('本次训练已结束')
+    clearInterval(timerId)
+
+    handleClose(() => {})
+    instance?.proxy?.$Bus.emit('trainList-refresh')
+  }
+}
+async function exec() {
+  // openDialogRef.value.openDialog()
+  const tempPlan = sessionStorage.getItem('currentPlanInfo')
+  if (tempPlan) {
+    planInfo.value = JSON.parse(tempPlan)
+  }
+  await nextTick(() => {
+    // 记录游戏开始时间戳
+    gameStartTime = performance.now()
+    gameStartTime_record = Date.now()
+    console.log(gameStartTime_record, '总开始时间')
+    // 重置游戏结束时间戳
+    gameEndTime = 0
+    // 重置当前等级
+    currentLevel = 0
+    // 重置当前试次
+    currentNumber = 0
+    // 清空游戏数据
+    gameList = []
+    // 设置第一个试次的游戏素材
+    setOnceData()
+  })
+}
+const setOnceData = () => {
+  // currentLevel
+  // currentNumber
+  // gameMode
+  // 记录响应开始时间
+  responseStartTime = performance.now()
+  onceData.value = gameData.value[currentLevel][currentNumber]
+  console.log(currentLevel, currentNumber, '1.2')
+  onceData.value.choices = shuffleArray(onceData.value.choices)
+}
+// 随机排列数组
+function shuffleArray(array: any) {
+  const newArray = [...array]
+  for (let i = newArray.length - 1; i > 0; i--) {
+    const j = Math.floor(Math.random() * (i + 1))
+    ;[newArray[i], newArray[j]] = [newArray[j], newArray[i]]
+  }
+  return newArray
+}
+onMounted(() => {
+  exec()
+})
+</script>
+
+<style scoped lang="scss">
+.adl-container {
+  background-size: 100% 100%;
+  background-position: center center;
+  background-repeat: repeat;
+  .question-img {
+    background-image: url('/static/image/cognitiveAbility/SpeechTraining/Visual/title.png');
+    background-size: 100% 100%;
+    background-position: center center;
+    background-repeat: repeat;
+    //font-family: 楷体;
+    text-align: center;
+  }
+  .question-imgStart {
+    background-image: url('/static/image/cognitiveAbility/SpeechTraining/Visual/verify-bg.png');
+    background-size: 100% 100%;
+    background-position: center center;
+    background-repeat: repeat;
+    //font-family: 楷体;
+    text-align: center;
+  }
+  .active {
+    background: url('/static/image/cognitiveAbility/SpeechTraining/Visual/Options-right.png') no-repeat 0 0 / 100% 100%;
+    //background-position: 90% 54%; /* 可选,让图片居中对齐 */
+  }
+  .normal {
+    background: url('/static/image/cognitiveAbility/SpeechTraining/Visual/Options-Blank.png') no-repeat 0 0 / 70% 70%;
+    background-position: 10% 90%; /* 可选,让图片居中对齐 */
+  }
+}
+</style>

+ 56 - 0
src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/wordMap/topics.json

@@ -0,0 +1,56 @@
+[
+  [
+    {
+      "question": "手",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"手",
+          "active": false
+        }, {
+          "name":"脚",
+          "active": false
+        }],
+      "answer": "手"
+    },
+    {
+      "question": "梨",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"柚",
+          "active": false
+        }, {
+          "name":"香",
+          "active": false
+        }, {
+          "name":"梨",
+          "active": false
+        }],
+      "answer": "梨"
+    },
+    {
+      "question": "纸",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"笔",
+          "active": false
+        }, {
+          "name":"纸",
+          "active": false
+        }, {
+          "name":"本",
+          "active": false
+        }, {
+          "name":"书",
+          "active": false
+        }],
+      "answer": "纸"
+    }
+  ]
+]
+

+ 128 - 115
src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/wordWord/index.vue

@@ -1,54 +1,88 @@
 <template>
   <!--  <my-full-screen-dialog ref="openDialogRef" close-color="#0F4DD8" @close-dialog="handleClose">-->
-  <div class="adl-container w-full h-full flex-center text-[#134FA4]">
+  <div v-if="isMainWin" class="adl-container w-full h-full flex-center text-[#134FA4]">
     <div class="center-area w-[90%] h-[90%]">
       <div
-        class="child-container bg-[#ffffff] bor-radius-8 w-[1960px] h-[960px] mt-[100px] flex flex-col justify-center items-center gap-y-[30px]"
+        class="child-container bg-[#ffffff] bor-radius-[10] w-[1360px] h-[860px] mt-[100px] flex flex-col justify-center items-center gap-y-[30px] absolute left-[500px]"
       >
         <div class="bg-1">
-          <div class="bg-1 text-[48px] w-[80%] absolute top-[330px] left-[240px] color-[#333333]">
-            <div class="question-img w-[260px] h-[260px]">{{ onceData.question }}</div>
-            <!--            <el-image :src="onceData.question" fit="contain" class="w-[260px] h-[260px]" />-->
+          <div class="bg-1 text-[68px] w-[80%] absolute top-[30px] left-[550px] color-[#333333]">
+            <div class="question-img w-[290px] h-[290px] text-[186px]">
+              {{ onceData.question }}
+            </div>
           </div>
         </div>
         <div
           id="myButton"
-          class="divMain absolute left-[225px] top-[605px] flex flex-wrap flex-row justify-center gap-x-[20px] mt-[70px] w-[1800px]"
+          class="divMain absolute left-[165px] top-[305px] flex flex-wrap flex-row gap-x-[20px] mt-[50px] w-[1100px]"
+          :class="checkItems && checkItems.length < 3 ? 'flex-center' : ''"
         >
-          <div v-for="(it, index) in onceData.choices" :key="it" @click="handleClick(it)">
-            <div class="text-[40px]">
-              <div class="w-[750px] sab ml-[25px] mt-[30px] p-[10px]">
-                <div
-                  class="bg-[#000] w-[60px] h-[60px]"
-                  :class="[it.active ? 'active' : 'normal']"
-                  @click="checkItemFn(it, index)"
-                ></div>
-                <span class="color-[#0072FF] mr-[50px] ml-[50px]">{{ chengList[index] }}.</span
-                ><span class="color-[#333333]">{{ it }}</span>
-              </div>
+          <div v-for="(it, index) in checkItems" :key="it">
+            <div class="text-[80px] w-[350px] flex flex-wrap flex-row justify-between">
+              <!--              <div class="w-[450px] sab ml-[115px] mt-[30px] flex flex-wrap flex-row justify-between">-->
+              <div
+                class="bg-[#000] w-[120px] h-[120px] flex-center"
+                :class="[it.active ? 'active' : 'normal']"
+                @click="checkItemFn(it, index)"
+              ></div>
+              <span class="color-[#000] fw-700 line-height-[155px]">{{ chengList[index] }}.</span
+              ><span class="color-[#333333] line-height-[155px] fw-700 mr-[60px]"> {{ it.name }}</span>
             </div>
           </div>
         </div>
       </div>
     </div>
   </div>
-
-  <!--  <div class="dialog-footer text-right absolute bottom-[0px] right-[0px]">-->
-  <!--    <el-button type="text" @click="handleSubmit">结束测验</el-button>-->
-  <!--  </div>-->
+  <div v-else class="adl-container w-full h-full flex-center text-[#134FA4]">
+    <div class="center-area w-[90%] h-[90%]">
+      <div
+        class="child-container bg-[#ffffff] bor-radius-[10] w-[1360px] h-[860px] mt-[100px] flex flex-col justify-center items-center gap-y-[30px] absolute left-[500px]"
+      >
+        <div class="bg-1">
+          <div class="bg-1 text-[68px] w-[80%] absolute top-[30px] left-[550px] color-[#333333]">
+            <div class="question-img w-[290px] h-[290px] text-[186px]">
+              {{ onceData.question }}
+            </div>
+          </div>
+        </div>
+        <div
+          id="myButton"
+          class="divMain absolute left-[165px] top-[305px] flex flex-wrap flex-row gap-x-[20px] mt-[50px] w-[1100px]"
+          :class="checkItems && checkItems.length < 3 ? 'flex-center' : ''"
+        >
+          <div v-for="(it, index) in checkItems" :key="it">
+            <div class="text-[80px] w-[350px] flex flex-wrap flex-row justify-between">
+              <!--              <div class="w-[450px] sab ml-[115px] mt-[30px] flex flex-wrap flex-row justify-between">-->
+              <div
+                class="bg-[#000] w-[120px] h-[120px] flex-center"
+                :class="[it.active ? 'active' : 'normal']"
+                @click="checkItemFn(it, isMainWin ? 1 : 0)"
+              ></div>
+              <span class="color-[#000] fw-700 line-height-[155px]">{{ chengList[index] }}.</span
+              ><span class="color-[#333333] line-height-[155px] fw-700 mr-[60px]"> {{ it.name }}</span>
+            </div>
+            <div
+              class="question-imgStart w-[290px] h-[90px] text-[186px] absolute bottom-[-170px] left-[405px]"
+              @click="handleClick(it, index)"
+            ></div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
   <VoiceImp ref="VoiceImpRef" />
   <!--  </my-full-screen-dialog>-->
 </template>
 
 <script setup lang="ts">
 /*
- * 组件名: LogicalReasoning
- * 组件用途: 逻辑推理
+ * 组件名: wordWord
+ * 组件用途: 字字匹配
  * 创建日期: 2024/9/9
  * 编写者: JutarryWu
  */
 import { getCurrentInstance, ref } from 'vue'
-import { getRandomInt } from '@/utils'
+import { getRandomInt, isJSON } from '@/utils'
 import AchievementAPI from '@/api/tester/rehabilitation/training/achievement'
 import { RTPlanMgrQuery } from '@/api/tester/rehabilitation/training/plain'
 import { useUserStore } from '@/store'
@@ -90,34 +124,37 @@ let currentNumber = 0
 // 每个等级的最大试次 15
 const maxNumber = 3
 let timerId: any
-const $emits = defineEmits(['gameOver'])
+const $emits = defineEmits(['close'])
 const openDialogRef = ref()
 const VoiceImpRef = ref()
 
 const handleClose = (done: () => void) => {
-  $emits('gameOver', 'ADLCapability')
+  $emits('close', '')
 }
-
+const isMainWin = ref(false)
 const instance = getCurrentInstance()
 const planInfo = ref<RTPlanMgrQuery>()
 const userStore = useUserStore()
 // 当前试次的游戏图片数据
 const onceData = ref({} as OnceItem)
+const checkItems = ref([])
 const handleClick = (answer: string) => {
-  const correct = onceData.value.answer === answer
-  VoiceImpRef.value.videoPlay('click')
-  setTimeout(() => {
-    if (correct) {
-      console.log(VoiceImpRef.value, 'hbbbbbbbbbbbbbbbbbbbbbbbbbdf=============>')
-      if (VoiceImpRef.value) {
-        VoiceImpRef.value.videoPlay('right')
-      }
-    } else {
-      if (VoiceImpRef.value) {
-        VoiceImpRef.value.videoPlay('error')
-      }
-    }
-  }, 200)
+  console.log(answer, '用户操作数据')
+  const correct = false
+  // const correct = onceData.value.answer === answer
+  // VoiceImpRef.value.videoPlay('click')
+  // setTimeout(() => {
+  //   if (correct) {
+  //     console.log(VoiceImpRef.value, 'hbbbbbbbbbbbbbbbbbbbbbbbbbdf=============>')
+  //     if (VoiceImpRef.value) {
+  //       VoiceImpRef.value.videoPlay('right')
+  //     }
+  //   } else {
+  //     if (VoiceImpRef.value) {
+  //       VoiceImpRef.value.videoPlay('error')
+  //     }
+  //   }
+  // }, 200)
   // 收集当前试次的用户操作数据
   gameList.push({
     level: currentLevel,
@@ -131,32 +168,28 @@ const handleClick = (answer: string) => {
   // 开始下一次
   nextOnce()
 }
-const checkItemFn = (item: any, index: number) => {
+const checkItemFn = (item: any, flag: number = 0) => {
   // if (modeSelect.value === 0) {
-  //   checkItems.value.forEach((item2) => {
-  //     item2.active = false
-  //   })
-  //   VoiceImpRef.value.videoPlay()
-  //   item.active = !item.active
-  //   itemActive.value = item.key
-  // }
+  console.log(item, '参数')
+  if (flag === 1) return
+  let tempIndex = -1
+  checkItems.value.forEach((item2, index) => {
+    item2.active = false
+    if (item2.name === item.name) tempIndex = index
+  })
+  checkItems.value[tempIndex].active = true
+  if (!isMainWin.value) {
+    console.log('本地存储', item)
+    localStorage.setItem('two-win-Visual-item-List', JSON.stringify(item))
+  }
 }
 const nextOnce = () => {
-  console.log(onceData.value.choices, 'onceData392')
-  // const a = document.getElementById('countdown');
-  // dingshqi()
+  // console.log(onceData.value.choices, 'onceData392')
   gameEndTime = performance.now()
   const gameDuration = gameEndTime - gameStartTime
   console.log(gameDuration, 'gameDuration')
   console.log(gameEndTime, 'gameEndTime')
   console.log(gameStartTime, 'gameStartTime')
-
-  // if (gameDuration >= gameMaxDuration) {
-  //   // 游戏结束
-  //   console.log('游戏结束1')
-  //   sendData()
-  //   return
-  // }
   const currentLevelList = gameList.filter((it) => it.level === currentLevel)
   // 当前等级进行 10 个试次以上时,计算正确率
   if (currentLevelList.length >= 3) {
@@ -167,10 +200,8 @@ const nextOnce = () => {
       }
       return acc
     }, 0)
-
     // 当前等级正确率
     const accuracy = (count / currentLevelList.length) * 100
-
     // 升降级规则:正确率达到80%难度升级,低于40%降级
     if (accuracy >= 80) {
       if (currentLevel < maxLevel) {
@@ -184,16 +215,6 @@ const nextOnce = () => {
     } else if (accuracy < 40) {
       sendData()
       return
-      // if (currentLevel > 0) {
-      //   // 删除降级之前的已有数据
-      //   gameList = gameList.filter((it) => it.level !== currentLevel)
-      //   currentLevel--
-      //   // 删除降级之后的已有数据
-      //   gameList = gameList.filter((it) => it.level !== currentLevel)
-      //   // 重置当前等级试次索引
-      //   currentNumber = 0
-      //   console.log('低于40%降级')
-      // }
     }
   }
 
@@ -300,6 +321,7 @@ const sendData = () => {
 }
 async function exec() {
   // openDialogRef.value.openDialog()
+  isMainWin.value = window.location.href.includes('win=main')
   const tempPlan = sessionStorage.getItem('currentPlanInfo')
   if (tempPlan) {
     planInfo.value = JSON.parse(tempPlan)
@@ -330,6 +352,7 @@ const setOnceData = () => {
   onceData.value = gameData.value[currentLevel][currentNumber]
   console.log(currentLevel, currentNumber, '1.2')
   onceData.value.choices = shuffleArray(onceData.value.choices)
+  checkItems.value = onceData.value.choices
 }
 // 随机排列数组
 function shuffleArray(array: any) {
@@ -342,13 +365,31 @@ function shuffleArray(array: any) {
 }
 onMounted(() => {
   exec()
+  window.addEventListener('storage', (val) => {
+    if (isMainWin.value) {
+      console.log(val.key)
+      // if (val.key === 'two-win-Visual-mode-select') {
+      //   modeSelectFn(Number(val.newValue))
+      //   localStorage.removeItem('two-win-Visual-mode-select')
+      // }
+      if (val.key === 'two-win-Visual-item-List' && isJSON(val.newValue!)) {
+        let tempVal = JSON.parse(val.newValue!)
+        checkItemFn(tempVal, 0)
+        // localStorage.removeItem('two-win-Visual-item-List')
+      }
+      // if (val.key === 'two-win-Visual-mode-start') {
+      //   startTask()
+      //   localStorage.removeItem('two-win-Visual-mode-start')
+      // }
+    } else {
+      console.log('')
+    }
+  })
 })
 </script>
 
 <style scoped lang="scss">
 .adl-container {
-  //background-color: #f5f4e9;
-  //background-image: url('/static/image/game/bg-ADL.png');
   background-size: 100% 100%;
   background-position: center center;
   background-repeat: repeat;
@@ -357,52 +398,24 @@ onMounted(() => {
     background-size: 100% 100%;
     background-position: center center;
     background-repeat: repeat;
+    //font-family: 楷体;
+    text-align: center;
   }
-  .active {
-    background: url('/static/image/cognitiveAbility/SpeechTraining/item-active.png') no-repeat 0 0 / 100% 100%
-      border-box border-box fixed;
-  }
-
-  .normal {
-    background: url('/static/image/cognitiveAbility/SpeechTraining/item.png') no-repeat 0 0 / 100% 100% border-box
-      border-box fixed;
-  }
-  .center-area {
-    //background-image: url('/static/image/game/ADL/bg-center.png');
+  .question-imgStart {
+    background-image: url('/static/image/cognitiveAbility/SpeechTraining/Visual/verify-bg.png');
     background-size: 100% 100%;
-    background-repeat: no-repeat;
-    background-position: center; /* 可选,让图片居中对齐 */
-  }
-  .bg-2a {
-    cursor: pointer;
-    transition: All 0.4s ease-in-out; //设置动画执行的时间为0.6s
-
-    :hover {
-      //transform: scale(1.1);
-      //border: 1px solid #0f4dd8;
-      background: #e5ffd8;
-      //background-image: url('/static/image/game/ADL/v.png');
-      background-size: 10% 100%;
-      background-repeat: no-repeat;
-      background-position: right; /* 可选,让图片居中对齐 */
-    }
-  }
-  .sab {
-    //border: 1px solid #0f4dd8;
-    background: #ffffff;
-    border-radius: 50px;
-    cursor: pointer;
+    background-position: center center;
+    background-repeat: repeat;
+    //font-family: 楷体;
+    text-align: center;
   }
-  .bg-chen {
-    background-image: url('/static/image/game/ADL/v.png');
-    background-size: 100% 100%;
-    background-repeat: no-repeat;
+  .active {
+    background: url('/static/image/cognitiveAbility/SpeechTraining/Visual/Options-right.png') no-repeat 0 0 / 100% 100%;
+    //background-position: 90% 54%; /* 可选,让图片居中对齐 */
   }
-  .divMain {
-    & div:nth-child(3) {
-      //background: #1b1b1c;
-      float: left;
-    }
+  .normal {
+    background: url('/static/image/cognitiveAbility/SpeechTraining/Visual/Options-Blank.png') no-repeat 0 0 / 70% 70%;
+    background-position: 10% 90%; /* 可选,让图片居中对齐 */
   }
 }
 </style>

+ 46 - 2
src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/components/wordWord/topics.json

@@ -3,9 +3,53 @@
     {
       "question": "手",
       "active": false,
-      "questions": ["小丽说:“我读了25天。","小华说:“小丽读了不到20天。","小丽读的天数不少于20天,也不超过30天。","她们三人中,只有一人说得不对。请问,小丽读了多少天书?"],
-      "choices": ["手", "脚"],
+      "questions": [""],
+      "choices": [
+        {
+          "name":"手",
+          "active": false
+        }, {
+          "name":"脚",
+          "active": false
+        }],
       "answer": "手"
+    },
+    {
+      "question": "梨",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"柚",
+          "active": false
+        }, {
+          "name":"香",
+          "active": false
+        }, {
+          "name":"梨",
+          "active": false
+        }],
+      "answer": "梨"
+    },
+    {
+      "question": "纸",
+      "active": false,
+      "questions": [""],
+      "choices": [
+        {
+          "name":"笔",
+          "active": false
+        }, {
+          "name":"纸",
+          "active": false
+        }, {
+          "name":"本",
+          "active": false
+        }, {
+          "name":"书",
+          "active": false
+        }],
+      "answer": "纸"
     }
   ]
 ]

+ 11 - 6
src/views/tester/components/RehabilitationEvaluation/CognitiveAbilityTask/CognitiveAbilityTaskVisualTraining/index.vue

@@ -24,20 +24,18 @@
             <span class="inline-block text-45px text-black mb-36px mt-28px">请选择您任务模式:</span>
             <div class="flex flex-row w-76% justify-between mt-18px">
               <el-image
-                v-if="!isMainWin"
                 src="/static/image/cognitiveAbility/SpeechTraining/tips-one.png"
                 fit="contain"
                 class="w-[280px] h-[90px] cursor-pointer"
                 :class="{ 'scale-113': modeActive === 0 }"
-                @click="modeSelectFn(0)"
+                @click="modeSelectFn(0, isMainWin ? 1 : 0)"
               />
               <el-image
-                v-if="!isMainWin"
                 src="/static/image/cognitiveAbility/SpeechTraining/tips-all.png"
                 fit="contain"
                 class="w-[280px] h-[90px] cursor-pointer"
                 :class="{ 'scale-113': modeActive === 1 }"
-                @click="modeSelectFn(1)"
+                @click="modeSelectFn(0, isMainWin ? 1 : 0)"
               />
             </div>
           </div>
@@ -53,7 +51,7 @@
             :key="index"
             class="w-[300px] h-[70px] flex-center fw-700 text-[38px] color-[#ffffff] mb-[18px] cursor-pointer hover:scale-102"
             :class="[item.active ? 'active' : 'normal']"
-            @click="checkItemFn(item, index)"
+            @click="checkItemFn(item, isMainWin ? 1 : 0)"
           >
             {{ item.name }}
           </div>
@@ -67,7 +65,7 @@
       </div>
     </div>
 
-    <Word-word v-if="gameActive === '1'" />
+    <Word-word v-if="gameActive === '1'" @close="handleClose" />
 
     <VoiceImp ref="VoiceImpRef" />
   </section>
@@ -148,6 +146,7 @@ const handleClose = () => {
  * @param flag 0: 副屏点击,1:主屏点击
  */
 const checkItemFn = useThrottleFn((item: CheckItem, flag: number = 0) => {
+  console.log(item, flag, 'haha')
   if (flag === 1) return
   if (modeSelect.value === 0) {
     let tempIndex = -1
@@ -170,6 +169,7 @@ const checkItemFn = useThrottleFn((item: CheckItem, flag: number = 0) => {
  * @param flag 0: 副屏点击,1:主屏点击
  */
 const modeSelectFn = useThrottleFn((mode: number, flag: number = 0) => {
+  console.log(mode, flag, 'haha')
   if (modeActive.value !== -1 || flag === 1) return
   if (!isMainWin.value) {
     VoiceImpRef.value.videoPlay('right')
@@ -189,6 +189,7 @@ const modeSelectFn = useThrottleFn((mode: number, flag: number = 0) => {
 const startTask = () => {
   // TODO 开始任务
   gameActive.value = itemActive.value
+  localStorage.setItem('two-win-Visual-mode-start', true)
   console.log('开始任务', gameActive.value)
 }
 
@@ -223,6 +224,10 @@ onMounted(() => {
         checkItemFn(tempVal)
         localStorage.removeItem('two-win-Visual-item-check')
       }
+      if (val.key === 'two-win-Visual-mode-start') {
+        startTask()
+        localStorage.removeItem('two-win-Visual-mode-start')
+      }
     } else {
       console.log('')
     }