Просмотр исходного кода

Merge branch 'master' of http://49.234.186.218:9000/root/ieplus

mingfu 1 неделя назад
Родитель
Сommit
62382d10d2
2 измененных файлов с 474 добавлено и 116 удалено
  1. 144 12
      back-ui/src/components/Editor/index.vue
  2. 330 104
      back-ui/src/views/learn/questions/index.vue

+ 144 - 12
back-ui/src/components/Editor/index.vue

@@ -1,13 +1,11 @@
 <template>
   <div>
     <el-upload
-      :action="uploadUrl"
+      :http-request="customUpload"
       :before-upload="handleBeforeUpload"
-      :on-success="handleUploadSuccess"
       :on-error="handleUploadError"
       name="file"
       :show-file-list="false"
-      :headers="headers"
       class="editor-img-uploader"
       v-if="type == 'url'"
     >
@@ -31,11 +29,12 @@ import axios from 'axios'
 import { QuillEditor } from "@vueup/vue-quill"
 import "@vueup/vue-quill/dist/vue-quill.snow.css"
 import { getToken } from "@/utils/auth"
+import request from "@/utils/request"
 
 const { proxy } = getCurrentInstance()
 
 const quillEditorRef = ref()
-const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload") // 上传的图片服务器地址
+const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload") // 保留作为备用
 const headers = ref({
   Authorization: "Bearer " + getToken()
 })
@@ -110,6 +109,13 @@ const content = ref("")
 watch(() => props.modelValue, (v) => {
   if (v !== content.value) {
     content.value = v == undefined ? "<p></p>" : v
+    // 内容更新后,为所有图片添加跨域和防盗链属性
+    nextTick(() => {
+      if (quillEditorRef.value && props.type == 'url') {
+        const quill = quillEditorRef.value.getQuill()
+        addImageAttributes(quill.root)
+      }
+    })
   }
 }, { immediate: true })
 
@@ -126,9 +132,40 @@ onMounted(() => {
       }
     })
     quill.root.addEventListener('paste', handlePasteCapture, true)
+    
+    // 监听编辑器内容变化,为所有图片添加跨域和防盗链属性
+    quill.on('text-change', () => {
+      addImageAttributes(quill.root)
+    })
+    
+    // 初始化时也为已有图片添加属性
+    nextTick(() => {
+      addImageAttributes(quill.root)
+    })
   }
 })
 
+// 为编辑器中的所有图片添加跨域和防盗链属性
+function addImageAttributes(rootElement) {
+  const images = rootElement.querySelectorAll('img')
+  images.forEach(img => {
+    // 如果图片还没有这些属性,则添加
+    if (!img.hasAttribute('crossorigin')) {
+      img.setAttribute('crossorigin', 'anonymous')
+    }
+    if (!img.hasAttribute('referrerpolicy')) {
+      img.setAttribute('referrerpolicy', 'no-referrer')
+    }
+    // 确保图片样式正常
+    if (!img.style.maxWidth) {
+      img.style.maxWidth = '100%'
+      img.style.height = 'auto'
+      img.style.display = 'block'
+      img.style.margin = '10px 0'
+    }
+  })
+}
+
 // 上传前校检格式和大小
 function handleBeforeUpload(file) {
   const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"]
@@ -149,6 +186,73 @@ function handleBeforeUpload(file) {
   return true
 }
 
+// 自定义上传方法 - 通过后端获取签名后上传到OSS
+async function customUpload(options) {
+  const file = options.file
+  
+  try {
+    // 1. 从后端获取OSS签名
+    const signatureResponse = await request({
+      url: '/common/oss/signature',
+      method: 'get',
+      params: {
+        dir: 'questions/images/'
+      }
+    })
+    
+    if (signatureResponse.code !== 200 || !signatureResponse.data) {
+      throw new Error(signatureResponse.msg || '获取OSS签名失败')
+    }
+    
+    const signature = signatureResponse.data
+    
+    // 2. 生成文件路径
+    const filePath = generateFilePath(file, signature.dir)
+    
+    // 3. 构建FormData,使用PostObject方式上传
+    const formData = new FormData()
+    formData.append('key', filePath)
+    formData.append('policy', signature.policy)
+    formData.append('OSSAccessKeyId', signature.accessKeyId)
+    formData.append('signature', signature.signature)
+    formData.append('file', file)
+    
+    // 4. 上传到OSS
+    const response = await fetch(signature.host, {
+      method: 'POST',
+      body: formData
+    })
+    
+    if (response.ok || response.status === 204) {
+      // 上传成功,返回OSS URL
+      const ossUrl = `${signature.host}/${filePath}`
+      handleUploadSuccess({
+        code: 200,
+        fileName: filePath,
+        url: ossUrl
+      }, file)
+    } else {
+      const errorText = await response.text()
+      throw new Error(`上传失败: ${response.status} ${errorText}`)
+    }
+  } catch (error) {
+    console.error('OSS上传失败:', error)
+    proxy.$modal.msgError('上传图片失败: ' + (error.message || '未知错误'))
+  }
+}
+
+// 生成文件路径
+function generateFilePath(file, dir) {
+  const date = new Date()
+  const year = date.getFullYear()
+  const month = String(date.getMonth() + 1).padStart(2, '0')
+  const day = String(date.getDate()).padStart(2, '0')
+  const timestamp = Date.now()
+  const random = Math.random().toString(36).substring(2, 15)
+  const ext = file.name.substring(file.name.lastIndexOf('.'))
+  return `${dir}${year}/${month}/${day}/${timestamp}_${random}${ext}`
+}
+
 // 上传成功处理
 function handleUploadSuccess(res, file) {
   // 如果上传成功
@@ -157,8 +261,25 @@ function handleUploadSuccess(res, file) {
     let quill = toRaw(quillEditorRef.value).getQuill()
     // 获取光标位置
     let length = quill.selection.savedRange.index
-    // 插入图片,res.url为服务器返回的图片链接地址
-    quill.insertEmbed(length, "image", import.meta.env.VITE_APP_BASE_API + res.fileName)
+    // 插入图片,使用OSS的完整URL
+    const imageUrl = res.url || (import.meta.env.VITE_APP_BASE_API + res.fileName)
+    quill.insertEmbed(length, "image", imageUrl)
+    
+    // 等待图片插入后,为其添加跨域和防盗链属性
+    nextTick(() => {
+      const images = quill.root.querySelectorAll('img')
+      images.forEach(img => {
+        if (img.src === imageUrl || img.src.includes(imageUrl)) {
+          img.setAttribute('crossorigin', 'anonymous')
+          img.setAttribute('referrerpolicy', 'no-referrer')
+          img.style.maxWidth = '100%'
+          img.style.height = 'auto'
+          img.style.display = 'block'
+          img.style.margin = '10px 0'
+        }
+      })
+    })
+    
     // 调整光标到最后
     quill.setSelection(length + 1)
   } else {
@@ -186,12 +307,14 @@ function handlePasteCapture(e) {
   }
 }
 
-function insertImage(file) {
-  const formData = new FormData()
-  formData.append("file", file)
-  axios.post(uploadUrl.value, formData, { headers: { "Content-Type": "multipart/form-data", Authorization: headers.value.Authorization } }).then(res => {
-    handleUploadSuccess(res.data)
-  })
+async function insertImage(file) {
+  try {
+    // 使用OSS上传
+    await customUpload({ file })
+  } catch (error) {
+    console.error('粘贴图片上传失败:', error)
+    proxy.$modal.msgError('上传图片失败')
+  }
 }
 </script>
 
@@ -203,6 +326,15 @@ function insertImage(file) {
   white-space: pre-wrap !important;
   line-height: normal !important;
 }
+
+/* 确保富文本编辑器中的图片正常显示 */
+.editor :deep(img) {
+  max-width: 100% !important;
+  height: auto !important;
+  display: block !important;
+  margin: 10px 0 !important;
+  border-radius: 4px;
+}
 .quill-img {
   display: none;
 }

+ 330 - 104
back-ui/src/views/learn/questions/index.vue

@@ -196,6 +196,9 @@
                     <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
                                v-hasPermi="['learn:questions:edit']">修改
                     </el-button>
+                    <el-button link type="primary" icon="" @click="handleTextUpdate(scope.row)"
+                               v-hasPermi="['learn:questions:edit']">文本修改
+                    </el-button>
                     <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
                                v-hasPermi="['learn:questions:remove']">删除
                     </el-button>
@@ -207,106 +210,184 @@
                     v-model:limit="queryParams.pageSize" @pagination="getList"/>
 
         <!-- 添加或修改试题对话框 -->
-        <el-dialog :title="title" v-model="open" width="700px" append-to-body>
-            <el-form ref="questionsRef" :model="form" :rules="rules" label-width="140px">
-                <el-form-item label="题型" prop="qtpye">
-                    <el-input v-model="form.qtpye" type="text" placeholder="请输入内容"/>
-                </el-form-item>
-                <el-form-item label="试题-题干" prop="title">
-                    <el-input v-model="form.title" type="textarea" placeholder="请输入内容"/>
-                </el-form-item>
-                <el-form-item label="选项A" prop="optionA">
-                    <el-input v-model="form.optionA" type="textarea" placeholder="请输入内容"/>
-                </el-form-item>
-                <el-form-item label="选项B" prop="optionB">
-                    <el-input v-model="form.optionB" type="textarea" placeholder="请输入内容"/>
-                </el-form-item>
-                <el-form-item label="选项C" prop="optionC">
-                    <el-input v-model="form.optionC" type="textarea" placeholder="请输入内容"/>
-                </el-form-item>
-                <el-form-item label="选项D" prop="optionD">
-                    <el-input v-model="form.optionD" type="textarea" placeholder="请输入内容"/>
-                </el-form-item>
-                <el-form-item label="选项E" prop="optionE">
-                    <el-input v-model="form.optionE" type="textarea" placeholder="请输入内容"/>
-                </el-form-item>
-                <el-form-item label="选项D" prop="optionF">
-                    <el-input v-model="form.optionF" type="textarea" placeholder="请输入内容"/>
-                </el-form-item>
-                <el-form-item label="选项E" prop="optionG">
-                    <el-input v-model="form.optionG" type="textarea" placeholder="请输入内容"/>
-                </el-form-item>
-                <el-form-item label="标准答案" prop="answer1">
-                    <el-input v-model="form.answer1" type="textarea" placeholder="请输入内容"/>
-                </el-form-item>
-                <el-form-item label="非标准答案" prop="answer2">
-                    <el-input v-model="form.answer2" type="textarea" placeholder="请输入内容"/>
-                </el-form-item>
-                <el-form-item label="类型" prop="qtpye">
-                    <!-- <el-input v-model="form.qtpye" placeholder="请输入类型" /> -->
+        <el-dialog :title="title" v-model="open" width="1200px" append-to-body>
+            <div class="form-content-wrapper">
+                <el-form ref="questionsRef" :model="form" :rules="rules" label-width="140px">
+                <!-- 只读显示区域:题目标题和选项(仅在修改时显示) -->
+                <div class="readonly-section" v-if="form.id != null">
+                    <el-form-item label="标题">
+                        <div class="readonly-content" v-html="formatContentWithImages(form.title) || '-'"></div>
+                    </el-form-item>
+                    <el-row :gutter="20" v-if="form.optionA || form.optionB || form.optionC || form.optionD || form.optionE || form.optionF || form.optionG">
+                        <el-col :span="12" v-if="form.optionA">
+                            <el-form-item label="选项A">
+                                <div class="readonly-content" v-html="formatContentWithImages(form.optionA)"></div>
+                            </el-form-item>
+                        </el-col>
+                        <el-col :span="12" v-if="form.optionB">
+                            <el-form-item label="选项B">
+                                <div class="readonly-content" v-html="formatContentWithImages(form.optionB)"></div>
+                            </el-form-item>
+                        </el-col>
+                        <el-col :span="12" v-if="form.optionC">
+                            <el-form-item label="选项C">
+                                <div class="readonly-content" v-html="formatContentWithImages(form.optionC)"></div>
+                            </el-form-item>
+                        </el-col>
+                        <el-col :span="12" v-if="form.optionD">
+                            <el-form-item label="选项D">
+                                <div class="readonly-content" v-html="formatContentWithImages(form.optionD)"></div>
+                            </el-form-item>
+                        </el-col>
+                        <el-col :span="12" v-if="form.optionE">
+                            <el-form-item label="选项E">
+                                <div class="readonly-content" v-html="formatContentWithImages(form.optionE)"></div>
+                            </el-form-item>
+                        </el-col>
+                        <el-col :span="12" v-if="form.optionF">
+                            <el-form-item label="选项F">
+                                <div class="readonly-content" v-html="formatContentWithImages(form.optionF)"></div>
+                            </el-form-item>
+                        </el-col>
+                        <el-col :span="12" v-if="form.optionG">
+                            <el-form-item label="选项G">
+                                <div class="readonly-content" v-html="formatContentWithImages(form.optionG)"></div>
+                            </el-form-item>
+                        </el-col>
+                    </el-row>
+                    <el-row :gutter="20" v-if="form.answer1 || form.answer2">
+                        <el-col :span="12" v-if="form.answer1">
+                            <el-form-item label="答案1">
+                                <div class="readonly-content" v-html="formatContentWithImages(form.answer1)"></div>
+                            </el-form-item>
+                        </el-col>
+                        <el-col :span="12" v-if="form.answer2">
+                            <el-form-item label="答案2">
+                                <div class="readonly-content" v-html="formatContentWithImages(form.answer2)"></div>
+                            </el-form-item>
+                        </el-col>
+                    </el-row>
+                </div>
+                
+                <!-- 横线分隔(仅在修改时显示) -->
+                <el-divider v-if="form.id != null"></el-divider>
+                
+                <!-- 可修改内容区域,两列布局 -->
+                <el-row :gutter="20">
+                    <el-col :span="12">
+                        <el-form-item label="科目" prop="subjectId">
+                            <el-select v-model="form.subjectId" placeholder="请选择科目" style="width: 100%">
+                                <el-option v-for="s in subjectList" :label="s.subjectName" :value="s.subjectId"/>
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="题型" prop="qtpye">
+                            <el-input v-model="form.qtpye" type="text" placeholder="请输入内容"/>
+                        </el-form-item>
+                    </el-col>
+                </el-row>
+                <el-form-item label="标题" prop="title">
+                    <Editor v-if="!isTextMode" v-model="form.title" :min-height="120" />
+                    <el-input v-else v-model="form.title" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入标题内容"/>
+                </el-form-item>
+                <el-row :gutter="20">
+                    <el-col :span="12">
+                        <el-form-item label="选项A" prop="optionA">
+                            <Editor v-if="!isTextMode" v-model="form.optionA" :min-height="120" />
+                            <el-input v-else v-model="form.optionA" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="选项B" prop="optionB">
+                            <Editor v-if="!isTextMode" v-model="form.optionB" :min-height="120" />
+                            <el-input v-else v-model="form.optionB" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
+                        </el-form-item>
+                    </el-col>
+                </el-row>
+                <el-row :gutter="20">
+                    <el-col :span="12">
+                        <el-form-item label="选项C" prop="optionC">
+                            <Editor v-if="!isTextMode" v-model="form.optionC" :min-height="120" />
+                            <el-input v-else v-model="form.optionC" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="选项D" prop="optionD">
+                            <Editor v-if="!isTextMode" v-model="form.optionD" :min-height="120" />
+                            <el-input v-else v-model="form.optionD" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
+                        </el-form-item>
+                    </el-col>
+                </el-row>
+                <el-row :gutter="20">
+                    <el-col :span="12">
+                        <el-form-item label="选项E" prop="optionE">
+                            <Editor v-if="!isTextMode" v-model="form.optionE" :min-height="120" />
+                            <el-input v-else v-model="form.optionE" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="选项F" prop="optionF">
+                            <Editor v-if="!isTextMode" v-model="form.optionF" :min-height="120" />
+                            <el-input v-else v-model="form.optionF" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
+                        </el-form-item>
+                    </el-col>
+                </el-row>
+                <el-row :gutter="20">
+                    <el-col :span="12">
+                        <el-form-item label="选项G" prop="optionG">
+                            <Editor v-if="!isTextMode" v-model="form.optionG" :min-height="120" />
+                            <el-input v-else v-model="form.optionG" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
+                        </el-form-item>
+                    </el-col>
+                </el-row>
+                <el-row :gutter="20">
+                    <el-col :span="12">
+                        <el-form-item label="答案1" prop="answer1">
+                            <Editor v-if="!isTextMode" v-model="form.answer1" :min-height="120" />
+                            <el-input v-else v-model="form.answer1" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="答案2" prop="answer2">
+                            <Editor v-if="!isTextMode" v-model="form.answer2" :min-height="120" />
+                            <el-input v-else v-model="form.answer2" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
+                        </el-form-item>
+                    </el-col>
+                </el-row>
+                <!-- <el-form-item label="题型" prop="qtpye">
                     <el-select v-model="form.typeId" placeholder="请选择类型">
                         <el-option v-for="q in question_type" :label="q.label" :value="q.value"/>
                     </el-select>
-                </el-form-item>
-                <el-form-item label="学科" prop="subjectId">
-                    <el-select v-model="form.subjectId" placeholder="请选择学科">
-                        <el-option v-for="s in subjectList" :label="s.subjectName" :value="s.subjectId"/>
-                    </el-select>
-                </el-form-item>
-                <!-- <el-form-item label="${comment}" prop="paperId">
-                  <el-input v-model="form.paperId" placeholder="请输入${comment}" />
                 </el-form-item> -->
-                <el-form-item label="知识点" prop="knowledgeId">
-                    <el-tree-select node-key="id" v-model="form.knowledgeId" :data="knowledgeTreeList" check-strictly
-                                    :render-after-expand="false" style=""
-                                    :props="{ label: 'name', children: 'children' }"
-                                    placeholder="请选择知识点"/>
-                </el-form-item>
-                <!-- <el-form-item label="${comment}" prop="diff">
-                            <el-input v-model="form.diff" placeholder="请输入${comment}"/>
-                        </el-form-item> -->
-                <el-form-item label="相似度" prop="similarity">
-                    <el-input v-model="form.similarity" placeholder="请输入相似度"/>
-                </el-form-item>
-                <el-form-item label="试题解析" prop="parse">
-                    <el-input v-model="form.parse" type="textarea" placeholder="请输入内容"/>
-                </el-form-item>
-                <!-- <el-form-item label="${comment}" prop="knowId">
-                  <el-input v-model="form.knowId" placeholder="请输入${comment}" />
-                </el-form-item> -->
-                <el-form-item label="年级ID" prop="gradeId">
-                    <el-input v-model="form.gradeId" placeholder="请输入年级ID"/>
-                </el-form-item>
-                <!-- <el-form-item label="${comment}" prop="knowledges">
-                  <el-input v-model="form.knowledges" placeholder="请输入${comment}" />
-                </el-form-item> -->
-                <el-form-item label="试题区域" prop="area">
-                    <el-input v-model="form.area" placeholder="请输入试题区域"/>
-                </el-form-item>
-                <el-form-item label="试题年份" prop="year">
-                    <el-input v-model="form.year" placeholder="请输入试题年份"/>
-                </el-form-item>
-                <el-form-item label="试题类型" prop="paperTpye">
-                    <el-input v-model="form.paperTpye" placeholder="请输入试题"/>
-                </el-form-item>
-                <el-form-item label="来源" prop="source">
-                    <el-input v-model="form.source" placeholder="请输入来源"/>
-                </el-form-item>
-                <el-form-item label="试题来源" prop="fromSite">
-                    <el-input v-model="form.fromSite" placeholder="请输入试题来源"/>
-                </el-form-item>
-                <el-form-item label="是否存在图片水印" prop="isSub">
-                    <el-input v-model="form.isSub" placeholder="请输入是否存在图片水印"/>
-                </el-form-item>
-                <el-form-item label="是否常规题" prop="isNormal">
-                    <el-input v-model="form.isNormal" placeholder="请输入是否常规题"/>
-                </el-form-item>
-                <el-form-item label="是否匹配章节知识点" prop="isKonw">
-                    <el-input v-model="form.isKonw" placeholder="请输入是否匹配章节知识点"/>
-                </el-form-item>
-                <el-form-item label="试题的tiid" prop="tiid">
-                    <el-input v-model="form.tiid" placeholder="请输入试题的tiid"/>
-                </el-form-item>
+                
+                <el-row :gutter="20">
+                    <el-col :span="12">
+                        <el-form-item label="知识点" prop="knowledgeId">
+                            <el-tree-select node-key="id" v-model="form.knowledgeId" :data="knowledgeTreeList" check-strictly
+                                            :render-after-expand="false" style="width: 100%"
+                                            :props="{ label: 'name', children: 'children' }"
+                                            placeholder="请选择知识点"/>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="来源" prop="source">
+                            <el-input v-model="form.source" placeholder="请输入来源"/>
+                        </el-form-item>
+                    </el-col>
+                </el-row>
+                <el-row :gutter="20">
+                    <el-col :span="12">
+                        <el-form-item label="源ID" prop="tiid">
+                            <el-input v-model="form.tiid" placeholder="请输入试题的源ID"/>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :span="12">
+                        <el-form-item label="试题解析" prop="parse">
+                            <el-input v-model="form.parse" type="textarea" :autosize="{ minRows: 3 }" placeholder="请输入内容"/>
+                        </el-form-item>
+                    </el-col>
+                </el-row>
                 <!-- <el-form-item label="试题题干的md5值" prop="md5">
                   <el-input v-model="form.md5" placeholder="请输入试题题干的md5值" />
                 </el-form-item> -->
@@ -316,19 +397,19 @@
                 <!-- <el-form-item label="${comment}" prop="md52">
                   <el-input v-model="form.md52" placeholder="请输入${comment}" />
                 </el-form-item> -->
-                <el-form-item label="分值" prop="score">
+                <!-- <el-form-item label="分值" prop="score">
                     <el-input v-model="form.score" placeholder="请输入分值"/>
                 </el-form-item>
                 <el-form-item label="选项" prop="options">
                     <el-input v-model="form.options" type="textarea" placeholder="请输入内容"/>
-                </el-form-item>
+                </el-form-item> -->
                 <!-- <el-form-item label="${comment}" prop="number">
                   <el-input v-model="form.number" placeholder="请输入${comment}" />
                 </el-form-item> -->
                 <!-- <el-form-item label="${comment}" prop="paperTypeTitle">
                   <el-input v-model="form.paperTypeTitle" placeholder="请输入${comment}" />
                 </el-form-item> -->
-                <el-form-item label="选项" prop="options0">
+                <!-- <el-form-item label="选项" prop="options0">
                     <el-input v-model="form.options0" type="textarea" placeholder="请输入内容"/>
                 </el-form-item>
                 <el-form-item label="试题-材料题题干" prop="title0">
@@ -339,14 +420,15 @@
                 </el-form-item>
                 <el-form-item label="试题解析" prop="parse0">
                     <el-input v-model="form.parse0" type="textarea" placeholder="请输入内容"/>
-                </el-form-item>
+                </el-form-item> -->
                 <!-- <el-form-item label="${comment}" prop="answer0">
                   <el-input v-model="form.answer0" type="textarea" placeholder="请输入内容" />
                 </el-form-item> -->
-                <el-form-item label="是否更新" prop="isUpdate">
+                <!-- <el-form-item label="是否更新" prop="isUpdate">
                     <el-input v-model="form.isUpdate" placeholder="请输入是否更新"/>
-                </el-form-item>
-            </el-form>
+                </el-form-item> -->
+                </el-form>
+            </div>
             <template #footer>
                 <div class="dialog-footer">
                     <el-button type="primary" @click="submitForm">确 定</el-button>
@@ -422,6 +504,7 @@ import {listKnowledgeTree} from "@/api/learn/knowledgeTree"
 import {listAllSubject} from "@/api/dz/subject"
 import {ElMessage} from "element-plus";
 import DictTag from '@/components/DictTag/index.vue'
+import Editor from '@/components/Editor/index.vue'
 import { computed } from 'vue'
 
 const {proxy} = getCurrentInstance()
@@ -445,6 +528,7 @@ const typeForm = ref({
 const showTypeDialog = ref(false)
 const showOptionsDialog = ref(false)
 const currentRow = ref({})
+const isTextMode = ref(false) // 是否为文本模式(false为富文本模式)
 
 const subjectList = ref([])
 const knowledgeTreeList = ref([])
@@ -493,6 +577,7 @@ const data = reactive({
         answer1: null,
         answer2: null,
         qtpye: null,
+        typeId: null,
         subjectId: null,
         paperId: null,
         knowledgeId: null,
@@ -568,6 +653,7 @@ function getSubjectName(subjectId) {
 // 取消按钮
 function cancel() {
     open.value = false
+    isTextMode.value = false // 重置模式
     reset()
 }
 
@@ -620,7 +706,11 @@ function reset() {
         isUpdate: null,
         isSubType: null
     }
-    proxy.resetForm("questionsRef")
+    // 先重置表单验证状态
+    if (proxy.$refs["questionsRef"]) {
+        proxy.$refs["questionsRef"].resetFields()
+        proxy.$refs["questionsRef"].clearValidate()
+    }
 }
 
 /** 科目变化处理 */
@@ -639,6 +729,8 @@ function handleQuery() {
 /** 重置按钮操作 */
 function resetQuery() {
     proxy.resetForm("queryRef")
+    // 手动清除 typeId,因为 resetForm 可能无法清除 el-select 的值
+    queryParams.value.typeId = null
     handleQuery()
 }
 
@@ -652,13 +744,27 @@ function handleSelectionChange(selection) {
 /** 新增按钮操作 */
 function handleAdd() {
     reset()
+    isTextMode.value = false // 新增使用富文本模式
     open.value = true
     title.value = "添加试题"
 }
 
+/** 文本修改按钮操作 */
+function handleTextUpdate(row) {
+    reset()
+    isTextMode.value = true // 文本修改使用普通文本模式
+    const _id = row.id || ids.value
+    getQuestions(_id).then(response => {
+        form.value = response.data
+        open.value = true
+        title.value = "文本修改"
+    })
+}
+
 /** 修改按钮操作 */
 function handleUpdate(row) {
     reset()
+    isTextMode.value = false // 修改使用富文本模式
     const _id = row.id || ids.value
     getQuestions(_id).then(response => {
         form.value = response.data
@@ -671,16 +777,19 @@ function handleUpdate(row) {
 function submitForm() {
     proxy.$refs["questionsRef"].validate(valid => {
         if (valid) {
+            // 提交所有可编辑字段(包括题干和选项)
             if (form.value.id != null) {
                 updateQuestions(form.value).then(response => {
                     proxy.$modal.msgSuccess("修改成功")
                     open.value = false
+                    isTextMode.value = false // 重置模式
                     getList()
                 })
             } else {
                 addQuestions(form.value).then(response => {
                     proxy.$modal.msgSuccess("新增成功")
                     open.value = false
+                    isTextMode.value = false // 重置模式
                     getList()
                 })
             }
@@ -728,6 +837,86 @@ function handleShowOptions(row) {
     showOptionsDialog.value = true
 }
 
+/** 获取图片代理URL(如果需要后端代理,可以在这里实现) */
+function getImageProxyUrl(imageUrl) {
+    if (!imageUrl) return ''
+    
+    // 如果是相对路径或本地路径,直接返回
+    if (!imageUrl.startsWith('http://') && !imageUrl.startsWith('https://') && !imageUrl.startsWith('//')) {
+        return imageUrl
+    }
+    
+    // 如果需要通过后端代理访问第三方图片,可以取消下面的注释
+    // 注意:需要后端提供图片代理接口
+    // const encodedUrl = encodeURIComponent(imageUrl)
+    // return `${import.meta.env.VITE_APP_BASE_API}/common/image/proxy?url=${encodedUrl}`
+    
+    // 暂时直接返回原URL,通过添加跨域属性来处理
+    return imageUrl
+}
+
+/** 格式化内容,将图片URL转换为img标签 */
+function formatContentWithImages(content) {
+    if (!content) return ''
+    
+    // 如果内容已经是HTML格式(包含img标签),需要处理其中的图片URL
+    if (content.includes('<img') || content.includes('<IMG')) {
+        // 提取所有img标签中的src属性
+        const imgSrcRegex = /<img[^>]+src=["']([^"']+)["'][^>]*>/gi
+        let formattedContent = content
+        
+        formattedContent = formattedContent.replace(imgSrcRegex, (match, src) => {
+            // 如果是第三方图片URL,转换为代理URL
+            let imageUrl = src
+            if (src.startsWith('http://') || src.startsWith('https://') || src.startsWith('//')) {
+                // 对于第三方图片,尝试使用代理
+                // 如果代理不可用,则使用原URL并添加跨域属性
+                imageUrl = getImageProxyUrl(src)
+                // 如果代理URL和原URL相同(代理不可用),添加跨域属性
+                if (imageUrl === src) {
+                    return match.replace(src, src).replace('<img', '<img crossorigin="anonymous" referrerpolicy="no-referrer"')
+                } else {
+                    return match.replace(src, imageUrl)
+                }
+            }
+            return match
+        })
+        
+        return formattedContent
+    }
+    
+    // 匹配图片URL的正则表达式
+    // 支持:https://、http://、//开头的完整URL,以及相对路径
+    const imageUrlRegex = /(https?:\/\/[^\s<>"']+\.(jpg|jpeg|png|gif|bmp|webp|svg)(\?[^\s<>"']*)?)|(\/\/[^\s<>"']+\.(jpg|jpeg|png|gif|bmp|webp|svg)(\?[^\s<>"']*)?)|(\/[^\s<>"']+\.(jpg|jpeg|png|gif|bmp|webp|svg)(\?[^\s<>"']*)?)/gi
+    
+    // 将纯文本的图片URL转换为img标签
+    let formattedContent = content
+    const matches = content.match(imageUrlRegex)
+    
+    if (matches) {
+        matches.forEach(url => {
+            // 确保URL是完整的
+            let imageUrl = url.trim()
+            // 如果是以//开头的,添加https:
+            if (imageUrl.startsWith('//')) {
+                imageUrl = 'https:' + imageUrl
+            }
+            
+            // 对于第三方图片,尝试使用代理
+            const proxyUrl = getImageProxyUrl(imageUrl)
+            // 如果代理不可用,使用原URL并添加跨域属性
+            const finalUrl = proxyUrl !== imageUrl ? proxyUrl : imageUrl
+            const crossOriginAttr = proxyUrl === imageUrl ? 'crossorigin="anonymous" referrerpolicy="no-referrer"' : ''
+            
+            // 将URL替换为img标签
+            const imgTag = `<img src="${finalUrl}" ${crossOriginAttr} alt="图片" style="max-width: 100%; height: auto; display: block; margin: 10px 0;" onerror="this.onerror=null; this.src='${imageUrl}'; this.style.border='1px solid #ddd'; this.alt='图片加载失败,点击查看原图'; this.style.cursor='pointer'; this.title='点击查看原图'; this.onclick='window.open(this.src)';" />`
+            formattedContent = formattedContent.replace(url, imgTag)
+        })
+    }
+    
+    return formattedContent
+}
+
 getKnowledgeTreeList()
 getSubjectList()
 getList()
@@ -762,4 +951,41 @@ getList()
     color: #909399;
     padding: 20px 0;
 }
+
+.readonly-section {
+    background-color: #f5f7fa;
+    padding: 15px;
+    border-radius: 4px;
+    margin-bottom: 20px;
+}
+
+.readonly-content {
+    color: #606266;
+    word-break: break-word;
+    white-space: pre-wrap;
+    line-height: 1.6;
+    min-height: 20px;
+}
+
+.readonly-content :deep(img) {
+    max-width: 100%;
+    height: auto;
+    display: block;
+    margin: 10px 0;
+    border-radius: 4px;
+    object-fit: contain;
+}
+
+.readonly-content :deep(img[src=""]) {
+    display: none;
+}
+
+.readonly-content :deep(img:not([src])) {
+    display: none;
+}
+
+.form-content-wrapper {
+    max-width: 1100px;
+    margin: 0 auto;
+}
 </style>