Selaa lähdekoodia

feat: 迁移答题组件

huangziyang 2 viikkoa sitten
vanhempi
sitoutus
6b6074ae28
6 muutettua tiedostoa jossa 263 lisäystä ja 157 poistoa
  1. 3 1
      .gitignore
  2. 12 13
      components/Container/Container.vue
  3. 37 21
      components/Topic/Questions.vue
  4. 162 116
      components/Topic/Topic.vue
  5. 44 5
      pages/index/index.vue
  6. 5 1
      uni.scss

+ 3 - 1
.gitignore

@@ -1 +1,3 @@
-unpackage
+unpackage
+
+.cursorrule

+ 12 - 13
components/Container/Container.vue

@@ -1,7 +1,7 @@
 <template>
   <view class="container">
     <scroll-view
-      :class="['scroll-view', className]"
+      :class="['scroll-view']"
       :scroll-y="true"
       :scroll-with-animation="true"
       :scroll-into-view-within-extent="true"
@@ -23,20 +23,19 @@
   </view>
 </template>
 
-<script setup lang="ts">
+<script setup>
 import { ref, onMounted } from 'vue';
 
-interface Props {
-  showBottom?: boolean;
-  bottomText?: string;
-  className?: string;
-}
-
-const props = withDefaults(defineProps<Props>(), {
-  showBottom: false,
-  bottomText: '~已经到底了~',
-  className: '',
-});
+defineProps({
+  showBottom: {
+    type: Boolean,
+    default: false
+  },
+  bottomText: {
+    type: String,
+    default: '~已经到底了~'
+  },
+})
 
 const footerHeight = ref(0);
 

+ 37 - 21
components/Topic/Questions.vue

@@ -16,14 +16,14 @@
     >
       {{ TopicMapList[index] }}
     </view>
-    <view class="option-text">改变形状</view>
+    <view class="option-text">{{ question.label }}</view>
   </view>
 </template>
 
-<script setup lang="ts">
-import { ref, computed } from 'vue';
+<script setup>
+import { ref, computed } from "vue";
 
-const TopicMapList = ['A', 'B', 'C', 'D', 'E'];
+const TopicMapList = ["A", "B", "C", "D", "E"];
 
 // Props 定义
 const props = defineProps({
@@ -39,35 +39,51 @@ const props = defineProps({
     type: Array,
     default: () => [],
   },
+  question: {
+    type: Object,
+    required: true,
+    default: () => ({
+      label: "",
+      value: 0,
+    }),
+  },
+  parindex: {
+    type: Number,
+    required: true,
+  },
 });
 
 // Emits 定义
-const emit = defineEmits(['select', 'showAnswer']);
-
-// 响应式数据
-const isSelected = ref(false);
+const emit = defineEmits(["select", "showAnswer"]);
 
 // 计算属性
-const isCorrect = computed(() => {
-  return props.answerList.includes(props.index);
-});
+const isSelected = computed(() =>
+  props.selectCount.includes(props.question.value)
+);
+const isCorrect = computed(() =>
+  props.answerList.includes(props.question.value)
+);
 
 // 方法
 const handleClick = () => {
-  if (isSelected.value || props.selectCount.length === props.answerList.length) {
+  if (
+    isSelected.value ||
+    props.selectCount.length === props.answerList.length
+  ) {
     return;
   }
 
   if (props.answerList.length - 1 === props.selectCount.length) {
-    emit('showAnswer', true);
+    emit("showAnswer", true);
   }
 
-  isSelected.value = true;
-  emit('select', props.index);
+  emit("select", props.question.value, props.parindex);
 };
 </script>
 
 <style lang="scss" scoped>
+@import "@/uni.scss";
+
 .question-item {
   display: flex;
   padding: 16px;
@@ -80,12 +96,12 @@ const handleClick = () => {
 }
 
 .question-item-correct {
-  border-color: #4caf50;
+  border-color: $success;
   background-color: #e8f5e9;
 }
 
 .question-item-wrong {
-  border-color: #f44336;
+  border-color: $error;
   background-color: #ffebee;
 }
 
@@ -96,14 +112,14 @@ const handleClick = () => {
 }
 
 .option-label-correct {
-  border-color: #4caf50;
-  background-color: #4caf50;
+  border-color: $success;
+  background-color: $success;
   color: #fff;
 }
 
 .option-label-wrong {
-  border-color: #f44336;
-  background-color: #f44336;
+  border-color: $error;
+  background-color: $error;
   color: #fff;
 }
 

+ 162 - 116
components/Topic/Topic.vue

@@ -4,177 +4,222 @@
       scroll-x
       :scroll-into-view="`item-${nowIndex}`"
       class="topic-scroll"
+      :scroll-with-animation="true"
+      :scroll-anchoring="true"
+      :show-scrollbar="false"
     >
-      <view
-        v-for="(item, parindex) in topics"
-        :key="parindex"
-        :id="`item-${parindex}`"
-        class="topic-item"
-        :style="{
-          flexShrink: 0
-        }"
-      >
-        <!-- 头部 -->
-        <view class="topic-header">
-          <view class="topic-header-left">
-            <view class="topic-type">
-              {{ answerList[parindex]?.length > 1 ? '多选题' : '单选题' }}
+      <template v-for="(item, parindex) in topics.ques" :key="parindex">
+        <view
+          :id="`item-${parindex}`"
+          class="topic-item"
+          :style="{
+            width: `${windowWidth}px`,
+            height: `${windowHeight}px`,
+            flexShrink: 0,
+          }"
+        >
+          <!-- 头部 -->
+          <view class="topic-header">
+            <view class="topic-header-left">
+              <view class="topic-type">
+                {{ answerList[parindex]?.length > 1 ? "多选题" : "单选题" }}
+              </view>
+              <view class="topic-count">
+                第{{ parindex + 1 }}题/共{{ topics.ques.length }}题
+              </view>
             </view>
-            <view class="topic-count">
-              第{{ parindex + 1 }}题/共{{ topics.length }}题
+            <view class="star-icon" @tap="handleStar(item)">
+              <text
+                class="iconfont"
+                :class="starIcon === 'star' ? 'icon-star' : 'icon-star-filled'"
+              ></text>
             </view>
           </view>
-          <view class="star-icon" @tap="handleStar(item)">
-            <text class="iconfont" :class="starIcon === 'star' ? 'icon-star' : 'icon-star-filled'"></text>
-          </view>
-        </view>
 
-        <!-- 问题内容 -->
-        <view class="topic-content">
-          <view class="question-text">请问图片里那个才是党参?</view>
-          <questions
-            v-for="(_, index) in 5"
-            :key="index"
-            :answer-list="answerList[parindex] || []"
-            :index="index"
-            :select-count="selectIndexList"
-            @select="handleSelect"
-            @show-answer="setShowAnswer"
-          />
-        </view>
+          <!-- 问题内容 -->
+          <view class="topic-content">
+            <view class="question-text">{{ item.title }}</view>
+            <questions
+              v-for="(question, index) in item.questions"
+              :key="index"
+              :answer-list="
+                Array.isArray(item.anslist) ? item.anslist : [item.anslist]
+              "
+              :index="index"
+              :select-count="selectIndexList[parindex] || []"
+              :question="question"
+              :parindex="parindex"
+              @select="handleSelect"
+              @show-answer="(index) => setShowAnswer(index, parindex)"
+            />
+          </view>
 
-        <!-- 答案展示 -->
-        <view v-if="showAnswer" class="answer-section">
-          <view class="answer-content">
-            <view class="answer-row">
-              <view class="answer-item">
-                正确答案:
-                <text class="answer-text">{{ getRightAnswer(parindex) }}</text>
-              </view>
-              <view class="answer-item">
-                我的答案:
-                <text class="answer-text">{{ getMyAnswer() }}</text>
+          <!-- 答案展示 -->
+          <view v-if="showAnswer[parindex]" class="answer-section">
+            <view class="answer-content">
+              <view class="answer-row">
+                <view class="answer-item">
+                  正确答案:
+                  <text class="answer-text">{{
+                    getRightAnswer(parindex)
+                  }}</text>
+                </view>
+                <view class="answer-item">
+                  我的答案:
+                  <text class="answer-text">{{ getMyAnswer(parindex) }}</text>
+                </view>
               </view>
             </view>
           </view>
-        </view>
 
-        <!-- 底部按钮 -->
-        <view class="button-group">
-          <button
-            v-if="parindex >= 1 && parindex < topics.length - 1"
-            class="prev-btn"
-            @tap="handlePrevPage(item, parindex)"
-          >
-            上一题
-          </button>
-          <button
-            v-if="parindex < topics.length - 1"
-            class="next-btn"
-            @tap="handleNextPage(item, parindex)"
-          >
-            下一题
-          </button>
+          <!-- 底部按钮 -->
+          <view class="button-group">
+            <button
+              v-if="parindex >= 1 && parindex < topics.ques.length - 1"
+              class="prev-btn"
+              @tap="handlePrevPage(item, parindex)"
+            >
+              上一题
+            </button>
+            <button
+              v-if="parindex < topics.ques.length - 1"
+              class="next-btn"
+              @tap="handleNextPage(item, parindex)"
+            >
+              下一题
+            </button>
+          </view>
         </view>
-      </view>
+      </template>
     </scroll-view>
   </view>
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue'
-import Questions from './Questions.vue'
+import { ref, onMounted } from "vue";
+import Questions from "./Questions.vue";
 
-const TopicMapList = ['A', 'B', 'C', 'D', 'E']
+const TopicMapList = ["A", "B", "C", "D", "E"];
 
 // Props 定义
 const props = defineProps({
   topics: {
-    type: Array,
-    default: () => Array(3).fill(0)
+    type: Object,
+    default: () => ({}),
   },
   onStar: {
     type: Function,
-    default: null
+    default: null,
   },
   type: {
     type: String,
-    default: 'radio'
-  }
-})
+    default: "radio",
+  },
+});
 
 // Emits 定义
-const emit = defineEmits(['prevPage', 'nextPage'])
+const emit = defineEmits(["prevPage", "nextPage"]);
 
 // 响应式数据
-const answerList = ref([])
-const nowIndex = ref(0)
-const windowWidth = ref(0)
-const windowHeight = ref(0)
-const starIcon = ref('star')
-const showAnswer = ref(false)
-const selectIndexList = ref([])
+const answerList = ref([]);
+const nowIndex = ref(0);
+const windowWidth = ref(0);
+const windowHeight = ref(0);
+const starIcon = ref("star");
+const showAnswer = ref([]);
+const selectIndexList = ref([]);
 
 // 生命周期钩子
 onMounted(() => {
-  const systemInfo = uni.getSystemInfoSync()
-  windowWidth.value = systemInfo.windowWidth
-  windowHeight.value = systemInfo.windowHeight
-  answerList.value = [[1, 3], [2], [3]]
-})
+  const systemInfo = uni.getSystemInfoSync();
+  windowWidth.value = systemInfo.windowWidth;
+  windowHeight.value = systemInfo.windowHeight;
+  selectIndexList.value = Array(props.topics.ques.length)
+    .fill()
+    .map(() => []);
+  showAnswer.value = Array(props.topics.ques.length).fill(false);
+});
 
 // 方法
 const handleStar = (item) => {
-  if (!props.onStar) return
-  props.onStar(item).then(res => {
-    if (!res) return
-    starIcon.value = starIcon.value === 'star' ? 'star-2' : 'star'
-  })
-}
-
-const handleSelect = (index) => {
-  selectIndexList.value.push(index)
-}
+  if (!props.onStar) return;
+  props.onStar(item).then((res) => {
+    if (!res) return;
+    starIcon.value = starIcon.value === "star" ? "star-2" : "star";
+  });
+};
+
+const handleSelect = (value, parindex) => {
+  const arr = selectIndexList.value[parindex];
+  const currentTopic = props.topics.ques[parindex];
+  const max = Array.isArray(currentTopic.anslist)
+    ? currentTopic.anslist.length
+    : 1;
+
+  if (arr.includes(value)) return;
+  if (arr.length >= max) return;
+
+  if (max > 1) {
+    arr.push(value);
+  } else {
+    selectIndexList.value[parindex] = [value];
+  }
+};
 
-const setShowAnswer = (value) => {
-  showAnswer.value = value
-}
+const setShowAnswer = (value, parindex) => {
+  showAnswer.value[parindex] = value;
+};
 
 const getRightAnswer = (index) => {
-  return (answerList.value[index] || [])
-    .map(item => TopicMapList[item])
-    .join('')
-}
-
-const getMyAnswer = () => {
-  return selectIndexList.value
-    .map(item => TopicMapList[item])
-    .join('')
-}
+  const topic = props.topics.ques[index];
+  const answers = Array.isArray(topic.anslist)
+    ? topic.anslist
+    : [topic.anslist];
+  return answers
+    .map((value) => {
+      const question = topic.questions.find((q) => q.value === value);
+      return question ? TopicMapList[topic.questions.indexOf(question)] : "";
+    })
+    .join("");
+};
+
+const getMyAnswer = (parindex) => {
+  const topic = props.topics.ques[parindex];
+  return (selectIndexList.value[parindex] || [])
+    .map((value) => {
+      const question = topic.questions.find((q) => q.value === value);
+      return question ? TopicMapList[topic.questions.indexOf(question)] : "";
+    })
+    .join("");
+};
 
 const handlePrevPage = (item, index) => {
-  nowIndex.value = index - 1
-  emit('prevPage', item, index)
-}
+  nowIndex.value = index - 1;
+  emit("prevPage", item, index);
+};
 
 const handleNextPage = (item, index) => {
-  nowIndex.value = index + 1
-  emit('nextPage', item, index)
-}
+  nowIndex.value = index + 1;
+  emit("nextPage", item, index);
+};
 </script>
 
 <style lang="scss" scoped>
-@import '@/uni.scss';
+@import "@/uni.scss";
 
 .topic-container {
   width: 100vw;
   overflow: hidden;
+  position: relative;
 }
 
 .topic-scroll {
   display: flex;
   white-space: nowrap;
   padding: 0;
+  width: 100%;
+  height: 100%;
+  -webkit-overflow-scrolling: touch;
 }
 
 .topic-item {
@@ -183,6 +228,7 @@ const handleNextPage = (item, index) => {
   gap: 12px;
   padding: 12px;
   position: relative;
+  box-sizing: border-box;
 }
 
 .topic-header {
@@ -226,7 +272,7 @@ const handleNextPage = (item, index) => {
 .answer-section {
   flex: 1;
   border-radius: 16rpx;
-  border: 1px solid $uni-primary;
+  border: 1px solid #ddd;
   padding: 24rpx;
   display: flex;
   flex-direction: column;
@@ -272,4 +318,4 @@ const handleNextPage = (item, index) => {
   background-color: $uni-primary;
   color: #fff;
 }
-</style> 
+</style>

+ 44 - 5
pages/index/index.vue

@@ -1,11 +1,50 @@
 <template>
-		<Topic ></Topic>
-		
+  <Topic :topics="list"></Topic>
 </template>
 
 <script setup>
-import Topic from '@/components/Topic/Topic.vue'
+import Topic from "@/components/Topic/Topic.vue";
+const list = {
+  ques: [
+    {
+      id: 1,
+			title: '测试题目',
+      questions: [
+        { label: "测试1", value: 1 },
+        { label: "测试2", value: 2 },
+        { label: "测试3", value: 3 },
+        { label: "测试4", value: 4 },
+        { label: "测试5", value: 5 },
+      ],
+      anslist: 2,
+    },
+    {
+      id: 2,
+			title: '测试题目',
+      questions: [
+        { label: "测试1", value: 1 },
+        { label: "测试2", value: 2 },
+        { label: "测试3", value: 3 },
+        { label: "测试4", value: 4 },
+        { label: "测试5", value: 5 },
+      ],
+      anslist: [2, 4],
+    },
+		
+    {
+      id: 3,
+			title: '测试题目',
+      questions: [
+        { label: "测试1", value: 1 },
+        { label: "测试2", value: 2 },
+        { label: "测试3", value: 3 },
+        { label: "测试4", value: 4 },
+        { label: "测试5", value: 5 },
+      ],
+      anslist: [2, 3],
+    },
+  ],
+};
 </script>
 
-<style>
-</style>
+<style></style>

+ 5 - 1
uni.scss

@@ -2,4 +2,8 @@
 
 $uni-primary: #002FA7;
 $uni-primary-disable:#94bcff;
-$uni-primary-light: #F3F6FF;
+$uni-primary-light: #F3F6FF;
+
+$success: #00be00;
+$error: #f00;
+$default: #e7e7e7;