Parcourir la source

添加刷题总量

shmily1213 il y a 1 mois
Parent
commit
4f9a8b52be

+ 1 - 7
src/components/ie-page/ie-page.vue

@@ -2,7 +2,7 @@
   <view class="ie-page theme-ie"
     :class="[safeAreaInsetBottom ? 'safe-area-inset-bottom' : '', { 'is-fixed': fixHeight }]"
     :style="{ backgroundColor: bgColor }">
-    <view class="ie-page-content" :class="{ 'tabbar-layout': $slots.tabbar }">
+    <view class="ie-page-content">
       <slot></slot>
     </view>
     <view v-if="$slots.tabbar" class="ie-page-tabbar">
@@ -42,12 +42,6 @@ const props = defineProps({
 
 .ie-page-content {
   @apply min-h-full relative flex flex-col z-99;
-
-  &.tabbar-layout {
-    // padding-bottom: 60px;
-    // min-height: calc(100vh - 50px);
-    // min-height: calc(100vh - 0px);
-  }
 }
 
 .ie-page-tabbar {

+ 38 - 15
src/components/ie-picker/ie-picker.vue

@@ -1,25 +1,30 @@
 <template>
   <view class="w-full" @click="handleClick">
-    <view class="flex items-center text-30 gap-x-10" :style="customStyle">
-      <view v-if="matchValue" class="flex-1 text-fore-title" :class="{ 'text-[#c0c4cc]': disabled || readonly }">
-        {{ label }}
+    <view class="flex items-center gap-x-10" :style="customStyle">
+      <view v-if="matchValue" class="flex-1 text-fore-title" :style="getValueStyle"
+        :class="{ 'text-[#c0c4cc]': disabled || readonly }">
+        <slot :label="label">
+          {{ label }}
+        </slot>
       </view>
-      <view v-else class="flex-1 text-[#c0c4cc]">{{ placeholder }}</view>
+      <view v-else class="flex-1 text-[#c0c4cc]" :style="getPlaceholderStyle">{{ placeholder }}</view>
       <view v-if="!readonly" class="transition-all duration-300">
         <uv-icon :name="icon" size="15" color="#B3B3B3" />
       </view>
     </view>
   </view>
-  <uv-picker ref="pickerRef" :showToolbar="false" :columns="columns" :defaultIndex="defaultIndex" :round="16"
-    activeColor="#31A0FC" :keyName="keyLabel" :title="title" @change="handleChange" @close="onClose">
-    <template #toolbar>
-      <view class="flex items-center justify-between pt-20">
-        <view class="px-46 py-20 text-28 text-fore-light" @click="handleCancel">取消</view>
-        <text class="text-30 text-fore-title font-bold">{{ title }}</text>
-        <view class="px-46 py-20 text-28 text-fore-title" @click="handleConfirm">确认</view>
-      </view>
-    </template>
-  </uv-picker>
+  <root-portal>
+    <uv-picker ref="pickerRef" :showToolbar="false" :columns="columns" :defaultIndex="defaultIndex" :round="16"
+      activeColor="#31A0FC" :keyName="keyLabel" :title="title" @change="handleChange" @close="onClose">
+      <template #toolbar>
+        <view class="flex items-center justify-between pt-20">
+          <view class="px-46 py-20 text-28 text-fore-light" @click="handleCancel">取消</view>
+          <text class="text-30 text-fore-title font-bold">{{ title }}</text>
+          <view class="px-46 py-20 text-28 text-fore-title" @click="handleConfirm">确认</view>
+        </view>
+      </template>
+    </uv-picker>
+  </root-portal>
 </template>
 <script lang="ts" setup>
 
@@ -62,6 +67,14 @@ const props = defineProps({
   customStyle: {
     type: Object,
     default: () => ({}),
+  },
+  placeholderStyle: {
+    type: Object,
+    default: () => ({}),
+  },
+  fontSize: {
+    type: Number,
+    default: 30,
   }
 });
 const defaultIndex = ref([0]);
@@ -71,7 +84,17 @@ const matchValue = ref(false);
 const columns = computed(() => {
   return [props.list];
 });
-
+const getPlaceholderStyle = computed(() => {
+  return {
+    ...props.placeholderStyle,
+    fontSize: props.fontSize + 'rpx'
+  }
+});
+const getValueStyle = computed(() => {
+  return {
+    fontSize: props.fontSize + 'rpx'
+  }
+});
 const init = () => {
   if (modelValue.value !== null && modelValue.value !== undefined && modelValue.value !== '') {
     const index = props.list.findIndex(item => item[props.keyValue] == modelValue.value);

+ 55 - 34
src/components/ie-table/ie-table.vue

@@ -7,42 +7,57 @@
     </view>
     <view class="table-body">
       <block v-if="data.length">
-        <view class="table-row" :class="{ 'has-border': tableConfig.border }" v-for="(row, index) in data" :key="row.id"
-          @click="handleRowClick(row)">
-          <view class="table-row-cell" v-for="item in tableColumns" :key="item.prop" :style="getColumnStyle(item)">
+        <view class="table-row" :class="{ 'sibling-border-top': getTableConfig.border }" v-for="(row, index) in data"
+          :key="getRowKey(row, index)" @click="handleRowClick(row)">
+          <view class="table-row-cell" v-for="item in tableColumns" :key="item.prop" :style="getCellStyle(item)">
             <view v-if="item.type === 'index'">
               {{ index + 1 }}
             </view>
             <view v-else>
-              <slot :name="item.slot" :item="row">
-                <text>{{ row[item.prop as keyof StudyRecord] }}</text>
+              <slot :name="item.slot" :item="row" :index="index">
+                <text>{{ getCellValue(row, item.prop) }}</text>
               </slot>
             </view>
           </view>
         </view>
       </block>
-      <view v-else class="no-data">暂无数据</view>
+      <view v-else class="no-data">{{ getTableConfig.emptyText }}</view>
     </view>
   </view>
 </template>
-<script lang="ts" setup>
+
+<script lang="ts" setup generic="T extends Record<string, any>">
 import { TableColumnConfig, TableConfig } from '@/types';
-import { StudyRecord } from '@/types/study';
+import { CSSProperties } from 'vue';
+
+// 使用泛型定义props
+interface Props<T> {
+  tableConfig: TableConfig;
+  tableColumns: TableColumnConfig[];
+  data: T[];
+  cellStyle: CSSProperties;
+}
 
-const props = defineProps({
-  tableConfig: {
-    type: Object as PropType<TableConfig>,
-    default: () => ({})
-  },
-  tableColumns: {
-    type: Array as PropType<TableColumnConfig[]>,
-    default: () => []
-  },
-  data: {
-    type: Array as PropType<StudyRecord[]>,
-    default: () => []
-  }
+const props = defineProps<Props<any>>();
+
+// 使用泛型定义emits
+const emit = defineEmits<{
+  rowClick: [row: T]
+}>();
+
+const getTableConfig = computed(() => {
+  return {
+    ...{
+      border: true,
+      stripe: false,
+      emptyText: '暂无数据',
+      loading: false,
+      rowKey: 'id'
+    },
+    ...props.tableConfig
+  };
 });
+
 const getHeaderStyle = (item: TableColumnConfig) => {
   return {
     flex: item.flex ? item.flex : 1,
@@ -50,20 +65,32 @@ const getHeaderStyle = (item: TableColumnConfig) => {
     textAlign: item.headerAlign ? item.headerAlign : 'center'
   };
 };
-const getColumnStyle = (item: TableColumnConfig) => {
+
+const getCellStyle = (item: TableColumnConfig) => {
   return {
     flex: item.flex ? item.flex : 1,
     minWidth: '1px',
-    textAlign: item.align ? item.align : 'center'
+    textAlign: item.align ? item.align : 'center',
+    ...props.cellStyle
   };
 };
-const emit = defineEmits<{
-  rowClick: [row: StudyRecord]
-}>();
-const handleRowClick = (row: StudyRecord) => {
+
+const handleRowClick = (row: any) => {
   emit('rowClick', row);
 };
+
+// 安全地获取行key
+const getRowKey = (row: any, index: number) => {
+  const rowKey = getTableConfig.value.rowKey;
+  return row[rowKey] || index;
+};
+
+// 安全地获取单元格值
+const getCellValue = (row: any, prop: string) => {
+  return row[prop] || '';
+};
 </script>
+
 <style lang="scss" scoped>
 .table-header {
   @apply flex items-center bg-[#EBF9FF] rounded-5;
@@ -77,17 +104,11 @@ const handleRowClick = (row: StudyRecord) => {
   @apply flex items-center;
 }
 
-.has-border {
-  &+.has-border {
-    @apply border-0 border-solid border-t border-border;
-  }
-}
-
 .table-row-cell {
   @apply px-20 py-20 text-28 text-fore-title;
 }
 
 .no-data {
-  @apply mt-16 bg-[#F6F8FA] text-center py-50 text-26 text-fore-tip-light rounded-5;
+  @apply mt-16 bg-[#F6F8FA] text-center py-50 text-26 text-fore-light rounded-5;
 }
 </style>

+ 277 - 0
src/pagesStudy/components/practice-table.vue

@@ -0,0 +1,277 @@
+<template>
+  <view class="px-30 py-38 bg-white">
+    <view class="flex gap-x-20">
+      <view class="flex items-center gap-x-20 flex-wrap">
+        <view class="picker-wrap">
+          <ie-picker ref="pickerRef" v-model="form.year" :list="yearList" placeholder="请选择" title="选择年份" :fontSize="26"
+            icon="arrow-down" key-label="label" key-value="value" @change="handleYearChange">
+            <template #default="{ label }">
+              <text>{{ label }}年</text>
+            </template>
+          </ie-picker>
+        </view>
+        <view class="picker-wrap">
+          <ie-picker ref="pickerRef" v-model="form.month" :list="monthList" placeholder="请选择" title="选择月份"
+            :fontSize="26" icon="arrow-down" key-label="label" key-value="value" @change="handleMonthChange">
+            <template #default="{ label }">
+              <text>第{{ label }}</text>
+            </template>
+          </ie-picker>
+        </view>
+        <view class="picker-wrap">
+          <ie-picker ref="pickerRef" v-model="form.week" :list="weekList" placeholder="请选择" title="选择周" :fontSize="26"
+            icon="arrow-down" key-label="label" key-value="value" @change="handleWeekChange">
+            <template #default="{ label }">
+              <text>第{{ label }}</text>
+            </template>
+          </ie-picker>
+        </view>
+        <view class="btn-wrap" @click="handleOpenCalendar">
+          <text>刷题日历</text>
+          <uv-icon name="search" size="16" color="white" />
+        </view>
+      </view>
+    </view>
+    <view class="mt-30 flex h-280 gap-x-20">
+      <view class="flex-1 h-full">
+        <ie-echart :option="options1" />
+      </view>
+      <view class="flex-1 h-full">
+        <ie-echart :option="options2" />
+      </view>
+    </view>
+    <view class="mt-30 flex items-center">
+      <text>已累计刷题</text>
+      <text class="text-32 text-primary">24</text>
+      <text>天~</text>
+    </view>
+    <view class="mt-30">
+      <ie-table :tableColumns="tableColumns" :data="tableDate">
+        <template #date="{ item }">
+          <text class="font-bold">{{ item.date }}</text>
+        </template>
+        <template #questionNum="{ item }">
+          <text class="font-bold">{{ item.questionNum }}</text>
+        </template>
+        <template #correctNum="{ item }">
+          <text class="font-bold">{{ item.correctNum }}</text>
+        </template>
+      </ie-table>
+    </view>
+    <root-portal>
+      <uv-popup ref="calendarPopupRef" mode="bottom" :round="16">
+        <view class="min-h-200">
+          <uni-calendar :insert="true" :lunar="false" :readonly="true" :showMonth="false" :selected="selected"
+            :endDate="endDate" @change-month="handleCalendarChange" />
+        </view>
+      </uv-popup>
+    </root-portal>
+  </view>
+</template>
+<script lang="ts" setup>
+import { TableColumnConfig } from '@/types';
+import ieEchart from './ie-echart/ie-echart.vue';
+import { CSSProperties } from 'vue';
+const form = ref({
+  year: '',
+  month: '',
+  week: ''
+})
+const year = ref('');
+const yearList = ref([
+  {
+    label: '2025',
+    value: '2025'
+  }
+]);
+const monthList = ref([
+  {
+    label: '10月',
+    value: '1'
+  }
+]);
+const weekList = ref([
+  {
+    label: '1周',
+    value: '1'
+  }
+]);
+const options1 = computed(() => {
+  return {
+    title: {
+      text: '680',
+      subtext: '{a|刷题总量}',
+      left: 'center',
+      top: '34%',
+      textStyle: {
+        fontSize: 20,
+        fontWeight: 'bold',
+        color: '#222'
+      },
+      subtextStyle: {
+        fontSize: 12,
+        color: '#666',
+        lineHeight: 12,
+        rich: {
+          a: {
+            color: '#B3B3B3',
+            padding: [0, 0, 10, 0]
+          },
+        }
+      }
+    },
+    series: [
+      {
+        type: 'pie',
+        radius: ['60%', '80%'],
+        startAngle: 90,
+        label: { show: false },
+        data: [
+          {
+            value: 45,
+            itemStyle: {
+              color: {
+                type: 'linear',
+                x: 0,
+                y: 0,
+                x2: 1,
+                y2: 1,
+                colorStops: [
+                  { offset: 0, color: '#FEC048' },
+                  { offset: 1, color: '#F9942F' }
+                ]
+              }
+            }
+          },
+          {
+            value: 55,
+            itemStyle: { color: '#FFF5DE' }
+          }
+        ]
+      }
+    ]
+  };
+});
+const options2 = computed(() => {
+  return {
+    title: {
+      text: '75%',
+      subtext: '{a|正确率}',
+      left: 'center',
+      top: '34%',
+      textStyle: {
+        fontSize: 20,
+        fontWeight: 'bold',
+        color: '#222'
+      },
+      subtextStyle: {
+        fontSize: 12,
+        color: '#666',
+        lineHeight: 12,
+        rich: {
+          a: {
+            color: '#B3B3B3',
+            padding: [0, 0, 10, 0]
+          },
+        }
+      }
+    },
+    series: [
+      {
+        type: 'pie',
+        radius: ['60%', '80%'],
+        startAngle: 90,
+        label: { show: false },
+        data: [
+          {
+            value: 75,
+            itemStyle: {
+              color: {
+                type: 'linear',
+                x: 0,
+                y: 0,
+                x2: 1,
+                y2: 1,
+                colorStops: [
+                  { offset: 0, color: '#70C8FD' },
+                  { offset: 1, color: '#31A0FC' }
+                ]
+              }
+            }
+          },
+          {
+            value: 35,
+            itemStyle: { color: '#EBF9FF' }
+          }
+        ]
+      }
+    ]
+  };
+});
+const tableColumns = ref<TableColumnConfig[]>([
+  {
+    prop: 'date',
+    label: '日期',
+    flex: 1,
+    slot: 'date'
+  },
+  {
+    prop: 'questionNum',
+    label: '题量',
+    flex: 1,
+    slot: 'questionNum'
+  },
+  {
+    prop: 'correctNum',
+    label: '正确率',
+    flex: 1,
+    slot: 'correctNum'
+  }
+]);
+const cellStyle: CSSProperties = {
+  padding: '30rpx 20rpx'
+}
+const tableDate = ref([
+  {
+    date: '2025.09.11',
+    questionNum: 10,
+    correctNum: 10
+  },
+  {
+    date: '2025.09.12',
+    questionNum: 10,
+    correctNum: 10
+  }
+])
+const handleYearChange = () => {
+  form.value.month = '';
+  form.value.week = '';
+}
+const handleMonthChange = () => {
+  form.value.week = '';
+}
+const handleWeekChange = () => {
+  loadData();
+}
+const endDate = ref('');
+const selected = ref([]);
+const calendarPopupRef = ref();
+const handleOpenCalendar = () => {
+  calendarPopupRef.value.open();
+}
+const handleCalendarChange = (e: any) => {
+  console.log(e)
+}
+const loadData = () => {
+
+}
+</script>
+<style lang="scss" scoped>
+.picker-wrap {
+  @apply flex items-center px-12 w-fit border border-solid border-border rounded-4 h-56;
+}
+
+.btn-wrap {
+  @apply flex items-center gap-x-10 bg-primary text-white text-26 px-10 h-56 rounded-4;
+}
+</style>

+ 1 - 1
src/pagesStudy/pages/index/compoentns/index-banner.vue

@@ -1,6 +1,6 @@
 <template>
   <view class="mx-30 mt-40">
-    <ie-image :is-oss="true" src="/banner/index-banner-3.png" :rounded="10" customClass="w-full h-264"
+    <ie-image :is-oss="true" src="/banner/index-banner-3.png" :round="10" customClass="w-full h-264"
       mode="widthFix" />
     <view class="mt-32 flex gap-x-30">
       <view class="flex-1 rounded-12 bg-[#F0FFF2] py-40 pl-22 pr-8 flex items-center"

+ 26 - 1
src/pagesStudy/pages/study-history/components/exam-history-student.vue

@@ -1,7 +1,32 @@
 <template>
-  <view></view>
+  <view class="px-30 pb-30">
+    <view class="sibling-border-top px-20 py-30" v-for="(item, index) in list" :key="index">
+      <exam-record-item />
+    </view>
+  </view>
 </template>
 <script lang="ts" setup>
+import examRecordItem from '@/pagesStudy/components/exam-record-item.vue';
+const props = defineProps({
+  examType: {
+    type: String,
+    default: 'test'
+  }
+});
+const list = ref([1,1,1,1,1]);
+const loadData = async () => {
+  // const res = await getExamHistory(userType.value, examType.value);
+  // console.log(res);
+  uni.$ie.showLoading();
+  setTimeout(() => {
+    uni.$ie.hideLoading();
+  }, 1000);
+}
 
+watch(() => props.examType, (newVal) => {
+  loadData();
+}, {
+  immediate: true
+});
 </script>
 <style lang="scss" scoped></style>

+ 174 - 1
src/pagesStudy/pages/study-history/components/exam-history-teacher.vue

@@ -1,7 +1,180 @@
 <template>
-  <view></view>
+  <view class="p-30 pt-50">
+    <view class="w-fit flex gap-x-80">
+      <ie-picker ref="pickerRef" v-model="classId" :list="classList" placeholder="选择批次" title="选择批次" icon="arrow-down"
+        key-label="name" key-value="classId" @change="handleClassChange"
+        :placeholder-style="placeholderStyle"></ie-picker>
+
+      <ie-picker ref="pickerRef" v-model="classId" :list="classList" placeholder="选择班级" title="选择班级" icon="arrow-down"
+        key-label="name" key-value="classId" @change="handleClassChange"
+        :placeholder-style="placeholderStyle"></ie-picker>
+    </view>
+    <view class="mt-32 py-38 bg-back border border-solid border-border rounded-10 flex">
+      <view class="flex-1 text-center">
+        <view class="text-40 text-primary font-bold leading-30">64</view>
+        <view class="mt-24 text-28 text-fore-light leading-28">总人数</view>
+      </view>
+      <view class="flex-1 text-center">
+        <view class="text-40 text-[#22C55E] font-bold leading-30">27</view>
+        <view class="mt-24 text-28 text-fore-light leading-28">完成</view>
+      </view>
+      <view class="flex-1 text-center">
+        <view class="text-40 text-[#F59E0B] font-bold leading-30">10</view>
+        <view class="mt-24 text-28 text-fore-light leading-28">未完成</view>
+      </view>
+      <view class="flex-1 text-center">
+        <view class="text-40 text-[#F59E0B] font-bold leading-30">9</view>
+        <view class="mt-24 text-28 text-fore-light leading-28">未发送</view>
+      </view>
+    </view>
+    <view class="mt-70">
+      <ie-table :tableColumns="tableColumns" :data="tableData" :cellStyle="cellStyle" @rowClick="handleRowClick">
+        <template #name="{ item }: { item: StudentExamRecord }">
+          <text class="font-bold">{{ item.name }}</text>
+        </template>
+        <template #score="{ item }: { item: StudentExamRecord }">
+          <text class="font-bold">{{ item.score }}</text>
+        </template>
+        <template #status="{ item }: { item: StudentExamRecord }">
+          <text :class="getStatusClass(item.status)">{{ getStatusText(item.status) }}</text>
+        </template>
+      </ie-table>
+    </view>
+  </view>
 </template>
 <script lang="ts" setup>
+import { TableColumnConfig } from '@/types';
+import { StudentExamRecord, TeachClass } from '@/types/study';
+import { CSSProperties } from 'vue';
+
+const props = defineProps({
+  examType: {
+    type: String,
+    default: 'test'
+  }
+});
+const classId = ref<number>(0);
+const classList = ref<TeachClass[]>([]);
+const pickerRef = ref();
+const placeholderStyle = {
+  color: '#1A1A1A'
+}
+const tableColumns: TableColumnConfig[] = [
+  {
+    prop: 'name',
+    label: '姓名',
+    flex: 1,
+    slot: 'name',
+    headerAlign: 'center',
+    align: 'center'
+  },
+  {
+    prop: 'score',
+    label: '得分',
+    flex: 1,
+    slot: 'score'
+  },
+  {
+    prop: 'status',
+    label: '状态',
+    flex: 1,
+    slot: 'status'
+  }
+]
+const tableData = ref<StudentExamRecord[]>([
+  {
+    id: 1,
+    name: '张三',
+    score: 100,
+    status: 1
+  },
+  {
+    id: 2,
+    name: '李四',
+    score: 90,
+    status: 2
+  },
+  {
+    id: 3,
+    name: '王五',
+    score: 80,
+    status: 1
+  },
+  {
+    id: 4,
+    name: '赵六',
+    score: 70,
+    status: 1
+  },
+  {
+    id: 5,
+    name: '孙七',
+    score: 60,
+    status: 2
+  },
+  {
+    id: 6,
+    name: '周八',
+    score: 50,
+    status: 2
+  },
+  {
+    id: 7,
+    name: '吴九',
+    score: 40,
+    status: 1
+  }
+])
+const cellStyle: CSSProperties = {
+  padding: '30rpx 20rpx'
+}
+const handleClassChange = (value: number, selectedItem: TeachClass) => {
+  classId.value = value;
+}
+
+// 处理表格行点击事件
+const handleRowClick = (row: StudentExamRecord) => {
+  console.log('点击了行:', row);
+  // 这里可以添加跳转到详情页或其他逻辑
+}
+
+// 获取状态文本
+const getStatusText = (status: number) => {
+  const statusMap = {
+    1: '已完成',
+    2: '未完成',
+    3: '未发送'
+  };
+  return statusMap[status as keyof typeof statusMap] || '未知';
+}
+
+// 获取状态样式类
+const getStatusClass = (status: number) => {
+  const classMap = {
+    1: 'text-[#22C55E] font-bold',
+    2: 'text-[#F59E0B] font-bold',
+    3: 'text-[#6B7280] font-bold'
+  };
+  return classMap[status as keyof typeof classMap] || 'text-gray-500';
+}
+
+const loadData = async () => {
+  // const res = await getClassHistory();
+  // console.log(res);
+  uni.$ie.showLoading();
+  setTimeout(() => {
+    uni.$ie.hideLoading();
+  }, 1000);
+}
+const loadClassList = async () => {
+  // const res = await getClassList();
+  // console.log(res);
+}
 
+watch(() => props.examType, (newVal) => {
+  loadData();
+}, {
+  immediate: true
+});
 </script>
 <style lang="scss" scoped></style>

+ 20 - 10
src/pagesStudy/pages/study-history/components/exam-history.vue

@@ -1,6 +1,6 @@
 <template>
-  <view class="bg-white">
-    <view class="p-30 pb-40 py-20 flex gap-x-20">
+  <view class="flex-1 min-h-1 bg-white">
+    <view class="p-30 pb-20 py-20 flex gap-x-20">
       <view class="exam-type-item" :class="{ 'is-active': examType === 'test' }" @click="handleChangeExamType('test')">
         <ie-image src="/pagesStudy/static/image/icon-exam-test.png" custom-class="w-64 h-60" />
         <view class="exam-type-text">模拟仿真</view>
@@ -11,8 +11,8 @@
         <view class="exam-type-text">组卷作业</view>
       </view>
     </view>
-    <!-- <exam-history-student v-if="type === 'student'" />
-    <exam-history-teacher /> -->
+    <exam-history-student v-if="userType === 'student'" :exam-type="examType" />
+    <exam-history-teacher v-else-if="userType === 'teacher'" :exam-type="examType" />
   </view>
 </template>
 <script lang="ts" setup>
@@ -21,13 +21,9 @@ import examHistoryStudent from './exam-history-student.vue';
 import examHistoryTeacher from './exam-history-teacher.vue';
 const userType = ref('teacher');
 const examType = ref('test')
-const loadData = async () => {
-  // const res = await getExamHistory(userType.value, examType.value);
-  // console.log(res);
-}
+
 const handleChangeExamType = (type: string) => {
   examType.value = type;
-  loadData();
 }
 </script>
 <style lang="scss" scoped>
@@ -40,7 +36,21 @@ const handleChangeExamType = (type: string) => {
 }
 
 .is-active {
-  @apply bg-[#E6F7FF];
+  @apply bg-[#E6F7FF] relative;
+
+  &::after {
+    content: "";
+    display: block;
+    position: absolute;
+    bottom: -9px;
+    left: 50%;
+    transform: translateX(-50%);
+    width: 0;
+    height: 0;
+    border-left: 14px solid transparent;
+    border-right: 14px solid transparent;
+    border-top: 10px solid #E6F7FF;
+  }
 
   .exam-type-text {
     @apply text-primary;

+ 4 - 2
src/pagesStudy/pages/study-history/components/practice-history.vue

@@ -1,7 +1,9 @@
 <template>
-  <view></view>
+  <view class="flex-1 min-h-1 bg-white">
+    <practice-table />
+  </view>
 </template>
 <script lang="ts" setup>
-
+import practiceTable from '@/pagesStudy/components/practice-table.vue';
 </script>
 <style lang="scss" scoped></style>

+ 9 - 7
src/pagesStudy/pages/study-history/study-history.vue

@@ -1,15 +1,17 @@
 <template>
-  <ie-page bg-color="#F6F8FA">
+  <ie-page bg-color="#F6F8FA" :fix-height="true">
     <ie-navbar title="学习记录" />
-    <view class="bg-white sticky" :style="{ top: baseStickyTop + 'px' }">
+    <view class="bg-white sticky z-1" :style="{ top: baseStickyTop + 'px' }">
       <uv-tabs :list="list" :current="current" :activeStyle="activeStyle" :inactiveStyle="inactiveStyle"
         :scrollable="false" @change="handleTabChange"></uv-tabs>
     </view>
     <view class="h-20"></view>
-    <knowledge-history v-if="current === 0" />
-    <exam-history v-if="current === 1" />
-    <practice-history v-if="current === 2" />
-    <video-history v-if="current === 3" />
+    <!-- <view class="z-0"> -->
+      <knowledge-history v-if="current === 0" />
+      <exam-history v-if="current === 1" />
+      <practice-history v-if="current === 2" />
+      <video-history v-if="current === 3" />
+    <!-- </view> -->
   </ie-page>
 </template>
 
@@ -20,7 +22,7 @@ import examHistory from './components/exam-history.vue'
 import practiceHistory from './components/practice-history.vue'
 import videoHistory from './components/video-history.vue'
 const { baseStickyTop } = useNavbar();
-const current = ref(1);
+const current = ref(2);
 const list = [
   { name: '知识点记录', },
   { name: '试卷记录', },

+ 17 - 3
src/static/style/tailwind.scss

@@ -2,7 +2,7 @@
 @tailwind components;
 @tailwind utilities;
 
-@layer base {
+@layer utilities {
   .ellipsis {
     overflow: hidden;
     text-overflow: ellipsis;
@@ -29,8 +29,22 @@
       padding-#{$d}: env(safe-area-inset-#{$d});
     }
   }
-  
+
   .shadow-card {
     box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.08);
   }
-}
+
+  .sibling-border-top {
+    &+.sibling-border-top {
+      border-top: 1px solid #E5E5E5;
+    }
+  }
+
+  .sibling-border-bottom {
+    &+.sibling-border-bottom {
+      border-bottom: 1px solid #E5E5E5;
+    }
+  }
+}
+
+@layer base {}

+ 7 - 0
src/types/study.ts

@@ -19,4 +19,11 @@ export interface StudentStat {
   questionNum: number;
   dateNum: number;
   rate: number;
+}
+
+export interface StudentExamRecord {
+  id: number;
+  name: string;
+  score: number;
+  status: number;
 }

+ 3 - 15
src/utils/uni-tool.ts

@@ -122,24 +122,14 @@ const tool: IeTool = {
     });
   },
   showLoading(title: string = '') {
-    if (this.loading) {
-      return;
-    }
     this.loadingStartTime = Date.now();
     uni.showLoading({
       title,
       mask: true,
-      success: () => {
-        this.loading = true;
-      }
+      success: () => { }
     });
   },
   hideLoading() {
-    if (!this.loading) {
-      // 强制立即关闭loading,防止其他地方使用uni.showLoading()导致无法关闭
-      uni.hideLoading();
-      return;
-    }
     const currentTime = Date.now();
     const elapsedTime = currentTime - this.loadingStartTime;
     const remainingTime = Math.max(0, this.minLoadingTime - elapsedTime);
@@ -147,15 +137,13 @@ const tool: IeTool = {
     if (remainingTime > 0) {
       setTimeout(() => {
         uni.hideLoading();
-        this.loading = false;
       }, remainingTime);
     } else {
       uni.hideLoading();
-      this.loading = false;
     }
   },
   showModal(params: IModalOptions) {
-    const {title, content, showCancel, confirmText, cancelText, cancelColor, confirmColor} = Object.assign(defaultModalOptions, params);
+    const { title, content, showCancel, confirmText, cancelText, cancelColor, confirmColor } = Object.assign(defaultModalOptions, params);
     return new Promise((resolve, reject) => {
       uni.showModal({
         title,
@@ -175,7 +163,7 @@ const tool: IeTool = {
   copy(content: string) {
     uni.setClipboardData({
       data: content,
-      success: () => {}
+      success: () => { }
     });
   },
   formatPrice(price: number) {