Ver Fonte

合并h5代码

shmily1213 há 3 semanas atrás
pai
commit
3a09549790

+ 1 - 0
builddev.sh

@@ -0,0 +1 @@
+npm run build:custom dev

+ 1 - 0
buildprod.sh

@@ -0,0 +1 @@
+npm run build:custom pro

+ 4 - 4
package.json

@@ -70,10 +70,10 @@
   },
   "devDependencies": {
     "@dcloudio/types": "^3.4.8",
-    "@dcloudio/uni-automator": "3.0.0-4080520251106001",
-    "@dcloudio/uni-cli-shared": "3.0.0-4080520251106001",
-    "@dcloudio/uni-stacktracey": "3.0.0-4080520251106001",
-    "@dcloudio/vite-plugin-uni": "3.0.0-4080520251106001",
+    "@dcloudio/uni-automator": "3.0.0-4080720251210001",
+    "@dcloudio/uni-cli-shared": "3.0.0-4080720251210001",
+    "@dcloudio/uni-stacktracey": "3.0.0-4080720251210001",
+    "@dcloudio/vite-plugin-uni": "3.0.0-4080720251210001",
     "@types/node": "^24.1.0",
     "@uni-helper/vite-plugin-uni-tailwind": "^0.15.2",
     "@vue/runtime-core": "^3.4.21",

+ 1 - 0
rundev.sh

@@ -0,0 +1 @@
+npm run dev:custom dev

+ 1 - 0
runprod.sh

@@ -0,0 +1 @@
+npm run dev:custom pro

+ 1 - 1
src/components/ie-picker/ie-picker.vue

@@ -184,7 +184,7 @@ const handleChange = () => { }
 
 const handleClick = () => {
   emit('click');
-  if (props.disabled) {
+  if (props.disabled || props.readonly) {
     return;
   }
   if (!props.list.length) {

+ 15 - 10
src/pagesStudy/components/paper-work-item.vue

@@ -3,8 +3,7 @@
     <view class="border-bottom flex items-center justify-between py-32 px-25 leading-27">
       <view class="text-28 text-fore-light flex items-center">
         <view class="w-12 h-12 rounded-full bg-[#E5E5E5]"></view>
-        <view class="ml-10">{{ publishTime }}</view>
-        <view class="ml-20">发布人: {{ data.publishUser }}</view>
+        <view class="ml-10">{{ publishTime }}{{  `${data.publishUser ? `-${data.publishUser}` : ''}` }}</view>
       </view>
       <view :class="['text-28', data.state === EnumPaperWorkState.NOT_COMPLETED ? 'text-warning' : 'text-success']">
         {{ data.state === EnumPaperWorkState.NOT_COMPLETED ? '未完成' : '已完成' }}
@@ -12,7 +11,7 @@
     </view>
     <view class="px-46 py-30 relative">
       <view>
-        <text class="text-28 text-fore-title font-bold">{{ data.universityName }}-{{ data.majorName }}</text>
+        <text class="text-28 text-fore-title font-bold">{{ paperName }}</text>
         <text v-if="data.directed"
           class="ml-10 bg-[#F0FDF4] text-[#22C55E] border border-solid border-[#22C55E] text-20 rounded-4 px-10 py-2">定向</text>
       </view>
@@ -23,17 +22,17 @@
         </view>
         <view class="mt-14">
           <text class="text-fore-light">科目/批次:</text>
-          <text class="text-fore-title">{{ batchName }}</text>
+          <text class="text-fore-title">{{ batchName || '-' }}</text>
         </view>
         <view class="mt-14">
           <text class="text-fore-light">所属校区:</text>
-          <text class="text-fore-title">{{ data.campusName }}</text>
+          <text class="text-fore-title">{{ data.campusName || '-' }}</text>
         </view>
       </view>
       <view v-if="data.state === EnumPaperWorkState.COMPLETED"
         class="mt-20 bg-[#FFFBEB] py-18 px-20 rounded-5 text-24 text-[#F59E0B] flex items-center justify-between border border-solid border-[#FEF6DA]">
-        <text>提交时间:{{ data.endTime }}</text>
-        <text>做题时长:{{ formatSeconds(data.duration) }}</text>
+        <text>提交时间:{{ data.endTime || '-' }}</text>
+        <text>做题时长:{{ formatSeconds(data.duration) || '-' }}</text>
       </view>
       <view class="absolute right-50 top-100">
         <view v-if="data.state === EnumPaperWorkState.COMPLETED"
@@ -55,10 +54,16 @@ const { transferTo } = useTransferPage();
 const props = defineProps<{
   data: Study.PaperWork;
 }>();
-
+const paperName = computed(() => {
+  const { universityName, majorName, name } = props.data;
+  if (universityName && majorName) {
+    return `${universityName}-${majorName}`;
+  }
+  return name;
+});
 const batchName = computed(() => {
-  const names = props.data.name.split('_');
-  return `${names[1]}(${names[0]})`
+  const { subjectName, batchName } = props.data;
+  return `${subjectName}(${batchName || ''})`;
 });
 const publishTime = computed(() => {
   return uni.$ie.formatTime(props.data.publishTime, 'yyyy年mm月dd日 hh:MM:ss');

+ 5 - 2
src/pagesStudy/pages/homework/homework.vue

@@ -74,13 +74,16 @@ const handleTabChange = (item: any) => {
 }
 const handleQuery = (page: number, pageSize: number) => {
   uni.$ie.showLoading();
-  const params = {} as { state?: EnumPaperWorkState };
+  const params = {
+    pageNum: 1,
+    pageSize: 10000
+  } as { state?: EnumPaperWorkState };
   const state = tabList.value[currentTab.value].state;
   if (state) {
     params.state = state;
   }
   getPaperWorkList(params).then(res => {
-    pagingRef.value.complete(res.rows);
+    pagingRef.value.completeByTotal(res.rows, res.total);
   }).catch(err => {
     pagingRef.value.complete(false);
   }).finally(() => {

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

@@ -48,7 +48,7 @@ const menus = computed(() => [
     label: '学习记录',
     icon: '/menu/menu-record.png',
     pageUrl: '/pagesStudy/pages/study-history/study-history',
-    visible: userStore.isStudent
+    visible: true
   }
 ])
 const navigateTo = (menu: MenuItem) => {

+ 5 - 5
src/pagesStudy/pages/study-history/components/exam-history-paperwork.vue

@@ -205,11 +205,11 @@ const loadData = async () => {
   const queryCondition = getTeacherTestRecordCondition({}).then(res => {
     const { buildType, buildStatus, batchId, classId, subjectId } = res.data;
     queryForm.value = {
-      buildType: buildType || buildTypeList.value[0].value,
-      buildStatus: buildStatus || buildStatusList.value[0].value,
-      batchId: batchId || batchList.value[0].batchId,
-      classId: classId || classList.value[0].classId,
-      subjectId: subjectId || subjectList.value[0].subjectId,
+      buildType: buildType || buildTypeList.value[0]?.value,
+      buildStatus: buildStatus || buildStatusList.value[0]?.value,
+      batchId: batchId || batchList.value[0]?.batchId,
+      classId: classId || classList.value[0]?.classId,
+      subjectId: subjectId || subjectList.value[0]?.subjectId,
     };
   });
   await Promise.all([queryCondition]);

+ 2 - 1
src/pagesStudy/pages/study-history/components/knowledge-history-teacher.vue

@@ -27,12 +27,13 @@ const studentStatData = ref<StudentPlanStudyRecord[]>([]);
 const averageRate = ref(0);
 
 const loadData = async () => {
+  uni.$ie.showLoading();
   getClassKnowledgeRecord({
     classId: props.teachClass.classId,
   }).then(res => {
     studentStatData.value = res.data.list;
     averageRate.value = res.data.rate;
-  })
+  }).finally(() => uni.$ie.hideLoading());
 }
 
 const handleRowClick = (row: StudentPlanStudyRecord) => {

+ 3 - 3
src/pagesStudy/pages/study-history/components/student-stat-table.vue

@@ -2,9 +2,9 @@
   <view>
     <ie-table :table-columns="tableColumns" :table-config="tableConfig" :data="data" @rowClick="handleRowClick">
       <template #name="{ item }">
-        <view class="flex items-center justify-center">
-          <ie-image :src="item.avatar" class="w-60 h-60 bg-back" :round="999" />
-          <text class="ml-10 flex-1 min-w-1 ellipsis-1">{{ item.name }}</text>
+        <view class="flex items-center justify-center gap-10">
+          <!-- <ie-image :src="item.avatar" class="w-60 h-60 bg-back" :round="999" /> -->
+          <text class="flex-1 min-w-1 ellipsis-1">{{ item.name }}</text>
         </view>
       </template>
       <template #rate="{ item }">

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

@@ -3,7 +3,7 @@
     <ie-table :table-columns="tableColumns" :data="data" @rowClick="handleRowClick">
       <template #name="{ item }">
         <view class="flex items-center justify-center">
-          <ie-image :src="item.avatar" class="w-60 h-60 bg-back" :round="999" />
+          <!-- <ie-image :src="item.avatar" class="w-60 h-60 bg-back" :round="999" /> -->
           <text class="ml-10 flex-1 min-w-1 ellipsis-1">{{ item.name }}</text>
         </view>
       </template>

+ 35 - 38
src/pagesStudy/pages/targeted-add/targeted-add.vue

@@ -33,41 +33,30 @@
       </view>
     </ie-safe-toolbar>
   </ie-page>
-  <!-- #ifdef H5 -->
-  <teleport to="body">
-    <!-- #endif -->
-    <!-- #ifdef MP-WEIXIN -->
-    <root-portal externalClass="theme-ie">
-      <!-- #endif -->
-      <uv-popup ref="popupRef" mode="bottom" :round="16" popup-class="theme-ie" :close-on-click-overlay="false">
-        <ie-popup-toolbar title="选择专业" @cancel="handleCancel" @confirm="handleConfirm" />
-        <view class="h-[50vh] bg-white px-30 pt-20 flex flex-col">
-          <view>
-            <uv-search v-model="keyword" shape="square" :showAction="false" placeholder="请输入专业名称" />
-          </view>
-          <view class="mt-20 flex-1 min-h-1">
-            <scroll-view scroll-y class="h-full">
-              <view v-for="item in filteredMajorList" :key="item.id"
-                class="px-20 py-16 bg-white sibling-border-top flex items-center" @click="handleSelect(item)">
-                <view class="flex-1 min-w-1 flex-shrink-0">
-                  <view class="text-30 text-fore-title" :class="[isActive(item) ? 'text-primary' : 'text-fore-title']">
-                    {{ item.name }}
-                  </view>
-                  <view class="mt-6 text-22 text-fore-light"
-                    :class="[isActive(item) ? 'text-primary' : 'text-fore-light']">{{ item.ancestors }}</view>
-                </view>
-                <uv-icon v-if="isActive(item)" name="checkmark" size="20" color="#31A0FC" />
+  <ie-popup ref="popupRef" title="选择专业" @confirm="handleConfirm">
+    <view class="h-[50vh] bg-white px-30 pt-20 flex flex-col">
+      <view>
+        <uv-search v-model="keyword" shape="square" :showAction="false" placeholder="请输入专业名称" />
+      </view>
+      <view class="mt-20 flex-1 min-h-1">
+        <scroll-view scroll-y class="h-full">
+          <view v-for="item in filteredMajorList" :key="item.id"
+            class="px-20 py-16 bg-white sibling-border-top flex items-center gap-x-20" @click="handleSelect(item)">
+            <view class="flex-1 min-w-1 flex-shrink-0">
+              <view class="flex items-center gap-x-10 text-fore-title">
+                <span class="text-30 " :class="[isActive(item) ? 'text-primary' : 'text-fore-title']">{{ item.name }}</span>
+                <span v-if="item.notice" class="text-22 text-fore-light flex-1 min-w-1 ellipsis-1"  :class="[isActive(item) ? 'text-primary' : 'text-fore-title']">{{ `(${item.notice})` }}</span>
               </view>
-            </scroll-view>
+              <view class="mt-6 text-22 text-fore-light" :class="[isActive(item) ? 'text-primary' : 'text-fore-light']">
+                {{
+                  item.ancestors }}</view>
+            </view>
+            <uv-icon v-if="isActive(item)" name="checkmark" size="20" color="#31A0FC" />
           </view>
-        </view>
-      </uv-popup>
-      <!-- #ifdef MP-WEIXIN -->
-    </root-portal>
-    <!-- #endif -->
-    <!-- #ifdef H5 -->
-  </teleport>
-  <!-- #endif -->
+        </scroll-view>
+      </view>
+    </view>
+  </ie-popup>
 </template>
 
 <script lang="ts" setup>
@@ -112,18 +101,26 @@ const handleUniversitySelect = () => {
 }
 
 const popupRef = ref();
-const selectedMajor = ref<UniversityMajor | null>(null);
+const selectedMajor = ref<Pick<UniversityMajor, 'ancestors' | 'code' | 'id' | 'name'> | null>(null);
 const handleMajorSelect = () => {
   if (!form.value.universityId) {
     uni.$ie.showToast('请选择院校');
     return;
   }
   keyword.value = '';
+  if (form.value.majorId) {
+    selectedMajor.value = {
+      ancestors: form.value.majorAncestors || '',
+      code: form.value.majorId || '',
+      id: Number(form.value.majorId),
+      name: form.value.majorName || ''
+    }
+  } else {
+    selectedMajor.value = null;
+  }
   popupRef.value.open();
 }
-const handleCancel = () => {
-  popupRef.value.close();
-}
+
 const handleConfirm = () => {
   if (!selectedMajor.value) {
     uni.$ie.showToast('请选择专业');
@@ -134,7 +131,7 @@ const handleConfirm = () => {
   form.value.majorName = selectedMajor.value.name;
   popupRef.value.close();
 }
-const handleSelect = (item: any) => {
+const handleSelect = (item: UniversityMajor) => {
   selectedMajor.value = item;
 }
 

+ 17 - 2
src/pagesSystem/pages/edit-profile/edit-profile.vue

@@ -52,7 +52,8 @@
             <ie-image v-if="form.examType === EnumExamType.OHS" slot="right" src="/static/image/icon-lock.png"
               custom-class="w-24 h-30" mode="aspectFill" />
           </uv-form-item>
-          <uv-form-item label="外语" prop="name" :borderBottom="form.examType === EnumExamType.OHS || form.examType === EnumExamType.SVS">
+          <uv-form-item label="外语" prop="name"
+            :borderBottom="form.examType === EnumExamType.OHS || form.examType === EnumExamType.SVS">
             <uv-input v-model="scores.foreign" border="none"
               :placeholder="form.examType === EnumExamType.OHS ? '' : '请输入'" placeholderClass="text-30"
               font-size="30rpx" :custom-style="customStyle" :readonly="form.examType === EnumExamType.OHS">
@@ -98,7 +99,12 @@
             </uv-form-item>
             <uv-form-item label="所在班级" prop="form.name">
               <ie-picker ref="pickerRef" v-model="form.classId" :list="classList" title="选择班级" placeholder="请选择所在班级"
-                :custom-style="customStyle" key-label="name" key-value="classId"></ie-picker>
+                :custom-style="customStyle" key-label="name" key-value="classId" :disabled="schoolClassDisabled"
+                :readonly="pickerDisabled">
+              </ie-picker>
+              <template #right v-if="pickerDisabled">
+                <ie-image src="/static/image/icon-lock.png" custom-class="w-24 h-30" mode="aspectFill" />
+              </template>
             </uv-form-item>
           </content-card>
         </template>
@@ -170,6 +176,15 @@ const customStyle = {
   paddingLeft: '26px'
 };
 
+const pickerDisabled = computed(() => {
+  const { classSelect } = form.value;
+  return classSelect !== 1;
+});
+const schoolClassDisabled = computed(() => {
+  const { schoolId } = form.value;
+  return schoolId === undefined || schoolId === null || pickerDisabled.value;
+});
+
 const handleSubmit = async () => {
   uni.$ie.showLoading();
   const params = {

+ 3 - 1
src/types/study.ts

@@ -338,7 +338,8 @@ export interface UniversityMajor {
   code: string;
   id: number;
   name: string;
-  type: string
+  type: string;
+  notice?: string;
 }
 
 // 
@@ -452,4 +453,5 @@ export interface PaperWork {
   universityName: string;
   endTime: string;
   duration: number;
+  batchName: string;
 }

+ 1 - 0
src/types/user.ts

@@ -158,6 +158,7 @@ export interface UserInfo {
   schoolId?: number;
   campusClassName?: string;
   campusName?: string;
+  classSelect: number; // 0: 不可修改班级 1: 可修改班级
 }
 
 export interface VipCardInfo {

+ 120 - 99
src/uni_modules/mp-html/components/mp-html/latex/index.js

@@ -13,113 +13,134 @@ Latex.prototype.onParse = function (node, vm) {
   // $...$、$$...$$、\(...\)、\[...\]包裹的内容为latex公式
   if (!vm.options.editable && node.type === 'text' && 
       (node.text.includes('$') || node.text.includes('\\(') || node.text.includes('\\['))) {
-    console.log(node.text)
-    // 同时匹配 $、$$、\(、\)、\[、\] 分隔符
-    const part = node.text.split(/(\$\$|\$|\\\(|\\\)|\\\[|\\\])/)
+    console.log('原始文本:', node.text)
+    
     const children = []
-    let status = 0 // 0-普通文本, 1-行内公式开始, 2-块公式开始
-    let formulaType = '' // 记录公式的起始分隔符类型
-    let formulaContent = '' // 临时存储公式内容
+    let remaining = node.text
     
-    for (let i = 0; i < part.length; i++) {
-      if (i % 2 === 0) {
-        // 文本内容
-        if (part[i]) {
-          if (status === 0) {
-            // 普通文本
-            children.push({
-              type: 'text',
-              text: part[i]
-            })
-          } else {
-            // 在公式内部,累积内容
-            formulaContent += part[i]
-          }
+    while (remaining.length > 0) {
+      // 查找所有可能的起始符位置
+      let nextDelimiters = [
+        { type: '$$', start: remaining.indexOf('$$'), isBlock: true, end: '$$' },
+        { type: '$', start: remaining.indexOf('$'), isBlock: false, end: '$' },
+        { type: '\\(', start: remaining.indexOf('\\('), isBlock: false, end: '\\)' },
+        { type: '\\[', start: remaining.indexOf('\\['), isBlock: true, end: '\\]' }
+      ].filter(d => d.start !== -1).sort((a, b) => {
+        // 先按位置排序
+        if (a.start !== b.start) return a.start - b.start
+        // 位置相同时,优先匹配更长的($$ 优先于 $)
+        return b.type.length - a.type.length
+      })
+      
+      if (nextDelimiters.length === 0) {
+        // 没有找到任何公式标记,剩余部分都是普通文本
+        if (remaining) {
+          children.push({
+            type: 'text',
+            text: remaining
+          })
         }
-      } else {
-        // 分隔符
-        const delimiter = part[i]
-        
-        if (status === 0) {
-          // 当前不在公式内,这是起始分隔符
-          if (delimiter === '$') {
-            status = 1
-            formulaType = '$'
-            formulaContent = ''
-          } else if (delimiter === '$$') {
-            status = 2
-            formulaType = '$$'
-            formulaContent = ''
-          } else if (delimiter === '\\(') {
-            status = 1
-            formulaType = '\\('
-            formulaContent = ''
-          } else if (delimiter === '\\[') {
-            status = 2
-            formulaType = '\\['
-            formulaContent = ''
-          } else {
-            // 没有起始符的结束符,当作普通文本
-            children.push({
-              type: 'text',
-              text: delimiter
-            })
-          }
+        break
+      }
+      
+      const delimiter = nextDelimiters[0]
+      
+      // 添加起始符之前的文本
+      if (delimiter.start > 0) {
+        children.push({
+          type: 'text',
+          text: remaining.substring(0, delimiter.start)
+        })
+      }
+      
+      // 查找对应的结束符
+      const contentStart = delimiter.start + delimiter.type.length
+      let endPos = remaining.indexOf(delimiter.end, contentStart)
+      
+      if (endPos === -1) {
+        // 没有找到结束符,将起始符当作普通文本
+        children.push({
+          type: 'text',
+          text: remaining.substring(delimiter.start, contentStart)
+        })
+        remaining = remaining.substring(contentStart)
+        continue
+      }
+      
+      // 提取公式内容
+      let formulaContent = remaining.substring(contentStart, endPos)
+      console.log('提取的原始公式内容:', formulaContent, '分隔符类型:', delimiter.type)
+      
+      // 清理公式内容:移除内部嵌套的数学分隔符
+      // 如果外层用 \(...\) 或 \[...\],需要移除内部的 $...$ 和 $$...$$
+      // 如果外层用 $...$ 或 $$...$$,需要移除内部的 \(...\) 和 \[...\]
+      if (delimiter.type === '\\(' || delimiter.type === '\\[') {
+        // 外层是 LaTeX 标准格式,移除内部的 $ 分隔符
+        // 匹配 $...$ 和 $$...$$ 并只保留内容
+        formulaContent = formulaContent.replace(/\$\$(.*?)\$\$/g, '$1')  // 先处理 $$
+        formulaContent = formulaContent.replace(/\$(.*?)\$/g, '$1')      // 再处理 $
+        console.log('清理后的公式内容:', formulaContent)
+      } else if (delimiter.type === '$' || delimiter.type === '$$') {
+        // 外层是 $ 格式,移除内部的 \(...\) 和 \[...\]
+        formulaContent = formulaContent.replace(/\\\[(.*?)\\\]/g, '$1')  // 先处理 \[...\]
+        formulaContent = formulaContent.replace(/\\\((.*?)\\\)/g, '$1')  // 再处理 \(...\)
+        console.log('清理后的公式内容:', formulaContent)
+      }
+      
+      // 容错处理:修正常见的 LaTeX 语法错误
+      // \pi 后面直接跟字母的情况,添加空格
+      formulaContent = formulaContent.replace(/\\pi([a-zA-Z])/g, '\\pi $1')
+      // 其他常见的希腊字母也做类似处理
+      formulaContent = formulaContent.replace(/\\alpha([a-zA-Z])/g, '\\alpha $1')
+      formulaContent = formulaContent.replace(/\\beta([a-zA-Z])/g, '\\beta $1')
+      formulaContent = formulaContent.replace(/\\gamma([a-zA-Z])/g, '\\gamma $1')
+      formulaContent = formulaContent.replace(/\\delta([a-zA-Z])/g, '\\delta $1')
+      formulaContent = formulaContent.replace(/\\epsilon([a-zA-Z])/g, '\\epsilon $1')
+      formulaContent = formulaContent.replace(/\\theta([a-zA-Z])/g, '\\theta $1')
+      formulaContent = formulaContent.replace(/\\lambda([a-zA-Z])/g, '\\lambda $1')
+      formulaContent = formulaContent.replace(/\\mu([a-zA-Z])/g, '\\mu $1')
+      formulaContent = formulaContent.replace(/\\sigma([a-zA-Z])/g, '\\sigma $1')
+      formulaContent = formulaContent.replace(/\\omega([a-zA-Z])/g, '\\omega $1')
+      formulaContent = formulaContent.replace(/\\Omega([a-zA-Z])/g, '\\Omega $1')
+      
+      // 渲染公式
+      try {
+        if (delimiter.isBlock) {
+          // 块公式
+          const nodes = parse.default(formulaContent, {
+            displayMode: true
+          })
+          children.push({
+            name: 'div',
+            attrs: {
+              style: 'text-align:center'
+            },
+            children: nodes
+          })
         } else {
-          // 当前在公式内,检查是否是对应的结束分隔符
-          let isClosing = false
-          
-          if ((formulaType === '$' && delimiter === '$') ||
-              (formulaType === '$$' && delimiter === '$$') ||
-              (formulaType === '\\(' && delimiter === '\\)') ||
-              (formulaType === '\\[' && delimiter === '\\]')) {
-            isClosing = true
-          }
-          
-          if (isClosing) {
-            // 找到匹配的结束符,渲染公式
-            if (status === 1) {
-              // 行内公式
-              const nodes = parse.default(formulaContent, {})
-              children.push({
-                name: 'span',
-                attrs: {},
-                l: 'T',
-                f: 'display:inline-block',
-                children: nodes
-              })
-            } else if (status === 2) {
-              // 块公式
-              const nodes = parse.default(formulaContent, {
-                displayMode: true
-              })
-              children.push({
-                name: 'div',
-                attrs: {
-                  style: 'text-align:center'
-                },
-                children: nodes
-              })
-            }
-            // 重置状态
-            status = 0
-            formulaType = ''
-            formulaContent = ''
-          } else {
-            // 不是匹配的结束符,将分隔符作为公式内容的一部分
-            formulaContent += delimiter
-          }
+          // 行内公式
+          const nodes = parse.default(formulaContent, {})
+          children.push({
+            name: 'span',
+            attrs: {},
+            l: 'T',
+            f: 'display:inline-block',
+            children: nodes
+          })
         }
+      } catch (e) {
+        console.error('KaTeX解析错误:', e, '公式内容:', formulaContent)
+        // 解析失败,当作普通文本
+        children.push({
+          type: 'text',
+          text: delimiter.type + remaining.substring(contentStart, endPos) + delimiter.end
+        })
       }
+      
+      // 继续处理剩余部分
+      remaining = remaining.substring(endPos + delimiter.end.length)
     }
     
-    // 如果还有未闭合的公式内容,作为普通文本处理
-    if (status !== 0 && formulaContent) {
-      children.push({
-        type: 'text',
-        text: (formulaType === '\\(' ? '\\(' : formulaType === '\\[' ? '\\[' : formulaType) + formulaContent
-      })
-    }
     delete node.type
     delete node.text
     node.name = 'span'

+ 8 - 1
vite.config.js

@@ -25,7 +25,14 @@ export default defineConfig(({ mode }) => ({
     }
   },
   server: {
-    port: 5174
+    port: 5174,
+    proxy: {
+      '/api': {
+        target: 'http://localhost:8080',
+        changeOrigin: true,
+        rewrite: (path) => path.replace(/^\/api/, '')
+      }
+    }
   },
   plugins: [
     viteCompression({