huangziyang пре 2 недеља
родитељ
комит
8cced35053
8 измењених фајлова са 484 додато и 166 уклоњено
  1. 19 27
      App.vue
  2. 149 16
      components/Container/Container.vue
  3. 32 10
      components/Topic/Questions.vue
  4. 147 106
      components/Topic/Topic.vue
  5. 2 1
      pages.json
  6. 11 6
      pages/index/index.vue
  7. 106 0
      utils/router.js
  8. 18 0
      utils/utils.js

+ 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>

+ 149 - 16
components/Container/Container.vue

@@ -1,64 +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']"
-      :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>
-import { ref, onMounted } from 'vue';
+import { ref, onMounted, watchEffect } from "vue";
+import { router } from "../../utils/router";
+import { getRect } from "../../utils/utils";
+import { getCurrentInstance } from "vue";
 
-defineProps({
+const props = defineProps({
   showBottom: {
     type: Boolean,
-    default: false
+    default: false,
   },
   bottomText: {
     type: String,
-    default: '~已经到底了~'
+    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;
 }
 
@@ -71,5 +196,13 @@ onMounted(() => {
 
 .bottom-button {
   width: 100%;
+  padding: 24rpx;
+  position: absolute;
+  bottom: 0;
+}
+
+.topic-scroll {
+  white-space: nowrap;
+  display: inline-flex;
 }
-</style>
+</style>

+ 32 - 10
components/Topic/Questions.vue

@@ -2,16 +2,18 @@
   <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] }}
@@ -21,7 +23,7 @@
 </template>
 
 <script setup>
-import { ref, computed } from "vue";
+import { computed } from "vue";
 
 const TopicMapList = ["A", "B", "C", "D", "E"];
 
@@ -51,6 +53,10 @@ const props = defineProps({
     type: Number,
     required: true,
   },
+  mode: {
+    type: String,
+    default: "exam", // practice: 练习模式, exam: 考试模式
+  },
 });
 
 // Emits 定义
@@ -66,14 +72,19 @@ const isCorrect = computed(() =>
 
 // 方法
 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) {
+  // 如果是练习模式,且已经达到答案数量,则不允许继续选择
+  if (props.mode === 'practice' && props.selectCount.length === props.answerList.length) {
+    return;
+  }
+
+  // 如果是练习模式,且即将完成所有选择,则显示答案
+  if (props.mode === 'practice' && props.answerList.length - 1 === props.selectCount.length) {
     emit("showAnswer", true);
   }
 
@@ -105,6 +116,11 @@ const handleClick = () => {
   background-color: #ffebee;
 }
 
+.question-item-selected {
+  border-color: $uni-primary;
+  background-color: #e3f2fd;
+}
+
 .option-label {
   border: 1px solid #ccc;
   border-radius: 50%;
@@ -123,6 +139,12 @@ const handleClick = () => {
   color: #fff;
 }
 
+.option-label-selected {
+  border-color: $uni-primary;
+  background-color: $uni-primary;
+  color: #fff;
+}
+
 .option-text {
   flex: 1;
 }

+ 147 - 106
components/Topic/Topic.vue

@@ -1,105 +1,109 @@
+view
 <template>
-  <view class="topic-container">
-    <scroll-view
-      scroll-x
-      :scroll-into-view="`item-${nowIndex}`"
-      class="topic-scroll"
-      :scroll-with-animation="true"
-      :scroll-anchoring="true"
-      :show-scrollbar="false"
-    >
-      <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>
+  <Container
+    :scrollX="true"
+    :scrollY="false"
+    :scroll-into-view="`item-${nowIndex}`"
+    @onSafeAreaChange="onSafeAreaChange"
+  >
+    <template v-for="(item, parindex) in topics.ques" :key="parindex">
+      <view
+        :id="`item-${parindex}`"
+        class="topic-item"
+        :style="{
+          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">
+              {{
+                item.mode || (item.anslist?.length > 1 ? "多选题" : "单选题")
+              }}
             </view>
-            <view class="star-icon" @tap="handleStar(item)">
-              <text
-                class="iconfont"
-                :class="starIcon === 'star' ? 'icon-star' : 'icon-star-filled'"
-              ></text>
+            <view class="topic-count">
+              第{{ parindex + 1 }}题/共{{ topics.ques.length }}题
             </view>
           </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 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">{{ 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"
+            :mode="mode"
+            @select="handleSelect"
+            @show-answer="(index) => setShowAnswer(index, parindex)"
+          />
+        </view>
 
-          <!-- 答案展示 -->
-          <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
+          v-if="showAnswer[parindex] && mode === 'practice'"
+          class="answer-section"
+        >
+          <view class="answer-content">
+            <view class="answer-row">
+              <view class="answer-item border-r-primary">
+                正确答案:
+                <text class="answer-text">{{ getRightAnswer(parindex) }}</text>
+              </view>
+              <view class="answer-item">
+                我的答案:
+                <text class="answer-text">{{ getMyAnswer(parindex) }}</text>
               </view>
             </view>
           </view>
+        </view>
 
-          <!-- 底部按钮 -->
-          <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 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>
-      </template>
-    </scroll-view>
-  </view>
+      </view>
+    </template>
+  </Container>
 </template>
 
 <script setup>
 import { ref, onMounted } from "vue";
 import Questions from "./Questions.vue";
+import Container from "../Container/Container.vue";
 
 const TopicMapList = ["A", "B", "C", "D", "E"];
+const safeArea = ref({});
 
 // Props 定义
 const props = defineProps({
@@ -115,16 +119,17 @@ const props = defineProps({
     type: String,
     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([]);
 const selectIndexList = ref([]);
@@ -132,8 +137,6 @@ const selectIndexList = ref([]);
 // 生命周期钩子
 onMounted(() => {
   const systemInfo = uni.getSystemInfoSync();
-  windowWidth.value = systemInfo.windowWidth;
-  windowHeight.value = systemInfo.windowHeight;
   selectIndexList.value = Array(props.topics.ques.length)
     .fill()
     .map(() => []);
@@ -152,17 +155,58 @@ const handleStar = (item) => {
 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;
+  }
+
+  // 如果是考试模式
+  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 max = Array.isArray(currentTopic.anslist)
     ? currentTopic.anslist.length
     : 1;
 
-  if (arr.includes(value)) return;
-  if (arr.length >= max) return;
+  // 如果已达到最大选择数,则不允许继续选择
+  if (arr.length >= max) {
+    return;
+  }
 
-  if (max > 1) {
-    arr.push(value);
-  } else {
+  // 如果是单选题,直接替换
+  if (max === 1) {
     selectIndexList.value[parindex] = [value];
+  } else {
+    // 添加新选择
+    arr.push(value);
+  }
+
+  // 如果是练习模式,且即将完成所有选择,则显示答案
+  if (props.mode === "practice" && max - 1 === arr.length) {
+    emit("showAnswer", true);
   }
 };
 
@@ -202,6 +246,10 @@ const handleNextPage = (item, index) => {
   nowIndex.value = index + 1;
   emit("nextPage", item, index);
 };
+
+const onSafeAreaChange = (s) => {
+  safeArea.value = s;
+};
 </script>
 
 <style lang="scss" scoped>
@@ -213,20 +261,10 @@ const handleNextPage = (item, index) => {
   position: relative;
 }
 
-.topic-scroll {
-  display: flex;
-  white-space: nowrap;
-  padding: 0;
-  width: 100%;
-  height: 100%;
-  -webkit-overflow-scrolling: touch;
-}
-
 .topic-item {
   display: flex;
   flex-direction: column;
   gap: 12px;
-  padding: 12px;
   position: relative;
   box-sizing: border-box;
 }
@@ -291,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;
 }

+ 2 - 1
pages.json

@@ -2,7 +2,8 @@
 	"pages": [{
 		"path": "pages/index/index",
 		"style": {
-			"navigationBarTitleText": "uni-app"
+			"navigationBarTitleText": "uni-app",
+			"navigationStyle": "custom"
 		}
 	}],
 	"globalStyle": {

+ 11 - 6
pages/index/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <Topic :topics="list"></Topic>
+  <Topic :topics="list" mode="practice" @answerChange="answerChange"></Topic>
 </template>
 
 <script setup>
@@ -8,7 +8,7 @@ const list = {
   ques: [
     {
       id: 1,
-			title: '测试题目',
+      title: "测试题目测试题目测试题目测试题目测试题目测试题目测试题目测试题目测试题目测试题目测试题目测试题目测试题目测试题目测试题目测试题目测试题目",
       questions: [
         { label: "测试1", value: 1 },
         { label: "测试2", value: 2 },
@@ -16,11 +16,11 @@ const list = {
         { label: "测试4", value: 4 },
         { label: "测试5", value: 5 },
       ],
-      anslist: 2,
+      anslist: [2],
     },
     {
       id: 2,
-			title: '测试题目',
+      title: "测试题目",
       questions: [
         { label: "测试1", value: 1 },
         { label: "测试2", value: 2 },
@@ -30,10 +30,11 @@ const list = {
       ],
       anslist: [2, 4],
     },
-		
+
     {
       id: 3,
-			title: '测试题目',
+      title: "测试题目",
+			mode: '综合分析题',
       questions: [
         { label: "测试1", value: 1 },
         { label: "测试2", value: 2 },
@@ -45,6 +46,10 @@ const list = {
     },
   ],
 };
+
+const answerChange = (item) => {
+  console.log(item);
+};
 </script>
 
 <style></style>

+ 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();
+};