Browse Source

feat: 学习本功能

huangziyang 7 hours ago
parent
commit
b318de73fe

+ 0 - 1
components/Container/Container.vue

@@ -142,7 +142,6 @@ onMounted(() => {
     name: ".title",
     onSuccess(res) {
       safeArea.value.title = res;
-      console.log(safeArea.value.title);
     },
     instance,
   });

+ 335 - 0
components/Topic/QuestionsItem.vue

@@ -0,0 +1,335 @@
+<script setup>
+import Questions from "./QuestionsItemSelect.vue";
+import uvParse from "@/uni_modules/uv-parse/components/uv-parse/uv-parse.vue";
+import { ref, watchEffect } from "vue";
+
+const dataList = ref([]);
+const props = defineProps({
+  data: {
+    type: Array,
+    default: () => [],
+  },
+  isRight: {
+    type: Boolean,
+    default: true,
+  },
+  onClick: {
+    type: Function,
+  },
+});
+const TopicMapList = ["A", "B", "C", "D", "E"];
+
+watchEffect(() => {
+  dataList.value = props.data.map((item, ind) => {
+    let questions = [];
+    const ans = item.correct_answer.split(",");
+    const ansList = [];
+    for (let i = 0; i < item.question_count; i++) {
+      const current = TopicMapList[i];
+      if (ans.includes(current)) {
+        ansList.push({
+          label: current,
+          value: i,
+        });
+      }
+      const v =
+        item[`select_${current.toLowerCase()}`].replace(/<br\s*\/?>/g, "") ||
+        "";
+      questions.push({
+        label: v,
+        value: current,
+        checked: false,
+        index: i,
+      });
+    }
+
+    return {
+      ...item,
+      questions, // 题目
+      ansList, // 正确答案
+      selectAns: !item.user_answer ? [] : item.user_answer.split(","), // 选择的答案
+      isRight: !!item.is_correct, // 是否正确
+      isImage: item.title.includes("<img"),
+      ind,
+    };
+  });
+  console.log(dataList.value);
+});
+
+const handleClick = (item) => {
+  if (props.onClick) {
+    props.onClick(item);
+    return;
+  }
+  console.log(item);
+};
+</script>
+<template>
+  <!-- 问题内容 -->
+  <view
+    class="topic-content"
+    v-for="(item, parindex) in dataList"
+    :key="parindex"
+    v-if="dataList.length"
+  >
+    <uvParse
+      v-if="item.isImage"
+      :content="item.title"
+      class="question-text"
+    ></uvParse>
+    <rich-text v-else :nodes="item.title" class="question-text"></rich-text>
+    <uvParse
+      :content="item.questions_ex"
+      v-if="item.style === 6 && item.isImage"
+      class="question-text"
+    ></uvParse>
+    <rich-text
+      v-else
+      :nodes="item.questions_ex"
+      class="question-text"
+      :style="{
+        lineHeight: 2,
+      }"
+    ></rich-text>
+    <questions
+      v-if="item.style !== 6"
+      v-for="(question, index) in item.questions"
+      :key="index"
+      :answerList="item.ansList"
+      :index="index"
+      :styleCount="item.style"
+      :question="question"
+      :showResult="item.showResult"
+      :parindex="parindex"
+    />
+    <view class="other" v-else>
+      <questions
+        v-for="(question, index) in item.questions"
+        :key="index"
+        :answerList="item.ansList"
+        :index="index"
+        :styleCount="item.style"
+        :question="question"
+        :showResult="item.showResult"
+        :parindex="parindex"
+      />
+    </view>
+    <div class="footer">
+      <view v-if="isRight" class="c-primary" @click="handleClick(item)">{{
+        onClick ? "答题" : "查看"
+      }}</view>
+      <view v-else class="c-error" @click="handleClick(item)">重做</view>
+    </div>
+  </view>
+</template>
+<style lang="scss" scoped>
+@import "@/uni.scss";
+.other {
+  display: flex;
+  gap: 12px;
+  margin-top: 20rpx;
+}
+.footer {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.c-primary {
+  padding: 2rpx 10rpx;
+  border-radius: 4rpx;
+  border: 1rpx solid $primary;
+  color: $primary;
+  font-size: 24rpx;
+}
+
+.c-error {
+  padding: 2rpx 10rpx;
+  border-radius: 4rpx;
+  border: 1rpx solid $error;
+  color: $error;
+  font-size: 24rpx;
+}
+.content {
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 500;
+  font-size: 28rpx;
+  color: #333333;
+  display: flex;
+  flex-direction: column;
+  gap: 24rpx;
+  padding: 0 30rpx;
+
+  .ques-type {
+    display: flex;
+    gap: 16rpx;
+    flex-direction: column;
+  }
+}
+.grid {
+  display: grid;
+  grid-template-columns: repeat(5, 1fr);
+  gap: 12px;
+}
+.bottom-modal {
+  display: flex;
+  flex-direction: column;
+  gap: 20rpx;
+  // padding: 20rpx;
+}
+.title {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  position: relative;
+  .closeempty {
+    position: absolute;
+    right: 0;
+  }
+}
+.parsing-text {
+  height: 381rpx;
+  white-space: normal;
+}
+.answer-content {
+  display: flex;
+  flex-direction: column;
+  gap: 45rpx;
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 500;
+  font-size: 32rpx;
+  color: #666666;
+}
+.topic-container {
+  width: 100vw;
+  overflow: hidden;
+  position: relative;
+}
+
+.topic-item {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  position: relative;
+  box-sizing: border-box;
+}
+
+.topic-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.topic-header-left {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+}
+
+.item {
+  width: 72rpx;
+  height: 72rpx;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: 1rpx solid #dddddd;
+  &.success {
+    border-color: $primary;
+    color: $primary;
+  }
+}
+
+.topic-type {
+  border: 1px solid $uni-primary;
+  padding: 0 8px;
+  border-radius: 6px;
+  color: $uni-primary;
+  font-weight: 600;
+  font-size: 14px;
+}
+
+.topic-count {
+  color: #333;
+  font-size: 14px;
+}
+
+.topic-content {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  background-color: #fff;
+  margin-top: 16rpx;
+  padding: 24rpx;
+  border-radius: 16rpx;
+}
+
+.question-text {
+  font-weight: bold;
+  font-size: 14px;
+  white-space: normal;
+}
+
+.answer-section {
+  border-radius: 16rpx;
+  border: 1px solid #ddd;
+  padding: 24rpx;
+  display: flex;
+  flex-direction: column;
+  gap: 32rpx;
+}
+
+.answer-row {
+  font-size: 14px;
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.answer-item {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+  padding-right: 12px;
+}
+
+.tip {
+  margin-left: auto;
+}
+
+.border-r-primary {
+  border-right: 2px solid $uni-primary;
+}
+
+.answer-text {
+  color: $uni-primary;
+}
+
+.button-group {
+  display: flex;
+  gap: 8px;
+  position: sticky;
+  bottom: 0;
+  margin-top: auto;
+}
+
+.prev-btn {
+  flex: 1;
+  background-color: $uni-primary-light;
+  color: $uni-primary;
+}
+
+.next-btn {
+  flex: 1;
+  background-color: $uni-primary;
+  color: #fff;
+}
+
+.star-icon {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  font-weight: 500;
+  font-size: 20rpx;
+  color: #000000;
+}
+</style>

+ 139 - 0
components/Topic/QuestionsItemSelect.vue

@@ -0,0 +1,139 @@
+<template>
+  <view class="question-item" @tap="handleClick">
+    <view
+      class="option-label"
+      :class="{
+        'option-label-selected': question.checked && !props.showResult,
+        'option-label-correct':
+          question.checked && props.showResult && question.isRight,
+        'option-label-wrong':
+          question.checked && props.showResult && !question.isRight,
+      }"
+    >
+      {{ question.value }}
+    </view>
+    <uvParse
+      :content="replaceImageDimensions(question.label)"
+      class="option-text"
+    ></uvParse>
+    <view
+      class="option-icon"
+      v-if="
+        question.checked &&
+        props.showResult &&
+        !question.isRight &&
+        styleCount !== 6
+      "
+    >
+      <uni-icons type="closeempty" color="#f00"></uni-icons>
+    </view>
+    <view
+      class="option-icon"
+      v-if="
+        question.checked &&
+        props.showResult &&
+        question.isRight &&
+        styleCount !== 6
+      "
+    >
+      <uni-icons type="checkmarkempty" color="#00be00"></uni-icons>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import uvParse from "../../uni_modules/uv-parse/components/uv-parse/uv-parse.vue";
+// Props 定义
+const props = defineProps({
+  answerList: {
+    type: Array,
+    default: () => [],
+  },
+  selectCount: {
+    type: Array,
+    default: () => [],
+  },
+  question: {
+    type: Object,
+    required: true,
+    default: () => ({
+      label: "",
+      value: 0,
+      checked: false,
+    }),
+  },
+  parindex: {
+    type: Number,
+    required: true,
+  },
+  showResult: {
+    type: Boolean,
+    default: false,
+  },
+  styleCount: Number,
+});
+
+// Emits 定义
+const emit = defineEmits(["select"]);
+
+const replaceImageDimensions = (str) =>
+  str
+    .replace(
+      /<img([^>]*?)style="([^"]*?)width:\s*[^;]+;\s*height:\s*[^;]+;([^"]*?)"([^>]*?)\/?>/g,
+      '<img$1style="$2width:100%;height:100%;$3"$4/>'
+    )
+    .replace(
+      /<img([^>]*?)width="[^"]*"([^>]*?)height="[^"]*"([^>]*?)\/?>/g,
+      '<img$1width="100%"$2height="100%"$3/>'
+    );
+
+// 方法
+const handleClick = () => {
+  if (props.showResult) return;
+  emit("select", {
+    ...props.question,
+    pid: props.parindex,
+    style: props.styleCount,
+  });
+};
+</script>
+
+<style lang="scss" scoped>
+@import "@/uni.scss";
+
+.question-item {
+  display: flex;
+  gap: 12px;
+  align-items: center;
+  color: #333;
+}
+
+.option-label {
+  border: 1px solid #ccc;
+  border-radius: 50%;
+  padding: 0 5px;
+}
+
+.option-label-correct {
+  border-color: $success;
+  background-color: $success;
+  color: #fff;
+}
+
+.option-label-wrong {
+  border-color: $error;
+  background-color: $error;
+  color: #fff;
+}
+
+.option-label-selected {
+  border-color: $uni-primary;
+  background-color: $uni-primary;
+  color: #fff;
+}
+
+.option-text {
+  flex: 1;
+  white-space: normal;
+}
+</style>

+ 0 - 1
components/Topic/TopicPractice.vue

@@ -150,7 +150,6 @@
             </view>
             <button
               :disabled="!item.selectAns.length"
-              class="button"
               v-if="!item.showResult"
               @click="questionSubmit(item, parindex)"
             >

+ 1 - 1
components/Tree/Tree.vue

@@ -68,7 +68,7 @@ const emit = defineEmits(["onChnage", "onClickButton"]);
             >继续学习</view
           >
           <view
-            @click="emit('onClickButton', items, true)"
+            @click="emit('onClickButton', { ...items, practice: true })"
             class="comment"
             v-if="items[keys.process] === 3"
             >去练习</view

+ 6 - 3
pages/challenge/index.vue

@@ -12,7 +12,9 @@
       </view>
     </view>
     <template #stick>
-      <button class="btn" @click="toAdd">立即报名!参与挑战!</button>
+      <button class="btn" @click="toAdd">
+        {{ isPay ? "查看报名信息" : "立即报名!参与挑战!" }}
+      </button>
     </template>
   </Container>
 </template>
@@ -54,13 +56,14 @@ const toRule = () => {
 
 const toAdd = async () => {
   router.push({
-    url: "/pages/challenge/registration",
+    url: !isPay.value
+      ? "/pages/challenge/registration"
+      : "/pages/challenge/addContant",
     params: {
       id: p.value,
       isPay: isPay.value,
     },
   });
-  
 };
 </script>
 

+ 3 - 1
pages/challenge/registration.vue

@@ -19,6 +19,7 @@ const formData = ref({
 });
 const loading = ref(false);
 const show = ref(false);
+const price = ref("");
 const rules = {
   contact_name: [
     {
@@ -46,6 +47,7 @@ onShow(() => {
     id,
   }).then((res) => {
     context.value = res.data.instructions || "";
+    price.value = res.data.package_preferential_price;
   });
 });
 
@@ -232,7 +234,7 @@ const onSubmit = async () => {
         class="btn"
         @click="onSubmit"
       >
-        ¥999 立即开通
+        ¥{{ price }} 立即开通
       </button>
     </template>
   </Container>

+ 94 - 57
pages/learn/index.vue

@@ -68,78 +68,44 @@
       </div>
     </div>
     <div class="context">
-      <template v-if="currentIndex === 0">
-        <div class="card" v-for="item in 10" :key="item">
-          <div class="question-header">
-            <span class="question-type">单选题</span>
-            <span class="question-date">2025-03-20 20:30:20</span>
-          </div>
-          <div class="question-text">
-            根据中药七情配伍理论,属于相畏的药组是?
-          </div>
-          <div class="options">
-            <div
-              class="option"
-              v-for="(option, i) in ['A', 'B', 'C', 'D', 'E']"
-              :key="i"
-            >
-              <span class="option-select">{{ option }}</span>
-              <span>选项内容</span>
-            </div>
-          </div>
-          <div class="footer">
-            <div class="c-primary">查看解析与考点</div>
-          </div>
-        </div>
+      <template v-if="currentIndex === 0 && dataList.length">
+        <QuestionsItem :data="dataList" :isRight="isRight" />
       </template>
-      <template v-if="currentIndex === 1">
-        <div class="card" v-for="item in 10" :key="item">
+      <template v-else-if="currentIndex === 1 && examList.length">
+        <div class="card" v-for="item in examList" :key="item.catalogue_id">
           <div class="card-header">
-            <span class="title">2024年真题</span>
-            <span class="exam-count">已考试15次</span>
+            <span class="title">{{ item.year }}年真题</span>
+            <span class="exam-count">已考试{{ item.exam_count }}次</span>
           </div>
           <div class="card-body">
-            <span>最低分:20分</span>
-            <span>最高分:98分</span>
+            <span>最低分:{{ item.min_score }}分</span>
+            <span>最高分:{{ item.max_score }}分</span>
           </div>
           <div class="footer">
-            <div class="c-primary">再考一次</div>
+            <div class="c-primary" @click="toHistory(item)">再考一次</div>
           </div>
         </div>
       </template>
-      <template v-if="currentIndex === 2">
-        <div class="card" v-for="item in 10" :key="item">
-          <div class="question-header">
-            <span class="question-type">单选题</span>
-            <span class="question-date">2025-03-20 20:30:20</span>
-          </div>
-          <div class="question-text">
-            根据中药七情配伍理论,属于相畏的药组是?
-          </div>
-          <div class="options">
-            <div
-              class="option"
-              v-for="(option, i) in ['A', 'B', 'C', 'D', 'E']"
-              :key="i"
-            >
-              <span class="option-select">{{ option }}</span>
-              <span>选项内容</span>
-            </div>
-          </div>
-          <div class="footer">
-            <div class="c-primary">查看解析与考点</div>
-          </div>
-        </div>
+      <template v-else-if="currentIndex === 2 && favoriteList.length">
+        <QuestionsItem
+          :data="favoriteList"
+          :isRight="isRight"
+          :onClick="toFavorite"
+        />
       </template>
+      <Empty v-else text="暂无数据"></Empty>
     </div>
   </Container>
 </template>
 
 <script setup>
 import Container from "@/components/Container/Container.vue";
+import Empty from "@/components/Empty/Empty.vue";
 import { ref, watch } from "vue";
 import { onShow } from "@dcloudio/uni-app";
 import { request } from "../../utils/request";
+import QuestionsItem from "../../components/Topic/QuestionsItem.vue";
+import { router } from "../../utils/router";
 
 const currentIndex = ref(0);
 const currentTab = ref(0);
@@ -147,21 +113,92 @@ const isRight = ref(1);
 const info = ref({});
 const rightTotal = ref(0);
 const errorTotal = ref(0);
-
-watch([currentTab, isRight], () => {
+const dataList = ref([]);
+const examList = ref([]);
+const favoriteList = ref([]);
+const toHistory = (item) => {
+  router.push({
+    path: "/pages/real/history",
+    query: {
+      id: item.catalogue_id,
+    },
+  });
+};
+const requestTopic = (is_correct, onSucess) => {
   request(
     "api/question_bank/question_reception/study_book/get_answer_topic_record",
     {
       chapter_id: currentTab.value,
-      is_correct: isRight.value,
+      is_correct,
     }
   ).then((res) => {
-    if (isRight.value) {
+    if (onSucess) {
+      onSucess(res.data.data);
+      return;
+    }
+    if (is_correct) {
       rightTotal.value = res.data.total;
+      dataList.value = res.data.data;
     } else {
       errorTotal.value = res.data.total;
     }
   });
+};
+const requestExamList = () => {
+  request("api/question_bank/question_reception/real_topic/get_exam_record", {
+    chapter_id: currentTab.value,
+  }).then((res) => {
+    examList.value = res.data;
+  });
+};
+const requestFavo = () => {
+  request(
+    "api/question_bank/question_reception/favorite/get_user_favorite_list",
+    {
+      chapter_id: currentTab.value,
+    }
+  ).then((res) => {
+    favoriteList.value = res.data.data;
+  });
+};
+const toFavorite = (item) => {
+  console.log(item);
+};
+watch(currentIndex, () => {
+  switch (currentIndex.value) {
+    case 1:
+      requestExamList();
+      break;
+    case 2:
+      requestFavo();
+      break;
+    default:
+      break;
+  }
+});
+
+watch(currentTab, () => {
+  switch (currentIndex.value) {
+    case 0:
+      isRight.value = 1;
+      requestTopic(isRight.value);
+      requestTopic(Number(!isRight.value));
+      break;
+    case 1:
+      requestExamList();
+      break;
+    case 2:
+      requestFavo();
+      break;
+    default:
+      break;
+  }
+});
+watch(isRight, () => {
+  dataList.value = [];
+  requestTopic(isRight.value, (data) => {
+    dataList.value = data;
+  });
 });
 
 onShow(() => {

+ 2 - 3
pages/regulations/index.vue

@@ -81,10 +81,9 @@ const chaptersList = ref([]);
 const freeChaptersList = ref([]);
 
 // 点击学习按钮
-const onClickButton = (item, practice) => {
-  console.log(item);
+const onClickButton = (item) => {
   router.push({
-    url: practice
+    url: item.practice
       ? "/pages/regulations/practice"
       : "/pages/regulations/learing",
     params: {

+ 6 - 1
pages/regulations/practice.vue

@@ -43,7 +43,10 @@ const user_exercise_paper_id = ref(0);
 const getList = async (params) => {
   if (pageParams.value.page * pageParams.value.limit >= total.value) return;
   const i = await request(
-    "api/question_bank/question_reception/topic/get_user_exercise_paper"
+    "api/question_bank/question_reception/topic/get_user_exercise_paper",
+    {
+      chapter_id: getRoute().params.id,
+    }
   );
   if (!i.data) {
     const r = await request(
@@ -118,6 +121,8 @@ const onStar = (item) =>
         : "api/question_bank/question_reception/topic/cancel_favorite",
       {
         id: item.id,
+        chapter_id: getRoute().params.id,
+        user_exercise_paper_id: user_exercise_paper_id.value,
       }
     ).then(() => {
       item.is_favorite = item.is_favorite ? 0 : 1;

+ 24 - 8
utils/request.js

@@ -1,19 +1,35 @@
-import ext from "@/ext.json";
+import ext from "./ext";
 import { router } from "./router";
 // 请求域名
 var domain = "https://openwork.dfwy.tech/";
-//var domain = 'http://saas.com/'
+var app_id = "";
+var port = "";
+console.log(ext);
+
 // #ifdef MP-WEIXIN
 // 'http://127.0.0.1:8000/';
-// domain = uni.getAccountInfoSync().miniProgram.envVersion == 'release' ? 'https://openwork.dfwy.tech/' : 'https://openwork.dfwy.tech/';
+//domain = uni.getAccountInfoSync().miniProgram.envVersion == 'release' ? 'https://openwork.dfwy.tech/' : 'https://openwork.dfwy.tech/';
 domain =
   uni.getAccountInfoSync().miniProgram.envVersion == "release"
-    ? ext.ext.release_host_url
-    : ext.ext.host_url;
-// var app_id = ext.getExtStoreId('app_id') ? ext.getExtStoreId('app_id') : '';
-var app_id = ext.extAppid;
+    ? ext.getExtStoreId("release_host_url")
+    : ext.getExtStoreId("host_url");
+app_id = ext.getExtStoreId("app_id") ? ext.getExtStoreId("app_id") : "";
 // #endif
-
+// #ifdef WEB
+port = "web";
+domain =
+  window.location.hostname == "h5.kailin.com.cn"
+    ? "https://api.kailin.com.cn/"
+    : "https://openwork.dfwy.tech/";
+// #endif
+// #ifdef H5
+domain =
+  window.location.hostname == "h5.kailin.com.cn"
+    ? "https://api.kailin.com.cn/"
+    : "https://openwork.dfwy.tech/";
+port = "wap";
+// #endif
+domain = "https://openwork.dfwy.tech/";
 // 发送网络请求的函数
 export const request = (url, data = {}, method = "GET") => {
   // 获取登录标识