fix: 引入 v-dompurify-html 指令解决 v-html 的安全隐患
This commit is contained in:
parent
144be6ef44
commit
c2168466f3
@ -65,6 +65,7 @@
|
|||||||
"url": "^0.11.0",
|
"url": "^0.11.0",
|
||||||
"video.js": "^8.3.0",
|
"video.js": "^8.3.0",
|
||||||
"vue": "3.3.4",
|
"vue": "3.3.4",
|
||||||
|
"vue-dompurify-html": "^4.1.4",
|
||||||
"vue-i18n": "9.2.2",
|
"vue-i18n": "9.2.2",
|
||||||
"vue-router": "^4.2.1",
|
"vue-router": "^4.2.1",
|
||||||
"vue-types": "^5.0.3",
|
"vue-types": "^5.0.3",
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
<script lang="tsx">
|
<script lang="tsx">
|
||||||
import { PropType, defineComponent, ref, computed, unref, watch, onMounted } from 'vue'
|
import { computed, defineComponent, onMounted, PropType, ref, unref, watch } from 'vue'
|
||||||
import { ElForm, ElFormItem, ElRow, ElCol, ElTooltip } from 'element-plus'
|
import { ElCol, ElForm, ElFormItem, ElRow, ElTooltip } from 'element-plus'
|
||||||
import { componentMap } from './componentMap'
|
import { componentMap } from './componentMap'
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
import { getSlot } from '@/utils/tsxHelper'
|
import { getSlot } from '@/utils/tsxHelper'
|
||||||
import {
|
import {
|
||||||
setTextPlaceholder,
|
|
||||||
setGridProp,
|
|
||||||
setComponentProps,
|
|
||||||
setItemComponentSlots,
|
|
||||||
initModel,
|
initModel,
|
||||||
setFormItemSlots
|
setComponentProps,
|
||||||
|
setFormItemSlots,
|
||||||
|
setGridProp,
|
||||||
|
setItemComponentSlots,
|
||||||
|
setTextPlaceholder
|
||||||
} from './helper'
|
} from './helper'
|
||||||
import { useRenderSelect } from './components/useRenderSelect'
|
import { useRenderSelect } from './components/useRenderSelect'
|
||||||
import { useRenderRadio } from './components/useRenderRadio'
|
import { useRenderRadio } from './components/useRenderRadio'
|
||||||
@ -196,7 +196,7 @@ export default defineComponent({
|
|||||||
<span>{item.label}</span>
|
<span>{item.label}</span>
|
||||||
<ElTooltip placement="right" raw-content>
|
<ElTooltip placement="right" raw-content>
|
||||||
{{
|
{{
|
||||||
content: () => <span v-html={item.labelMessage}></span>,
|
content: () => <span v-dompurify-html={item.labelMessage}></span>,
|
||||||
default: () => (
|
default: () => (
|
||||||
<Icon
|
<Icon
|
||||||
icon="ep:warning"
|
icon="ep:warning"
|
||||||
|
@ -41,9 +41,10 @@ import App from './App.vue'
|
|||||||
import './permission'
|
import './permission'
|
||||||
|
|
||||||
import '@/plugins/tongji' // 百度统计
|
import '@/plugins/tongji' // 百度统计
|
||||||
|
|
||||||
import Logger from '@/utils/Logger'
|
import Logger from '@/utils/Logger'
|
||||||
|
|
||||||
|
import VueDOMPurifyHTML from 'vue-dompurify-html' // 解决v-html 的安全隐患
|
||||||
|
|
||||||
// 创建实例
|
// 创建实例
|
||||||
const setupAll = async () => {
|
const setupAll = async () => {
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
@ -66,6 +67,8 @@ const setupAll = async () => {
|
|||||||
|
|
||||||
await router.isReady()
|
await router.isReady()
|
||||||
|
|
||||||
|
app.use(VueDOMPurifyHTML)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
<el-row>
|
<el-row>
|
||||||
<el-col>
|
<el-col>
|
||||||
<div class="mb-2 float-right">
|
<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="primary" @click="showJson">生成 JSON</el-button>
|
||||||
<el-button size="small" type="success" @click="showOption">生成 Options</el-button>
|
<el-button size="small" type="success" @click="showOption">生成 Options</el-button>
|
||||||
<el-button size="small" type="danger" @click="showTemplate">生成组件</el-button>
|
<el-button size="small" type="danger" @click="showTemplate">生成组件</el-button>
|
||||||
@ -18,18 +16,18 @@
|
|||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 弹窗:表单预览 -->
|
<!-- 弹窗:表单预览 -->
|
||||||
<Dialog :title="dialogTitle" v-model="dialogVisible" max-height="600">
|
<Dialog v-model="dialogVisible" :title="dialogTitle" max-height="600">
|
||||||
<div ref="editor" v-if="dialogVisible">
|
<div v-if="dialogVisible" ref="editor">
|
||||||
<XTextButton style="float: right" :title="t('common.copy')" @click="copy(formData)" />
|
<XTextButton style="float: right" :title="t('common.copy')" @click="copy(formData)" />
|
||||||
<el-scrollbar height="580">
|
<el-scrollbar height="580">
|
||||||
<div>
|
<div>
|
||||||
<pre><code class="hljs" v-html="highlightedCode(formData)"></code></pre>
|
<pre><code v-dompurify-html="highlightedCode(formData)" class="hljs"></code></pre>
|
||||||
</div>
|
</div>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts" name="InfraBuild">
|
<script lang="ts" name="InfraBuild" setup>
|
||||||
import FcDesigner from '@form-create/designer'
|
import FcDesigner from '@form-create/designer'
|
||||||
// import { useClipboard } from '@vueuse/core'
|
// import { useClipboard } from '@vueuse/core'
|
||||||
import { isString } from '@/utils/is'
|
import { isString } from '@/utils/is'
|
||||||
@ -54,12 +52,6 @@ const openModel = (title: string) => {
|
|||||||
dialogTitle.value = title
|
dialogTitle.value = title
|
||||||
}
|
}
|
||||||
|
|
||||||
const setJson = () => {
|
|
||||||
openModel('导入JSON--未实现')
|
|
||||||
}
|
|
||||||
const setOption = () => {
|
|
||||||
openModel('导入Options--未实现')
|
|
||||||
}
|
|
||||||
const showJson = () => {
|
const showJson = () => {
|
||||||
openModel('生成 JSON')
|
openModel('生成 JSON')
|
||||||
formType.value = 0
|
formType.value = 0
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
:key="item.filePath"
|
:key="item.filePath"
|
||||||
>
|
>
|
||||||
<XTextButton style="float: right" :title="t('common.copy')" @click="copy(item.code)" />
|
<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-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</el-card>
|
</el-card>
|
||||||
@ -35,6 +35,14 @@ import { handleTree2 } from '@/utils/tree'
|
|||||||
import { previewCodegenApi } from '@/api/infra/codegen'
|
import { previewCodegenApi } from '@/api/infra/codegen'
|
||||||
import { CodegenTableVO, CodegenPreviewVO } from '@/api/infra/codegen/types'
|
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 { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
// ======== 显示页面 ========
|
// ======== 显示页面 ========
|
||||||
@ -148,6 +156,28 @@ const copy = async (text: string) => {
|
|||||||
message.success(t('common.copySuccess'))
|
message.success(t('common.copySuccess'))
|
||||||
oInput.remove()
|
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 || ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 **/
|
||||||
|
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({
|
defineExpose({
|
||||||
show
|
show
|
||||||
})
|
})
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
>
|
>
|
||||||
<!-- 展示 HTML 内容 -->
|
<!-- 展示 HTML 内容 -->
|
||||||
<template #description="{ row }">
|
<template #description="{ row }">
|
||||||
<div style="width: 600px" v-html="row.description"></div>
|
<div v-dompurify-html="row.description" style="width: 600px"></div>
|
||||||
</template>
|
</template>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</template>
|
</template>
|
||||||
|
@ -37,7 +37,12 @@
|
|||||||
v-if="actionType === 'detail'"
|
v-if="actionType === 'detail'"
|
||||||
:schema="allSchemas.detailSchema"
|
:schema="allSchemas.detailSchema"
|
||||||
:data="detailData"
|
:data="detailData"
|
||||||
/>
|
>
|
||||||
|
<!-- 展示 HTML 内容 -->
|
||||||
|
<template #templateContent="{ row }">
|
||||||
|
<div v-dompurify-html="row.templateContent"></div>
|
||||||
|
</template>
|
||||||
|
</Descriptions>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<!-- 按钮:关闭 -->
|
<!-- 按钮:关闭 -->
|
||||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" />
|
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" />
|
||||||
|
Loading…
Reference in New Issue
Block a user