Procházet zdrojové kódy

fix: 引入 v-dompurify-html 指令解决 v-html 的安全隐患

shizhong před 1 rokem
rodič
revize
c2168466f3

+ 1 - 0
package.json

@@ -65,6 +65,7 @@
     "url": "^0.11.0",
     "video.js": "^8.3.0",
     "vue": "3.3.4",
+    "vue-dompurify-html": "^4.1.4",
     "vue-i18n": "9.2.2",
     "vue-router": "^4.2.1",
     "vue-types": "^5.0.3",

+ 7 - 7
src/components/Form/src/Form.vue

@@ -1,16 +1,16 @@
 <script lang="tsx">
-import { PropType, defineComponent, ref, computed, unref, watch, onMounted } from 'vue'
-import { ElForm, ElFormItem, ElRow, ElCol, ElTooltip } from 'element-plus'
+import { computed, defineComponent, onMounted, PropType, ref, unref, watch } from 'vue'
+import { ElCol, ElForm, ElFormItem, ElRow, ElTooltip } from 'element-plus'
 import { componentMap } from './componentMap'
 import { propTypes } from '@/utils/propTypes'
 import { getSlot } from '@/utils/tsxHelper'
 import {
-  setTextPlaceholder,
-  setGridProp,
+  initModel,
   setComponentProps,
+  setFormItemSlots,
+  setGridProp,
   setItemComponentSlots,
-  initModel,
-  setFormItemSlots
+  setTextPlaceholder
 } from './helper'
 import { useRenderSelect } from './components/useRenderSelect'
 import { useRenderRadio } from './components/useRenderRadio'
@@ -196,7 +196,7 @@ export default defineComponent({
               <span>{item.label}</span>
               <ElTooltip placement="right" raw-content>
                 {{
-                  content: () => <span v-html={item.labelMessage}></span>,
+                  content: () => <span v-dompurify-html={item.labelMessage}></span>,
                   default: () => (
                     <Icon
                       icon="ep:warning"

+ 4 - 1
src/main.ts

@@ -41,9 +41,10 @@ import App from './App.vue'
 import './permission'
 
 import '@/plugins/tongji' // 百度统计
-
 import Logger from '@/utils/Logger'
 
+import VueDOMPurifyHTML from 'vue-dompurify-html' // 解决v-html 的安全隐患
+
 // 创建实例
 const setupAll = async () => {
   const app = createApp(App)
@@ -66,6 +67,8 @@ const setupAll = async () => {
 
   await router.isReady()
 
+  app.use(VueDOMPurifyHTML)
+
   app.mount('#app')
 }
 

+ 4 - 12
src/views/infra/build/index.vue

@@ -3,8 +3,6 @@
     <el-row>
       <el-col>
         <div class="mb-2 float-right">
-          <el-button size="small" @click="setJson"> 导入JSON</el-button>
-          <el-button size="small" @click="setOption"> 导入Options</el-button>
           <el-button size="small" type="primary" @click="showJson">生成 JSON</el-button>
           <el-button size="small" type="success" @click="showOption">生成 Options</el-button>
           <el-button size="small" type="danger" @click="showTemplate">生成组件</el-button>
@@ -18,18 +16,18 @@
   </ContentWrap>
 
   <!-- 弹窗:表单预览 -->
-  <Dialog :title="dialogTitle" v-model="dialogVisible" max-height="600">
-    <div ref="editor" v-if="dialogVisible">
+  <Dialog v-model="dialogVisible" :title="dialogTitle" max-height="600">
+    <div v-if="dialogVisible" ref="editor">
       <XTextButton style="float: right" :title="t('common.copy')" @click="copy(formData)" />
       <el-scrollbar height="580">
         <div>
-          <pre><code class="hljs" v-html="highlightedCode(formData)"></code></pre>
+          <pre><code v-dompurify-html="highlightedCode(formData)" class="hljs"></code></pre>
         </div>
       </el-scrollbar>
     </div>
   </Dialog>
 </template>
-<script setup lang="ts" name="InfraBuild">
+<script lang="ts" name="InfraBuild" setup>
 import FcDesigner from '@form-create/designer'
 // import { useClipboard } from '@vueuse/core'
 import { isString } from '@/utils/is'
@@ -54,12 +52,6 @@ const openModel = (title: string) => {
   dialogTitle.value = title
 }
 
-const setJson = () => {
-  openModel('导入JSON--未实现')
-}
-const setOption = () => {
-  openModel('导入Options--未实现')
-}
 const showJson = () => {
   openModel('生成 JSON')
   formType.value = 0

+ 31 - 1
src/views/infra/codegen/components/Preview.vue

@@ -22,7 +22,7 @@
             :key="item.filePath"
           >
             <XTextButton style="float: right" :title="t('common.copy')" @click="copy(item.code)" />
-            <pre>{{ item.code }}</pre>
+            <pre><code v-dompurify-html="highlightedCode(item)" class="hljs"></code></pre>
           </el-tab-pane>
         </el-tabs>
       </el-card>
@@ -35,6 +35,14 @@ import { handleTree2 } from '@/utils/tree'
 import { previewCodegenApi } from '@/api/infra/codegen'
 import { CodegenTableVO, CodegenPreviewVO } from '@/api/infra/codegen/types'
 
+import hljs from 'highlight.js' // 导入代码高亮文件
+import 'highlight.js/styles/github.css' // 导入代码高亮样式
+import java from 'highlight.js/lib/languages/java'
+import xml from 'highlight.js/lib/languages/java'
+import javascript from 'highlight.js/lib/languages/javascript'
+import sql from 'highlight.js/lib/languages/sql'
+import typescript from 'highlight.js/lib/languages/typescript'
+
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
 // ======== 显示页面 ========
@@ -148,6 +156,28 @@ const copy = async (text: string) => {
   message.success(t('common.copySuccess'))
   oInput.remove()
 }
+
+/**
+ * 代码高亮
+ */
+const highlightedCode = (item) => {
+  const language = item.filePath.substring(item.filePath.lastIndexOf('.') + 1)
+  const result = hljs.highlight(language, item.code || '', true)
+  return result.value || '&nbsp;'
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  // 注册代码高亮的各种语言
+  hljs.registerLanguage('java', java)
+  hljs.registerLanguage('xml', xml)
+  hljs.registerLanguage('html', xml)
+  hljs.registerLanguage('vue', xml)
+  hljs.registerLanguage('javascript', javascript)
+  hljs.registerLanguage('sql', sql)
+  hljs.registerLanguage('typescript', typescript)
+})
+
 defineExpose({
   show
 })

+ 1 - 1
src/views/mall/product/spu/components/DescriptionForm.vue

@@ -19,7 +19,7 @@
   >
     <!-- 展示 HTML 内容 -->
     <template #description="{ row }">
-      <div style="width: 600px" v-html="row.description"></div>
+      <div v-dompurify-html="row.description" style="width: 600px"></div>
     </template>
   </Descriptions>
 </template>

+ 6 - 1
src/views/system/mail/log/index.vue

@@ -37,7 +37,12 @@
       v-if="actionType === 'detail'"
       :schema="allSchemas.detailSchema"
       :data="detailData"
-    />
+    >
+      <!-- 展示 HTML 内容 -->
+      <template #templateContent="{ row }">
+        <div v-dompurify-html="row.templateContent"></div>
+      </template>
+    </Descriptions>
     <template #footer>
       <!-- 按钮:关闭 -->
       <XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" />