浏览代码

fix: 解决冲突

huangziyang 2 周之前
父节点
当前提交
56b522e587
共有 10 个文件被更改,包括 627 次插入202 次删除
  1. 3 1
      .gitignore
  2. 19 27
      App.vue
  3. 155 23
      components/Container/Container.vue
  4. 64 26
      components/Topic/Questions.vue
  5. 172 85
      components/Topic/Topic.vue
  6. 36 34
      pages.json
  7. 49 5
      pages/index/index.vue
  8. 5 1
      uni.scss
  9. 106 0
      utils/router.js
  10. 18 0
      utils/utils.js

+ 3 - 1
.gitignore

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

+ 19 - 27
App.vue

@@ -1,32 +1,24 @@
 <script>
-	export default {
-		onLaunch: function() {
-			console.warn('当前组件仅支持 uni_modules 目录结构 ,请升级 HBuilderX 到 3.1.0 版本以上!')
-			console.log('App Launch')
-		},
-		onShow: function() {
-			console.log('App Show')
-		},
-		onHide: function() {
-			console.log('App Hide')
-		}
-	}
+export default {
+  onLaunch: function () {
+    console.warn(
+      "当前组件仅支持 uni_modules 目录结构 ,请升级 HBuilderX 到 3.1.0 版本以上!"
+    );
+    console.log("App Launch");
+  },
+  onShow: function () {
+    console.log("App Show");
+  },
+  onHide: function () {
+    console.log("App Hide");
+  },
+};
 </script>
 
 <style lang="scss">
-	/*每个页面公共css */
-	@import '@/uni_modules/uni-scss/index.scss';
-	/* #ifndef APP-NVUE */
-	@import '@/static/customicons.css';
-	// 设置整个项目的背景色
-	page {
-		background-color: #f5f5f5;
-	}
-
-	/* #endif */
-	.example-info {
-		font-size: 14px;
-		color: #333;
-		padding: 10px;
-	}
+/*每个页面公共css */
+@import "@/uni_modules/uni-scss/index.scss";
+/* #ifndef APP-NVUE */
+@import "@/static/customicons.css";
+/* #endif */
 </style>

+ 155 - 23
components/Container/Container.vue

@@ -1,65 +1,189 @@
 <template>
   <view class="container">
+    <view
+      class="title"
+      :style="{
+        paddingTop: `${safeArea.top}px`,
+        background: bgColor,
+      }"
+      v-if="showTitle"
+    >
+      <uni-nav-bar
+        :left-icon="showBack ? 'arrow-left' : ''"
+        @clickLeft="onBack"
+        :border="false"
+        title="导航栏组件"
+        fixed
+        :backgroundColor="bgColor"
+      />
+    </view>
     <scroll-view
-      :class="['scroll-view', className]"
-      :scroll-y="true"
+      :class="['scroll-view', scrollX ? 'topic-scroll' : '']"
+      :scroll-y="scrollY"
       :scroll-with-animation="true"
       :scroll-into-view-within-extent="true"
       :enable-flex="true"
       :enable-passive="true"
       :enhanced="true"
       :paging-enabled="true"
+      :scroll-anchoring="true"
       :show-scrollbar="false"
+      :scroll-x="scrollX"
+      :scroll-into-view="scrollIntoView"
+      :style="{
+        // 24是内边距
+        // width: `${safeArea.width}px`,
+        // 减去底部高度
+        height: `${safeArea.height}px`,
+        whiteSpace: scrollX ? 'nowrap' : 'normal',
+      }"
     >
       <slot></slot>
       <!-- 底部文字 -->
-      <view v-if="showBottom" class="bottom-text">
+      <view v-if="showBottom && bottomText" class="bottom-text">
         {{ bottomText }}
       </view>
     </scroll-view>
-    <view class="bottom-button">
+    <view
+      class="bottom-button"
+      :style="{
+        width: `${safeArea.width}px`,
+        background: bgColor,
+      }"
+    >
       <slot name="footer" :footer-height="footerHeight"></slot>
     </view>
   </view>
 </template>
 
-<script setup lang="ts">
-import { ref, onMounted } from 'vue';
-
-interface Props {
-  showBottom?: boolean;
-  bottomText?: string;
-  className?: string;
-}
+<script setup>
+import { ref, onMounted, watchEffect } from "vue";
+import { router } from "../../utils/router";
+import { getRect } from "../../utils/utils";
+import { getCurrentInstance } from "vue";
 
-const props = withDefaults(defineProps<Props>(), {
-  showBottom: false,
-  bottomText: '~已经到底了~',
-  className: '',
+const props = defineProps({
+  showBottom: {
+    type: Boolean,
+    default: false,
+  },
+  bottomText: {
+    type: String,
+    default: "~已经到底了~",
+  },
+  scrollX: {
+    type: Boolean,
+    default: false,
+  },
+  scrollY: {
+    type: Boolean,
+    default: true,
+  },
+  className: {
+    type: String,
+    default: "",
+  },
+  scrollIntoView: {
+    type: String,
+    default: "",
+  },
+  style: {
+    type: [String, Object],
+    default: "",
+  },
+  onBack: Function,
+  showBack: {
+    type: Boolean,
+    default: true,
+  },
+  showTitle: {
+    type: Boolean,
+    default: true,
+  },
+  bgColor: {
+    type: String,
+    default: "#fff",
+  },
 });
 
+const emit = defineEmits(["onSafeAreaChange"]);
+
+const onBack = async () => {
+  if (props.onBack) {
+    await props.onBack();
+    router.back();
+    return;
+  }
+  router.back();
+};
+
+const safeArea = ref({
+  footer: {},
+  title: {},
+});
 const footerHeight = ref(0);
 
 onMounted(() => {
+  const systemInfo = uni.getSystemInfoSync();
+  safeArea.value = systemInfo.safeArea;
+  // 24是内边距
+  safeArea.value.width -= 24;
+
+  const instance = getCurrentInstance();
+  // 获取头部高度
+  getRect({
+    name: ".title",
+    onSuccess(res) {
+      safeArea.value.title = res;
+      safeArea.value.height -= props.showTitle
+        ? safeArea.value.title?.height
+        : 0;
+    },
+    instance,
+  });
+  // 获取底部高度
+  getRect({
+    name: ".bottom-button",
+    onSuccess(res) {
+      safeArea.value.footer = res;
+      safeArea.value.height -= safeArea.value.footer?.height;
+      safeArea.value.height += 22; // 剩余高度
+    },
+    instance,
+  });
+});
+
+watchEffect(() => {
+  emit("onSafeAreaChange", safeArea.value);
+  console.log(safeArea.value);
+});
+
+defineExpose({
+  safeArea: safeArea.value,
 });
 </script>
 
 <style lang="scss" scoped>
-@import '@/uni.scss';
+@import "@/uni.scss";
+
+.title {
+  position: sticky;
+  top: 0;
+  z-index: 9999;
+}
 
 .container {
   display: flex;
   flex-direction: column;
-  justify-content: space-between;
-  gap: 8px;
-  color: $uni-text-color;
+  color: #333;
   box-sizing: content-box;
+  position: relative;
+  height: 100vh;
 }
 
 .scroll-view {
   position: relative;
-  padding: 12px;
-  flex: 1;
+  padding: 24rpx 24rpx 0;
   box-sizing: content-box;
 }
 
@@ -72,5 +196,13 @@ onMounted(() => {
 
 .bottom-button {
   width: 100%;
+  padding: 24rpx;
+  position: absolute;
+  bottom: 0;
+}
+
+.topic-scroll {
+  white-space: nowrap;
+  display: inline-flex;
 }
-</style>
+</style>

+ 64 - 26
components/Topic/Questions.vue

@@ -2,28 +2,30 @@
   <view
     class="question-item"
     :class="{
-      'question-item-correct': isSelected && isCorrect,
-      'question-item-wrong': isSelected && !isCorrect,
+      'question-item-correct': mode === 'practice' && isSelected && isCorrect,
+      'question-item-wrong': mode === 'practice' && isSelected && !isCorrect,
+      'question-item-selected': mode === 'exam' && isSelected
     }"
     @tap="handleClick"
   >
     <view
       class="option-label"
       :class="{
-        'option-label-correct': isSelected && isCorrect,
-        'option-label-wrong': isSelected && !isCorrect,
+        'option-label-correct': mode === 'practice' && isSelected && isCorrect,
+        'option-label-wrong': mode === 'practice' && isSelected && !isCorrect,
+        'option-label-selected': mode === 'exam' && isSelected
       }"
     >
       {{ 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 { computed } from "vue";
 
-const TopicMapList = ['A', 'B', 'C', 'D', 'E'];
+const TopicMapList = ["A", "B", "C", "D", "E"];
 
 // Props 定义
 const props = defineProps({
@@ -39,35 +41,60 @@ const props = defineProps({
     type: Array,
     default: () => [],
   },
+  question: {
+    type: Object,
+    required: true,
+    default: () => ({
+      label: "",
+      value: 0,
+    }),
+  },
+  parindex: {
+    type: Number,
+    required: true,
+  },
+  mode: {
+    type: String,
+    default: "exam", // practice: 练习模式, exam: 考试模式
+  },
 });
 
 // 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) {
+    emit("select", props.question.value, props.parindex);
     return;
   }
 
-  if (props.answerList.length - 1 === props.selectCount.length) {
-    emit('showAnswer', true);
+  // 如果是练习模式,且已经达到答案数量,则不允许继续选择
+  if (props.mode === 'practice' && props.selectCount.length === props.answerList.length) {
+    return;
   }
 
-  isSelected.value = true;
-  emit('select', props.index);
+  // 如果是练习模式,且即将完成所有选择,则显示答案
+  if (props.mode === 'practice' && props.answerList.length - 1 === props.selectCount.length) {
+    emit("showAnswer", true);
+  }
+
+  emit("select", props.question.value, props.parindex);
 };
 </script>
 
 <style lang="scss" scoped>
+@import "@/uni.scss";
+
 .question-item {
   display: flex;
   padding: 16px;
@@ -80,15 +107,20 @@ 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;
 }
 
+.question-item-selected {
+  border-color: $uni-primary;
+  background-color: #e3f2fd;
+}
+
 .option-label {
   border: 1px solid #ccc;
   border-radius: 50%;
@@ -96,14 +128,20 @@ 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;
+}
+
+.option-label-selected {
+  border-color: $uni-primary;
+  background-color: $uni-primary;
   color: #fff;
 }
 

+ 172 - 85
components/Topic/Topic.vue

@@ -1,59 +1,75 @@
+view
 <template>
-  <view class="topic-container">
-    <scroll-view
-      scroll-x
-      :scroll-into-view="`item-${nowIndex}`"
-      class="topic-scroll"
-    >
+  <Container
+    :scrollX="true"
+    :scrollY="false"
+    :scroll-into-view="`item-${nowIndex}`"
+    @onSafeAreaChange="onSafeAreaChange"
+  >
+    <template v-for="(item, parindex) in topics.ques" :key="parindex">
       <view
-        v-for="(item, parindex) in topics"
-        :key="parindex"
         :id="`item-${parindex}`"
         class="topic-item"
         :style="{
-          flexShrink: 0
+          width: `${safeArea.width}px`,
+          height: `${safeArea.height}px`,
+          flexShrink: 0, // 解决宽度无效问题
+          marginLeft: parindex !== 0 ? `${24}rpx` : `0` 
         }"
       >
         <!-- 头部 -->
         <view class="topic-header">
           <view class="topic-header-left">
             <view class="topic-type">
-              {{ answerList[parindex]?.length > 1 ? '多选题' : '单选题' }}
+              {{
+                item.mode || (item.anslist?.length > 1 ? "多选题" : "单选题")
+              }}
             </view>
             <view class="topic-count">
-              第{{ parindex + 1 }}题/共{{ topics.length }}题
+              第{{ parindex + 1 }}题/共{{ topics.ques.length }}题
             </view>
           </view>
           <view class="star-icon" @tap="handleStar(item)">
-            <text class="iconfont" :class="starIcon === 'star' ? 'icon-star' : 'icon-star-filled'"></text>
+            <text
+              class="iconfont"
+              :class="starIcon === 'star' ? 'icon-star' : 'icon-star-filled'"
+            ></text>
           </view>
         </view>
 
         <!-- 问题内容 -->
         <view class="topic-content">
-          <view class="question-text">请问图片里那个才是党参?</view>
+          <view class="question-text">{{ item.title }}</view>
           <questions
-            v-for="(_, index) in 5"
+            v-for="(question, index) in item.questions"
             :key="index"
-            :answer-list="answerList[parindex] || []"
+            :answer-list="
+              Array.isArray(item.anslist) ? item.anslist : [item.anslist]
+            "
             :index="index"
-            :select-count="selectIndexList"
+            :select-count="selectIndexList[parindex] || []"
+            :question="question"
+            :parindex="parindex"
+            :mode="mode"
             @select="handleSelect"
-            @show-answer="setShowAnswer"
+            @show-answer="(index) => setShowAnswer(index, parindex)"
           />
         </view>
 
         <!-- 答案展示 -->
-        <view v-if="showAnswer" class="answer-section">
+        <view
+          v-if="showAnswer[parindex] && mode === 'practice'"
+          class="answer-section"
+        >
           <view class="answer-content">
             <view class="answer-row">
-              <view class="answer-item">
+              <view class="answer-item border-r-primary">
                 正确答案:
                 <text class="answer-text">{{ getRightAnswer(parindex) }}</text>
               </view>
               <view class="answer-item">
                 我的答案:
-                <text class="answer-text">{{ getMyAnswer() }}</text>
+                <text class="answer-text">{{ getMyAnswer(parindex) }}</text>
               </view>
             </view>
           </view>
@@ -62,14 +78,14 @@
         <!-- 底部按钮 -->
         <view class="button-group">
           <button
-            v-if="parindex >= 1 && parindex < topics.length - 1"
+            v-if="parindex >= 1 && parindex < topics.ques.length - 1"
             class="prev-btn"
             @tap="handlePrevPage(item, parindex)"
           >
             上一题
           </button>
           <button
-            v-if="parindex < topics.length - 1"
+            v-if="parindex < topics.ques.length - 1"
             class="next-btn"
             @tap="handleNextPage(item, parindex)"
           >
@@ -77,112 +93,180 @@
           </button>
         </view>
       </view>
-    </scroll-view>
-  </view>
+    </template>
+  </Container>
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue'
-import Questions from './Questions.vue'
+import { ref, onMounted } from "vue";
+import Questions from "./Questions.vue";
+import Container from "../Container/Container.vue";
 
-const TopicMapList = ['A', 'B', 'C', 'D', 'E']
+const TopicMapList = ["A", "B", "C", "D", "E"];
+const safeArea = ref({});
 
 // 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",
+  },
+  mode: {
+    type: String,
+    default: "practice", // practice: 练习模式, exam: 考试模式
+  },
+});
 
 // Emits 定义
-const emit = defineEmits(['prevPage', 'nextPage'])
+const emit = defineEmits(["prevPage", "nextPage", "answerChange"]);
 
 // 响应式数据
-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 nowIndex = 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();
+  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'
-  })
-}
+  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 isSingleChoice =
+    !Array.isArray(currentTopic.anslist) || currentTopic.anslist.length === 1;
+
+  // 如果点击已选中的选项,则取消选择
+  if (arr.includes(value)) {
+    selectIndexList.value[parindex] = arr.filter((item) => item !== value);
+    emit("answerChange", {
+      questionIndex: parindex,
+      answers: selectIndexList.value[parindex],
+      isSingleChoice,
+    });
+    return;
+  }
 
-const handleSelect = (index) => {
-  selectIndexList.value.push(index)
-}
+  // 如果是考试模式
+  if (props.mode === "exam") {
+    // 如果是单选题,直接替换
+    if (isSingleChoice) {
+      selectIndexList.value[parindex] = [value];
+    } else {
+      // 多选题直接添加
+      arr.push(value);
+    }
+    emit("answerChange", {
+      questionIndex: parindex,
+      answers: selectIndexList.value[parindex],
+      isSingleChoice,
+    });
+    return;
+  }
 
-const setShowAnswer = (value) => {
-  showAnswer.value = value
-}
+  // 练习模式逻辑
+  const max = Array.isArray(currentTopic.anslist)
+    ? currentTopic.anslist.length
+    : 1;
 
-const getRightAnswer = (index) => {
-  return (answerList.value[index] || [])
-    .map(item => TopicMapList[item])
-    .join('')
-}
+  // 如果已达到最大选择数,则不允许继续选择
+  if (arr.length >= max) {
+    return;
+  }
 
-const getMyAnswer = () => {
-  return selectIndexList.value
-    .map(item => TopicMapList[item])
-    .join('')
-}
+  // 如果是单选题,直接替换
+  if (max === 1) {
+    selectIndexList.value[parindex] = [value];
+  } else {
+    // 添加新选择
+    arr.push(value);
+  }
+
+  // 如果是练习模式,且即将完成所有选择,则显示答案
+  if (props.mode === "practice" && max - 1 === arr.length) {
+    emit("showAnswer", true);
+  }
+};
+
+const setShowAnswer = (value, parindex) => {
+  showAnswer.value[parindex] = value;
+};
+
+const getRightAnswer = (index) => {
+  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);
+};
+
+const onSafeAreaChange = (s) => {
+  safeArea.value = s;
+};
 </script>
 
 <style lang="scss" scoped>
-@import '@/uni.scss';
+@import "@/uni.scss";
 
 .topic-container {
   width: 100vw;
   overflow: hidden;
-}
-
-.topic-scroll {
-  display: flex;
-  white-space: nowrap;
-  padding: 0;
+  position: relative;
 }
 
 .topic-item {
   display: flex;
   flex-direction: column;
   gap: 12px;
-  padding: 12px;
   position: relative;
+  box-sizing: border-box;
 }
 
 .topic-header {
@@ -226,7 +310,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;
@@ -245,10 +329,13 @@ const handleNextPage = (item, index) => {
   display: flex;
   gap: 8px;
   align-items: center;
-  border-right: 2px solid $uni-primary;
   padding-right: 12px;
 }
 
+.border-r-primary {
+  border-right: 2px solid $uni-primary;
+}
+
 .answer-text {
   color: $uni-primary;
 }
@@ -272,4 +359,4 @@ const handleNextPage = (item, index) => {
   background-color: $uni-primary;
   color: #fff;
 }
-</style> 
+</style>

+ 36 - 34
pages.json

@@ -1,36 +1,38 @@
 {
-	"pages": [
-		{
-			"path": "pages/index/index",
-			"style": {
-				"navigationBarTitleText": "uni-app"
-			}
-		},{
-		  "path": "pages/user/index",
-		  "style": {
-			"navigationBarTitleText": "我的"
-		  }
-		},
-		{
-		  "path": "pages/user/settings",
-		  "style": {
-			"navigationBarTitleText": "设置"
-		  }
-		},
-		{
-		  "path": "pages/login/index",
-		  "style": {
-			"navigationBarTitleText": "登录注册"
-		  }
-		}
-	],
-	"globalStyle": {
-		"navigationBarTextStyle": "black",
-		"navigationBarTitleText": "uni-app",
-		"navigationBarBackgroundColor": "#F8F8F8",
-		"backgroundColor": "#F8F8F8",
-		"app-plus": {
-			"background": "#efeff4"
-		}
-	}
+  "pages": [
+    {
+      "path": "pages/index/index",
+      "style": {
+        "navigationBarTitleText": "uni-app",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/user/index",
+      "style": {
+        "navigationBarTitleText": "我的"
+      }
+    },
+    {
+      "path": "pages/user/settings",
+      "style": {
+        "navigationBarTitleText": "设置"
+      }
+    },
+    {
+      "path": "pages/login/index",
+      "style": {
+        "navigationBarTitleText": "登录注册"
+      }
+    }
+  ],
+  "globalStyle": {
+    "navigationBarTextStyle": "black",
+    "navigationBarTitleText": "uni-app",
+    "navigationBarBackgroundColor": "#F8F8F8",
+    "backgroundColor": "#F8F8F8",
+    "app-plus": {
+      "background": "#efeff4"
+    }
+  }
 }

+ 49 - 5
pages/index/index.vue

@@ -1,11 +1,55 @@
 <template>
-		<Topic ></Topic>
-		
+  <Topic :topics="list" mode="practice" @answerChange="answerChange"></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: "测试题目",
+			mode: '综合分析题',
+      questions: [
+        { label: "测试1", value: 1 },
+        { label: "测试2", value: 2 },
+        { label: "测试3", value: 3 },
+        { label: "测试4", value: 4 },
+        { label: "测试5", value: 5 },
+      ],
+      anslist: [2, 3],
+    },
+  ],
+};
+
+const answerChange = (item) => {
+  console.log(item);
+};
 </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;

+ 106 - 0
utils/router.js

@@ -0,0 +1,106 @@
+// 对象转为路径参数
+export const objToUrlParams = (obj) => {
+  if (typeof obj === "string") return obj;
+  let result = "?";
+  for (const element in obj) {
+    result += `${element}=${obj[element]}&`;
+  }
+  return result.substring(0, result.length - 1);
+};
+
+// 将字符串转为对象
+export const urlParamsToObj = (url) => {
+  const params = url.split("?")[1];
+  if (!params) return {};
+  const paramsArr = params.split("&");
+  const result = {};
+  paramsArr.forEach((item) => {
+    const [key, value] = item.split("=");
+    result[key] = value;
+  });
+  return result;
+};
+
+// 参数归一化路由参数
+const normalizeParams = (url, options) => {
+  let normalizeOptions = {
+    url: "",
+  };
+  if (typeof url === "string") {
+    normalizeOptions = {
+      url,
+      ...options,
+    };
+  } else {
+    normalizeOptions = url;
+  }
+  const params = normalizeOptions.params || {};
+  normalizeOptions.url += objToUrlParams(params);
+  delete normalizeOptions.params;
+  return normalizeOptions;
+};
+
+// 路由映射表
+export const router = {
+  /**
+   * 保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面。
+   */
+  push(url, options) {
+    const normalizeOptions = normalizeParams(url, options);
+    uni.navigateTo(normalizeOptions);
+  },
+  /**
+   * 关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面。
+   */
+  redirectTo(url, options) {
+    const normalizeOptions = normalizeParams(url, options);
+    uni.redirectTo(normalizeOptions);
+  },
+  /**
+   * 关闭所有页面,打开到应用内的某个页面。
+   */
+  reLaunch(url, options) {
+    const normalizeOptions = normalizeParams(url, options);
+    uni.reLaunch(normalizeOptions);
+  },
+  /**
+   * 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
+   */
+  switchTab(url, options) {
+    const normalizeOptions = normalizeParams(url, options);
+    uni.switchTab(normalizeOptions);
+  },
+  /**
+   * 关闭当前页面,返回上一页面或多级页面。
+   */
+  back() {
+    const route = getRoute().routeList;
+    if (route.length > 1) {
+      uni.navigateBack();
+      return;
+    }
+    const { redirected, ...params } = route[0].params;
+    if (redirected) {
+      this.reLaunch({
+        url: redirected,
+        params,
+      });
+    }
+  },
+};
+
+// 获取当前路由参数
+export const getRoute = () => {
+  const pages = getCurrentPages();
+  const routeList = pages.map((item, index) => {
+    return {
+      params: item.options,
+      path: item.route,
+      index,
+    };
+  });
+
+  return {
+    routeList,
+  };
+};

+ 18 - 0
utils/utils.js

@@ -0,0 +1,18 @@
+
+
+// 获取元素宽高
+export const getRect = ({ name, onSuccess, instance }) => {
+  // 创建一个选择器查询对象
+  const select = uni.createSelectorQuery().in(instance.proxy);
+  // 选择指定元素,并获取其矩形信息
+  select
+    .select(name)
+    .boundingClientRect((rect) => {
+      // 如果rect是一个数组,则返回
+      if (!rect) return;
+      if (Array.isArray(rect)) return rect[0];
+      // 如果onSuccess存在,则调用onSuccess函数,并传入rect的高度
+      onSuccess?.(rect);
+    })
+    .exec();
+};