Sfoglia il codice sorgente

feat: 练习流程完成

huangziyang 1 settimana fa
parent
commit
566fe5a3eb

+ 6 - 8
components/Container/Container.vue

@@ -112,9 +112,7 @@ onMounted(() => {
 
   if (isTarbarPage) {
     // 高度再减掉tabbar高度
-    safeArea.value.height -= 50;
-    // 减去系统导航栏高度
-    safeArea.value.height -= systemInfo.statusBarHeight - 18;
+    safeArea.value.height -= 48;
   }
   // 24是内边距
   safeArea.value.width -= 24;
@@ -135,6 +133,8 @@ onMounted(() => {
   getRect({
     name: ".bottom-button",
     onSuccess(res) {
+      console.log(res);
+
       safeArea.value.footer = res;
       safeArea.value.height -= safeArea.value.footer?.height;
       safeArea.value.height += 22; // 剩余高度
@@ -180,9 +180,6 @@ defineExpose({
     </view>
     <scroll-view
       enable-flex
-      enable-passive
-      enhanced
-      paging-enabled
       show-scrollbar
       enable-back-to-top
       scroll-with-animation
@@ -190,7 +187,6 @@ defineExpose({
       :scroll-x="scrollX"
       :scroll-y="scrollY"
       :scroll-top="scrollTop"
-      :scroll-into-view="scrollIntoView"
       @scroll="onScroll"
       v-if="!empty"
     >
@@ -210,8 +206,9 @@ defineExpose({
       </view>
     </scroll-view>
     <view class="empty" v-else>
-      <Empty />
+      <Empty v-bind="$attrs" />
     </view>
+    <slot name="stick"></slot>
     <view
       v-if="$slots.footer"
       class="bottom-button"
@@ -253,6 +250,7 @@ defineExpose({
   color: #333;
   box-sizing: content-box;
   position: relative;
+  overflow: hidden;
   height: 100vh;
 }
 

+ 153 - 0
components/ShareTemplate/ShareTemplateExam.vue

@@ -0,0 +1,153 @@
+<script setup name="shareTemplate">
+import lPainter from "@/uni_modules/lime-painter/components/l-painter/l-painter.vue";
+import lPainterView from "@/uni_modules/lime-painter/components/l-painter-view/l-painter-view.vue";
+import lPainterImage from "@/uni_modules/lime-painter/components/l-painter-image/l-painter-image.vue";
+import lPainterText from "@/uni_modules/lime-painter/components/l-painter-text/l-painter-text.vue";
+import { ref } from "vue";
+
+const painter = ref(null);
+const prosp = defineProps({
+  onClose: {
+    type: Function,
+    default: () => {},
+  },
+  bg: {
+    type: String,
+    default: "",
+  },
+  correct: {
+    type: Object,
+    default: () => ({}),
+  },
+  onSuccess: {
+    type: Function,
+    default: () => {},
+  },
+});
+
+const onSaveImage = () => {
+  painter.value?.canvasToTempFilePathSync({
+    fileType: "jpg",
+    pathType: "url",
+    quality: 1,
+    success: (res) => {
+      // 非H5 保存到相册
+      uni.saveImageToPhotosAlbum({
+        filePath: res.tempFilePath,
+        success: function () {
+          uni.showToast({
+            title: "保存成功",
+            icon: "success",
+          });
+          prosp.onClose?.();
+        },
+      });
+    },
+  });
+};
+</script>
+<template>
+  <view class="share-template">
+    <view class="share-img">
+      <l-painter ref="painter">
+        <l-painter-view
+          css="width: 620rpx; height: 889rpx; position: relative;"
+        >
+          <l-painter-image
+            :src="bg"
+            css="width: 620rpx; height: 889rpx; position: absolute;top: 0;z-index: 1;"
+          />
+          <l-painter-text
+            :text="correct.right + '分'"
+            css="position: absolute;top: 410rpx;left: 120rpx;color: #e7a552; z-index: 2;"
+          />
+          <l-painter-text
+            :text="correct.total + '题'"
+            css="position: absolute;top: 265rpx;color: #e7a552;left: 255rpx; z-index: 2;"
+          />
+          <l-painter-text
+            :text="correct.rate.toFixed(1) + '%'"
+            css="position: absolute;top: 550rpx;left: 100rpx;color: #e7a552;font-size: 30rpx; z-index: 2;"
+          />
+          <l-painter-view
+            css="background: red; width: 146rpx; height: 146rpx;position: absolute;bottom: 18rpx;left: 18rpx;"
+          >
+          </l-painter-view>
+        </l-painter-view>
+      </l-painter>
+    </view>
+    <view class="footer">
+      <div class="title">
+        <view>分享更多好友</view>
+        <uni-icons type="closeempty" class="closeempty" @click="onClose" />
+      </div>
+      <view class="items">
+        <view class="save-img" @click="onSaveImage">
+          <image
+            src="https://openwork-oss.oss-cn-shenzhen.aliyuncs.com/uploads/question/2025/05/zGBXaE27pgRjEA6NoMLC75CSdMs3BKjh1vnpGKAc.png"
+            mode="scaleToFill"
+            class="save"
+          />
+          <text>保存图片</text>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<style scoped lang="scss">
+.share-template {
+  height: 100vh;
+  width: 100%;
+  background: #999999;
+  position: relative;
+  .share-img {
+    position: absolute;
+    top: 368rpx;
+    left: 65rpx;
+  }
+
+  .footer {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: calc(68rpx + 80rpx + 140rpx);
+    background-color: #fff;
+    border-radius: 40rpx 40rpx 0rpx 0rpx;
+
+    .title {
+      display: flex;
+      height: 80rpx;
+      align-items: center;
+      justify-content: center;
+      position: relative;
+
+      .closeempty {
+        position: absolute;
+        right: 32rpx;
+      }
+    }
+
+    .items {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      height: 140rpx;
+      .save-img {
+        display: flex;
+        align-items: center;
+        gap: 12rpx;
+        font-weight: 400;
+        font-size: 28rpx;
+        color: #000000;
+
+        .save {
+          width: 60rpx;
+          height: 60rpx;
+        }
+      }
+    }
+  }
+}
+</style>

+ 24 - 4
components/Topic/TopicExam.vue

@@ -174,7 +174,7 @@
                 }"
                 @click="
                   () => {
-                    nowIndex = item.ind;
+                    onBeforePageChange(item.ind);
                     bottomModal?.close();
                   }
                 "
@@ -207,6 +207,8 @@ import Container from "../Container/Container.vue";
 import Modal from "../Modal/Modal.vue";
 import uvParse from "@/uni_modules/uv-parse/components/uv-parse/uv-parse.vue";
 import { useTimeStore } from "@/store/time";
+import { request } from "../../utils/request";
+import { getRoute } from "../../utils/router";
 const styleMap = {
   2: "单选",
   3: "多选",
@@ -270,8 +272,26 @@ const props = defineProps({
     default: 0,
   },
   title: String,
+  real_topic_id: Number,
 });
 
+const onBeforePageChange = (index) => {
+  // 保存用户答题
+  const item = data.value[nowIndex.value];
+  request(
+    "api/question_bank/question_reception/real_topic/save_user_real_topic",
+    {
+      catalogue_id: getRoute().params.id,
+      real_topic_id: props.real_topic_id,
+      is_correct: item.isRight,
+      answer: item.selectAns.join(","),
+    },
+    "post"
+  ).finally(() => {
+    nowIndex.value = index;
+  });
+};
+
 watchEffect(() => {
   data.value = props.topics;
 });
@@ -351,7 +371,7 @@ const handleSelect = ({ pid, checked, index, style }) => {
 };
 
 const handlePage = (item, index, type) => {
-  nowIndex.value = index + (type === "prevPage" ? -1 : 1);
+  onBeforePageChange(index + (type === "prevPage" ? -1 : 1));
   emit(type, { item, index });
 };
 
@@ -454,8 +474,8 @@ const onSafeAreaChange = (s) => {
   justify-content: center;
   border: 1rpx solid #dddddd;
   &.success {
-    border-color: $success;
-    color: $success;
+    border-color: $primary;
+    color: $primary;
   }
 }
 

+ 0 - 7
pages.json

@@ -91,13 +91,6 @@
         "navigationBarTitleText": "考试"
       }
     },
-    {
-      "path": "pages/real/shareExam",
-      "style": {
-        "navigationStyle": "custom",
-        "navigationBarTitleText": "考试海报"
-      }
-    },
     {
       "path": "pages/recharge/index",
       "style": {

+ 31 - 2
pages/challenge/index.vue

@@ -1,5 +1,17 @@
 <template>
-  <Container empty title="挑战" :showBack="false">
+  <Container
+    title="挑战"
+    :scrollStyle="{
+      padding: 0,
+    }"
+    :showBack="false"
+  >
+    <view>
+      <view class="bg"></view>
+    </view>
+    <template #stick>
+      <button class="btn">立即报名!参与挑战!</button>
+    </template>
   </Container>
 </template>
 
@@ -7,4 +19,21 @@
 import Container from "../../components/Container/Container.vue";
 </script>
 
-<style></style>
+<style lang="scss" scoped>
+.bg {
+  width: 100%;
+  height: 1734rpx;
+  background: url("https://openwork-oss.oss-cn-shenzhen.aliyuncs.com/uploads/question/2025/05/IFolqhKEbaTWdBgII6gjvqMKrf8WUbbgEpoeRH5t.png");
+  background-size: cover;
+  padding: 32rpx;
+  box-sizing: border-box;
+}
+
+.btn {
+  width: 660rpx;
+  position: absolute;
+  bottom: 3%;
+  left: 50%;
+  transform: translateX(-50%);
+}
+</style>

+ 2 - 1
pages/index/index.vue

@@ -130,11 +130,12 @@ const calculateDays = () => {
   days.value = diffDays > 0 ? diffDays : 0;
 };
 
-const clickClass = ({ id }) => {
+const clickClass = ({ id, name }) => {
   router.push({
     url: "/pages/regulations/index",
     params: {
       id,
+      title: name,
     },
   });
 };

+ 73 - 3
pages/order/index.vue

@@ -2,17 +2,87 @@
 import Container from "@/components/Container/Container.vue";
 import { request } from "@/utils/request";
 import { ref, onMounted } from "vue";
+import utils from "@/utils/common";
 
 const orderList = ref([]);
 
 onMounted(async () => {
   const res = await request("api/question_bank/question_reception/orders/list");
-  console.log(res);
+  orderList.value = res.data;
 });
 </script>
 
 <template>
-  <Container title="订单记录"> </Container>
+  <Container
+    title="订单记录"
+    bgColor="#f8f8f8"
+    :empty="!orderList.length"
+    headerColor="#fff"
+    text="暂无订单记录"
+  >
+    <view class="Container">
+      <view class="card" v-for="item in orderList" :key="item.id">
+        <view class="left">
+          <view class="name">{{ item.name }}</view>
+          <view
+            >有效期: {{ utils.timestampToString(item.start_time_validity) }}至{{
+              utils.timestampToString(item.end_time_validity)
+            }}</view
+          >
+          <view>订单号: {{ item.order_sn }}</view>
+          <view
+            >创建订单时间: {{ utils.timestampToString(item.insert_time) }}</view
+          >
+        </view>
+        <view class="right">
+          <view class="price"
+            >¥{{ item.order_preferential_price?.toFixed?.(1) }}</view
+          >
+          <view class="line">{{ item.order_original_price?.toFixed?.(1) }}</view>
+        </view>
+      </view>
+    </view>
+  </Container>
 </template>
 
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+@import "@/uni.scss";
+.Container {
+  display: flex;
+  flex-direction: column;
+  gap: 16rpx;
+
+  .card {
+    background-color: #fff;
+    padding: 24rpx 16rpx;
+    border-radius: 16rpx;
+    display: flex;
+    justify-content: space-between;
+    border-radius: 16rpx;
+    font-family: PingFang SC, PingFang SC;
+    font-weight: 500;
+    font-size: 28rpx;
+
+    .left {
+      display: flex;
+      flex-direction: column;
+      gap: 8rpx;
+      color: #666666;
+
+      .name {
+        color: $primary;
+      }
+    }
+
+    .right {
+      .price {
+        color: $error;
+      }
+      .line {
+        color: #999;
+        text-decoration: line-through;
+      }
+    }
+  }
+}
+</style>

+ 32 - 11
pages/real/exam.vue

@@ -5,7 +5,7 @@ import { getRoute } from "../../utils/router";
 import { request } from "../../utils/request";
 import { useTimeStore } from "@/store/time";
 import Container from "../../components/Container/Container.vue";
-import ShareTemplate from "../../components/ShareTemplate/ShareTemplate.vue";
+import ShareTemplate from "../../components/ShareTemplate/ShareTemplateExam.vue";
 
 const Time = useTimeStore();
 
@@ -39,15 +39,25 @@ const pageParams = ref({
   limit: 999,
 });
 const data = ref([]);
+const real_topic_id = ref(0);
 
-const getList = async (params) => {
+const getList = async (params = {}) => {
   if (pageParams.value.page * pageParams.value.limit >= total.value) return;
+  const t = await request(
+    "api/question_bank/question_reception/real_topic/set_user_new_paper",
+    {
+      catalogue_id: getRoute().params.id,
+    }
+  );
+
+  real_topic_id.value = t.data;
+
   const res = await request(
-    "api/question_bank/question_reception/topic/get_chapter_topic",
+    "api/question_bank/question_reception/real_topic/get_real_topic",
     {
       ...params,
-      // id: getRoute().params.id,
-      id: 66,
+      catalogue_id: getRoute().params.id,
+      real_topic_id: real_topic_id.value,
     }
   );
   total.value = res.data.total;
@@ -96,10 +106,20 @@ const nextPage = (e) => {
   getList(pageParams.value);
 };
 
-const lookReport = (d, s) => {
+const lookReport = async (d, s) => {
   data.value = d;
-  showReport.value = true;
   const totalTime = Time.end();
+  // 提交试卷
+  await request(
+    "api/question_bank/question_reception/real_topic/user_submit_real_paper",
+    {
+      user_real_paper_id: real_topic_id.value,
+      catalogue_id: getRoute().params.id,
+      use_time: totalTime.totalTime,
+      use_time_str: totalTime.formatTime,
+    }
+  );
+  showReport.value = true;
   submitter.value = {
     ...s,
     totalTime,
@@ -115,11 +135,11 @@ const lookReport = (d, s) => {
   };
 };
 
-onMounted(() => {
-  Time.start();
+onMounted(async () => {
   const params = getRoute().params;
   title.value = params.title;
-  getList(pageParams.value);
+  await getList(pageParams.value);
+  Time.start();
 });
 </script>
 
@@ -131,6 +151,7 @@ onMounted(() => {
     :topics="data"
     @nextPage="nextPage"
     @lookReport="lookReport"
+    :real_topic_id="real_topic_id"
     :empty="!data.length"
     border
     v-if="!showReport && !showShare"
@@ -191,7 +212,7 @@ onMounted(() => {
   <ShareTemplate
     :onClose="() => (showShare = false)"
     :correct="correct"
-    bg="https://openwork-oss.oss-cn-shenzhen.aliyuncs.com/uploads/question/2025/05/ix4wGk9h7Fb4c3d8hjkYjJP6VsPNbZgG3uDTUzxp.png"
+    bg="https://openwork-oss.oss-cn-shenzhen.aliyuncs.com/uploads/question/2025/05/BxBKVS1eV1xaSylWq3i2UjE5VmiJfrfVT7Cc98m3.png"
     v-else
   />
 </template>

+ 55 - 3
pages/real/history.vue

@@ -1,5 +1,5 @@
 <template>
-  <Container title="往年真题">
+  <Container title="往年真题" bgColor="#f8f8f8">
     <view class="user">
       <image
         class="avatar"
@@ -27,6 +27,20 @@
     <view class="tip"
       >注:题型可能会含有单选题、多选题、配伍题、综合分析题;请注意考试时长,无论答题是否完全,到时自动交卷。</view
     >
+
+    <view class="title-exam">考试记录</view>
+    <view class="exam-history">
+      <view
+        class="i"
+        :class="index !== 3 && 'no-laster'"
+        v-for="(item, index) in 4"
+      >
+        <view>2025-05-31</view>
+        <view>90分钟</view>
+        <view>50/100题</view>
+        <view class="score">100分<uni-icons type="right" /></view>
+      </view>
+    </view>
     <template #footer>
       <button @click="onClick">开始考试</button>
     </template>
@@ -36,7 +50,7 @@
 <script setup name="history">
 import { ref, onMounted } from "vue";
 import Container from "../../components/Container/Container.vue";
-import { router } from "../../utils/router";
+import { getRoute, router } from "../../utils/router";
 
 const userInfo = ref({
   userpic: "https://img-cdn-qiniu.dcloud.net.cn/uniapp/images/uni@2x.png",
@@ -48,6 +62,7 @@ const onClick = () => {
     url: "/pages/real/exam",
     params: {
       title: "考试",
+      id: getRoute().params.id, // 考试id
     },
   });
 };
@@ -57,11 +72,14 @@ onMounted(() => {
 </script>
 
 <style scoped lang="scss">
+@import "@/uni.scss";
 .user {
   display: flex;
   align-items: center;
   gap: 20rpx;
-  padding-left: 20rpx;
+  padding: 30rpx 20rpx;
+  background: #eff4ff;
+  border-radius: 24rpx;
 
   .avatar {
     border-radius: 50%;
@@ -75,6 +93,8 @@ onMounted(() => {
   grid-template-columns: repeat(3, 1fr);
   gap: 20rpx;
   padding: 20rpx;
+  background: #eff4ff;
+  border-radius: 24rpx;
   .item {
     display: flex;
     align-items: center;
@@ -88,4 +108,36 @@ onMounted(() => {
     }
   }
 }
+
+.title-exam {
+  font-family: PingFang SC, PingFang SC;
+  font-weight: 500;
+  font-size: 32rpx;
+  color: #333333;
+}
+
+.exam-history {
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 16rpx;
+
+  .i {
+    display: flex;
+    font-family: PingFang SC, PingFang SC;
+    font-size: 28rpx;
+    color: #333;
+    align-items: center;
+    justify-content: space-between;
+
+    .score {
+      color: $error;
+    }
+  }
+
+  .no-laster {
+    padding-bottom: 16rpx;
+    margin-bottom: 8rpx;
+    border-bottom: 1rpx solid #dddddd;
+  }
+}
 </style>

+ 11 - 5
pages/real/index.vue

@@ -15,7 +15,12 @@
           <view v-for="i in item?.children" :key="i.id" class="flex">
             <view class="bg-red">
               <view class="text">{{ i.name }}</view>
-              <view class="button" @click="clickClass(i)">开始考试</view>
+              <view
+                class="button"
+                :class="i.status === 1 && 'plain'"
+                @click="clickClass(i)"
+                >{{ i.status === 1 ? "再次考试" : "开始考试" }}</view
+              >
             </view>
           </view>
         </view>
@@ -39,7 +44,7 @@ const clickClass = (item) => {
   router.push({
     url: "/pages/real/history",
     params: {
-      id: item.id,
+      id: item.catalogue_id,
       title: item.name,
     },
   });
@@ -49,8 +54,10 @@ onMounted(async () => {
   const params = getRoute().params;
   title.value = params.title;
   const res = await request(
-    "api/question_bank/question_reception/chapter/get",
-    {},
+    "api/question_bank/question_reception/real_catalogue/get_by_year",
+    {
+      year: params.origin,
+    },
     "POST"
   );
   list.value = arrayToTree({
@@ -75,7 +82,6 @@ onMounted(async () => {
 }
 
 .p-20 {
-  padding: 0 30rpx 30rpx;
   display: flex;
   flex-direction: column;
   gap: 20rpx;

+ 0 - 11
pages/real/shareExam.vue

@@ -1,11 +0,0 @@
-<script setup>
-import Container from '../../components/Container/Container.vue';
-import ShareTemplate from '../../components/ShareTemplate/ShareTemplate.vue';
-
-</script>
-
-<template>
-  <Container title="考试报告">
-    <ShareTemplate />
-  </Container>
-</template>

+ 22 - 6
pages/regulations/index.vue

@@ -1,6 +1,6 @@
 <template>
   <Container
-    title="法规"
+    :title="title"
     :scrollY="maskStyle.height < 0"
     @onSafeAreaChange="onSafeAreaChange"
   >
@@ -38,7 +38,9 @@
           }"
           v-if="maskStyle.height > 0"
         >
-          <uni-icons type="locked" color="#fff" size="35"></uni-icons>
+          <view class="locked">
+            <uni-icons type="locked" color="#fff" size="35"></uni-icons>
+          </view>
           <view class="modal-wrapper" @click="onClickMask">邀请好友可解锁</view>
         </view>
         <uni-collapse-item
@@ -81,6 +83,7 @@ import { ref, getCurrentInstance, onMounted } from "vue";
 import { getRect, arrayToTree } from "../../utils";
 import { getRoute, router } from "../../utils/router";
 import { request } from "../../utils/request";
+const title = ref("");
 const collapse = ref(null);
 const popup = ref(null);
 const showContainer = ref(false);
@@ -130,7 +133,9 @@ const onChnage = () => {
 };
 
 const onClickMask = () => {
-  showContainer.value = true;
+  router.push({
+    url: '/pages/user/share'
+  })
 };
 
 onMounted(async () => {
@@ -151,6 +156,7 @@ onMounted(async () => {
   freeChaptersList.value = freeList;
   // 付费章节
   chaptersList.value = payList;
+  title.value = getRoute().params.title;
 });
 </script>
 
@@ -218,7 +224,7 @@ onMounted(async () => {
   position: relative;
 
   .modal-mask {
-    background-color: rgba($color: #858585, $alpha: 0.8);
+    background-color: rgba($color: #333333, $alpha: 0.8);
     position: absolute;
     height: 100%;
     z-index: 9999;
@@ -234,11 +240,21 @@ onMounted(async () => {
 .modal-wrapper {
   width: 685rpx;
   height: 64rpx;
-  background: #dfdfdf;
+  background: $primary;
   font-family: PingFang SC, PingFang SC;
   font-weight: 500;
   font-size: 28rpx;
-  color: #000000;
+  color: #fff;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.locked {
+  width: 96rpx;
+  height: 96rpx;
+  background: linear-gradient(159deg, #00d0ff 0%, #008cff 100%);
+  border-radius: 50%;
   display: flex;
   align-items: center;
   justify-content: center;

+ 14 - 10
pages/regulations/learing.vue

@@ -1,7 +1,8 @@
 <script setup name="regulations">
 import Container from "../../components/Container/Container.vue";
 import uvParse from "../../uni_modules/uv-parse/components/uv-parse/uv-parse.vue";
-import { ref, onMounted } from "vue";
+import { ref } from "vue";
+import { onShow } from "@dcloudio/uni-app";
 import { getRoute, router } from "../../utils/router";
 import { request } from "../../utils/request";
 
@@ -33,19 +34,20 @@ const normalText = (text) =>
 
 const onScroll = (e) => {
   se.value = e.detail.scrollTop;
+  scrollTop.value = e.detail.scrollTop;
 };
-const submitScrollTop = (is_done) => {
+const submitScrollTop = (is_done, scroll) => {
   request(
     "api/question_bank/question_reception/point/set_point_study_progress",
     {
       id: getRoute().params.id,
-      scroll_top: se.value,
+      scroll_top: scroll || se.value,
       is_done,
     }
   );
 };
 const onSubmit = () => {
-  submitScrollTop(1);
+  submitScrollTop(1, 0);
   const params = getRoute().params;
   // TODO: 提交滚动条位置,并跳转页面
   router.push({
@@ -64,7 +66,7 @@ const onBack = () =>
     resolve(true);
   });
 
-onMounted(() => {
+onShow(() => {
   request("api/question_bank/question_reception/point/get_chapter_point", {
     id: getRoute().params.id,
   }).then((res) => {
@@ -76,7 +78,7 @@ onMounted(() => {
       };
     });
     otherParams.value = data;
-    scrollTop.value = data.scroll_top;
+    // scrollTop.value = data.scroll_top;
   });
 });
 </script>
@@ -89,11 +91,13 @@ onMounted(() => {
     :scrollTop="scrollTop"
     :onBack="onBack"
   >
-    <view :key="item.id" v-for="(item, index) in context">
-      <view class="title">考 点{{ index + 1 }} {{ item.name }}</view>
-      <uv-parse :content="item.context" :selectable="true" />
+    <view>
+      <view :key="item.id" v-for="(item, index) in context">
+        <view class="title">考 点{{ index + 1 }} {{ item.name }}</view>
+        <uv-parse :content="item.context" selectable />
+      </view>
+      <text class="button" @click="onSubmit">已学完, 开启练习模式</text>
     </view>
-    <text class="button" @click="onSubmit">已学完, 开启练习模式</text>
   </Container>
 </template>
 

+ 29 - 14
pages/regulations/practice.vue

@@ -5,6 +5,7 @@ import { getRoute, router } from "../../utils/router";
 import { request } from "../../utils/request";
 import { useTimeStore } from "@/store/time";
 import Container from "../../components/Container/Container.vue";
+import ShareTemplate from "../../components/ShareTemplate/ShareTemplate.vue";
 
 const Time = useTimeStore();
 
@@ -17,9 +18,11 @@ const correct = ref({
   error: 0, // 错误数
   right: 0, // 正确数
   not: 0, // 未答题
+  total: 0, // 总题数
 });
 
 const showReport = ref(false);
+const showShare = ref(false);
 const submitter = ref({
   closeText: "直接退出",
   context: "您本次答题还未提交, 确定要退出答题吗?",
@@ -93,6 +96,21 @@ const nextPage = (e) => {
   getList(pageParams.value);
 };
 
+const onStar = (item) =>
+  new Promise((resolve) => {
+    request(
+      !item.is_favorite
+        ? "api/question_bank/question_reception/topic/set_favorite"
+        : "api/question_bank/question_reception/topic/cancel_favorite",
+      {
+        id: item.id,
+      }
+    ).then(() => {
+      item.is_favorite = item.is_favorite ? 0 : 1;
+      resolve(item.is_favorite);
+    });
+  });
+
 const lookReport = (d, s) => {
   data.value = d;
   showReport.value = true;
@@ -108,6 +126,7 @@ const lookReport = (d, s) => {
     right: r,
     error: total.value - r - n,
     not: n,
+    total: total.value,
   };
 };
 
@@ -127,14 +146,15 @@ onMounted(() => {
     :topics="data"
     @nextPage="nextPage"
     @lookReport="lookReport"
+    :onStar="onStar"
     :empty="!data.length"
     border
-    v-if="!showReport"
+    v-if="!showReport && !showShare"
   />
   <Container
     bgColor="url('https://openwork-oss.oss-cn-shenzhen.aliyuncs.com/uploads/question/2025/05/7i6ROn34tZGe8QR4gaWDP1ZzyPYjqlvPgLLFRmIe.png')"
     bottomBorder
-    v-else
+    v-else-if="!showShare && showReport"
     bottomBgColor="#fff"
     title="练习报告"
   >
@@ -175,21 +195,16 @@ onMounted(() => {
     <template #footer>
       <view class="footer">
         <view class="button plain" @click="showReport = false">答题解析</view>
-        <view
-          class="button"
-          @click="
-            router.push({
-              url: '/pages/real/shareExam',
-              params: {
-                correct,
-              },
-            })
-          "
-          >炫耀一下</view
-        >
+        <view class="button" @click="showShare = true">炫耀一下</view>
       </view>
     </template>
   </Container>
+  <ShareTemplate
+    :onClose="() => (showShare = false)"
+    :correct="correct"
+    bg="https://openwork-oss.oss-cn-shenzhen.aliyuncs.com/uploads/question/2025/05/ix4wGk9h7Fb4c3d8hjkYjJP6VsPNbZgG3uDTUzxp.png"
+    v-else
+  />
 </template>
 
 <style lang="scss" scoped>

+ 15 - 12
pages/user/invited.vue

@@ -12,16 +12,16 @@ const titleList = ref([
     value: 0,
     key: "number_of_invitations",
   },
-  {
-    label: "可免费解锁科目",
-    value: 0,
-    key: "unlockable_subjects",
-  },
-  {
-    label: "已免费解锁科目",
-    value: 0,
-    key: "unlocked_subject",
-  },
+  // {
+  //   label: "可免费解锁科目",
+  //   value: 0,
+  //   key: "unlockable_subjects",
+  // },
+  // {
+  //   label: "已免费解锁科目",
+  //   value: 0,
+  //   key: "unlocked_subject",
+  // },
 ]);
 
 onMounted(() => {
@@ -86,8 +86,11 @@ onMounted(() => {
       no-repeat center center;
     background-size: cover;
     height: 181rpx;
-    display: grid;
-    grid-template-columns: repeat(3, 1fr);
+    // display: grid;
+    // grid-template-columns: repeat(3, 1fr);
+    display: flex;
+    align-items: center;
+    justify-content: center;
 
     .item {
       display: flex;

+ 8 - 6
pages/user/share.vue

@@ -37,7 +37,7 @@ onMounted(() => {
     page_url: "/pages/index/index",
   }).then((res) => {
     console.log(res);
-    qrCode.value = res.data;
+    qrCode.value = res.data?.image;
   });
 });
 </script>
@@ -49,8 +49,10 @@ onMounted(() => {
     title="分享有礼"
     v-if="!showShare"
   >
-    <view class="bg">
-      <image class="qr-code" :src="qrCode" mode="scaleToFill" />
+    <view>
+      <view class="bg">
+        <image class="qr-code" :src="qrCode" mode="scaleToFill" />
+      </view>
     </view>
     <template #footer>
       <button @click="showShare = true">立即分享</button>
@@ -98,9 +100,9 @@ onMounted(() => {
 <style scoped lang="scss">
 @import "@/uni.scss";
 .bg {
-  height: 100%;
+  height: 1621rpx;
   width: 100%;
-  background: url("https://openwork-oss.oss-cn-shenzhen.aliyuncs.com/uploads/question/2025/05/MTMhEHtFleeotSfPxxtyozvk9Qr7tRK1lSzwO4L3.png");
+  background: url("https://openwork-oss.oss-cn-shenzhen.aliyuncs.com/uploads/question/2025/05/L3YoluT87QrM2nbwXD1YwJyZVjd3C2LvSKtZ9dd4.png");
   background-size: cover;
   position: relative;
   .qr-code {
@@ -110,7 +112,7 @@ onMounted(() => {
     border-radius: 24rpx;
     border: 16rpx solid $primary;
     left: 50%;
-    top: 37%;
+    top: 31.5%;
     transform: translateX(-50%);
   }
 }

+ 10 - 0
uni_modules/uv-parse/components/uv-parse/parser.js

@@ -230,6 +230,14 @@ Parser.prototype.hook = function (node) {
  * @returns {String} 拼接后的链接
  */
 Parser.prototype.getUrl = function (url) {
+  if (url.startsWith('data:')) {
+    var imgPath = uni.env.USER_DATA_PATH + '/e-invoice' + Date.parse(new Date()) + '.png';
+    var imageData = url.replace(/^data:image\/\w+;base64,/, "");
+    var fs = uni.getFileSystemManager();
+    fs.writeFileSync(imgPath, imageData, "base64");
+    fs.close();
+    url = imgPath
+  }
   const domain = this.options.domain
   if (url[0] === '/') {
     if (url[1] === '/') {
@@ -248,6 +256,8 @@ Parser.prototype.getUrl = function (url) {
       url = plus.io.convertLocalFileSystemURL(url)
     } /* #endif */
   }
+  
+  
   return url
 }
 

+ 1 - 0
uni_modules/uv-parse/components/uv-parse/uv-parse.vue

@@ -315,6 +315,7 @@ export default {
         this.imgList = []
       }
       const nodes = new Parser(this).parse(content)
+      
       // #ifdef APP-PLUS-NVUE
       if (this._ready) {
         this._set(nodes, append)