2025-06-19 11:21:06 +08:00
|
|
|
|
<script lang="ts" setup>
|
|
|
|
|
import { ref, watch, onMounted, onBeforeUnmount } from 'vue'
|
|
|
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
|
|
|
import * as VueRouter from 'vue-router'
|
|
|
|
|
import { loadModule } from 'vue3-sfc-loader'
|
|
|
|
|
import * as Vue from 'vue/dist/vue.esm-bundler.js'
|
|
|
|
|
import { i18n } from '@/data-visualization/plugins/vue-i18n'
|
|
|
|
|
import * as ElementPlus from 'element-plus'
|
|
|
|
|
import less from 'less'
|
|
|
|
|
import defaultTemplate from '@/views/system/userlogin/login.vue?raw'
|
|
|
|
|
// import { moduleList, moduleById } from '@/api/application/module'
|
|
|
|
|
import { userLogin } from '@/api/data-visualization/manage/user'
|
|
|
|
|
import { useCache } from '@/data-visualization/hooks/web/useCache'
|
2025-06-19 11:54:47 +08:00
|
|
|
|
import { encrypt,decrypt } from '@/utils/rsaEncrypt';
|
2025-06-19 18:31:46 +08:00
|
|
|
|
import { useUserStore } from '@/store/user'
|
|
|
|
|
import _ from 'lodash'
|
|
|
|
|
import * as lodashEs from 'lodash-es'
|
2025-06-19 11:21:06 +08:00
|
|
|
|
const route = useRoute()
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
const sfcCode = ref(defaultTemplate)
|
|
|
|
|
const previewContainer:any = ref(null)
|
|
|
|
|
let prevApp:any = null
|
|
|
|
|
// 增强的Base64转换函数
|
|
|
|
|
const convertToBase64 = async (imagePath:any) => {
|
|
|
|
|
try {
|
|
|
|
|
// 处理路径别名
|
|
|
|
|
const resolvedPath = imagePath.replace('@/', '/src/')
|
|
|
|
|
const response = await fetch(resolvedPath)
|
|
|
|
|
if (!response.ok) throw new Error(`图片加载失败: ${response.status}`)
|
|
|
|
|
const blob = await response.blob()
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
const reader = new FileReader()
|
|
|
|
|
reader.onloadend = () => resolve(reader.result)
|
|
|
|
|
reader.readAsDataURL(blob)
|
|
|
|
|
})
|
|
|
|
|
} catch (error:any) {
|
|
|
|
|
console.error('Base64转换失败:', error.message)
|
|
|
|
|
return ''
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const runCode = async () => {
|
|
|
|
|
try {
|
|
|
|
|
if (prevApp) {
|
|
|
|
|
prevApp.unmount()
|
|
|
|
|
previewContainer.value.innerHTML = ''
|
|
|
|
|
}
|
|
|
|
|
const options:any = {
|
|
|
|
|
moduleCache: {
|
|
|
|
|
vue: Vue,
|
|
|
|
|
'element-plus': ElementPlus,
|
|
|
|
|
'vue/dist/vue.esm-bundler.js': Vue,
|
|
|
|
|
'vue-router': VueRouter,
|
|
|
|
|
'@/api/data-visualization/manage/user': { userLogin },
|
2025-06-19 11:54:47 +08:00
|
|
|
|
'@/data-visualization/hooks/web/useCache': { useCache },
|
2025-06-19 18:31:46 +08:00
|
|
|
|
'@/utils/rsaEncrypt':{ encrypt,decrypt },
|
|
|
|
|
'lodash': _,
|
|
|
|
|
'lodash-es': lodashEs,
|
|
|
|
|
'@/store/user':{ useUserStore }
|
2025-06-19 11:21:06 +08:00
|
|
|
|
},
|
|
|
|
|
getFile: async (fileName:any) => {
|
2025-06-19 18:31:46 +08:00
|
|
|
|
if (!fileName.startsWith('@/') && !fileName.endsWith('.less') && fileName !== 'dynamic.vue') {
|
|
|
|
|
try {
|
|
|
|
|
// 添加 ?v= 参数避免缓存问题
|
|
|
|
|
const module = await import(/* @vite-ignore */ `${fileName}?v=${Date.now()}`)
|
|
|
|
|
return {
|
|
|
|
|
content: `export default ${module.default || module}`,
|
|
|
|
|
mediaType: 'application/javascript'
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(`模块加载失败: ${fileName}`, e)
|
|
|
|
|
return { content: `/* 模块加载失败 */` }
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-19 11:21:06 +08:00
|
|
|
|
if (fileName.startsWith('@/')) {
|
|
|
|
|
const resolvedPath = fileName.replace('@/', '/src/')
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(resolvedPath)
|
|
|
|
|
return { content: await response.text() }
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(`文件加载失败: ${resolvedPath}`, e)
|
|
|
|
|
return { content: '<!-- 文件加载失败 -->' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (fileName === 'dynamic.vue') {
|
|
|
|
|
let code = sfcCode.value
|
|
|
|
|
// 增强的Less处理(支持scoped)
|
|
|
|
|
const lessRegex = /<style\s+.*?lang="less".*?>(.*?)<\/style>/gis
|
|
|
|
|
let match
|
|
|
|
|
while ((match = lessRegex.exec(code)) !== null) {
|
|
|
|
|
const [full, content] = match
|
|
|
|
|
try {
|
|
|
|
|
const { css } = await less.render(content, {
|
|
|
|
|
paths: ['.'], // 设置解析路径
|
|
|
|
|
filename: 'dynamic.less'
|
|
|
|
|
})
|
|
|
|
|
code = code.replace(full, `<style>${css}</style>`)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('Less编译错误:', e.message)
|
|
|
|
|
code = code.replace(full, `<style>/* Less Error: ${e.message} */</style>`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理图片资源(增强路径处理)
|
|
|
|
|
const imgRegex = /src=['"](.+?\.(png|jpg|jpeg))['"]/gi
|
|
|
|
|
let imgMatch
|
|
|
|
|
while ((imgMatch = imgRegex.exec(code)) !== null) {
|
|
|
|
|
const [full, path] = imgMatch
|
|
|
|
|
const base64 = await convertToBase64(path)
|
|
|
|
|
code = code.replace(full, `src="${base64}"`)
|
|
|
|
|
}
|
|
|
|
|
return code
|
|
|
|
|
.replace(/<\/script>/g, '<\\/script>')
|
|
|
|
|
.replace(/\\\//g, '/')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理外部Less文件(支持别名)
|
|
|
|
|
if (fileName.endsWith('.less')) {
|
|
|
|
|
const resolvedPath = fileName.replace('@/', '/src/')
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(resolvedPath)
|
|
|
|
|
const content = await response.text()
|
|
|
|
|
const { css } = await less.render(content, { filename: fileName })
|
|
|
|
|
return { content: css, mediaType: 'text/css' }
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return { content: `/* Less Error: ${e.message} */`, mediaType: 'text/css' }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ''
|
|
|
|
|
},
|
|
|
|
|
addStyle: (css:any) => {
|
|
|
|
|
const style = document.createElement('style')
|
|
|
|
|
style.textContent = css
|
|
|
|
|
document.head.appendChild(style)
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
compiledCache: new Map(),
|
|
|
|
|
compileTemplate: true
|
|
|
|
|
}
|
|
|
|
|
const componentModule:any = await loadModule('dynamic.vue', options)
|
|
|
|
|
const component = componentModule.default || componentModule
|
|
|
|
|
const dynamicProps = ref({
|
2025-06-19 18:31:46 +08:00
|
|
|
|
id: '1927554158852841473',
|
|
|
|
|
name: '测试',
|
2025-06-19 11:21:06 +08:00
|
|
|
|
isExecuteEvent: false
|
|
|
|
|
})
|
|
|
|
|
// prevApp = Vue.createApp({
|
|
|
|
|
// render: () => Vue.h(component, dynamicProps.value)
|
|
|
|
|
// })
|
|
|
|
|
prevApp = Vue.createApp({
|
|
|
|
|
render: () => Vue.h(component, {
|
|
|
|
|
...dynamicProps.value,
|
|
|
|
|
router: router,
|
|
|
|
|
route: route
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 显式提供路由实例
|
|
|
|
|
prevApp.use(router)
|
|
|
|
|
prevApp.use(ElementPlus)
|
|
|
|
|
prevApp.use(i18n)
|
|
|
|
|
prevApp.mount(previewContainer.value)
|
|
|
|
|
|
|
|
|
|
} catch (error:any) {
|
|
|
|
|
console.error('运行时错误:', error)
|
|
|
|
|
previewContainer.value.innerHTML = `
|
|
|
|
|
<div class="error">
|
|
|
|
|
<h3>错误</h3>
|
|
|
|
|
<pre>${error.message}</pre>
|
|
|
|
|
</div>
|
|
|
|
|
`
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
init()
|
|
|
|
|
runCode()
|
|
|
|
|
})
|
|
|
|
|
function init() {
|
|
|
|
|
// const paramss = { appId: route.query.id }
|
|
|
|
|
// moduleList(paramss).then(ress => {
|
|
|
|
|
// var arr = ress.data.data
|
|
|
|
|
// let list: any = {}
|
|
|
|
|
// arr.forEach((item: any) => {
|
|
|
|
|
// if (item.type == '01' && item.node_type == '02') {
|
|
|
|
|
// list = item
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
// if (list.id) {
|
|
|
|
|
// moduleById(list.id).then(res2 => {
|
|
|
|
|
// if (res2.data.data.canvas_style_data) {
|
|
|
|
|
// sfcCode.value = res2.data.data.canvas_style_data
|
|
|
|
|
// runCode()
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
// }else{
|
|
|
|
|
// sfcCode.value = defaultTemplate
|
|
|
|
|
// runCode()
|
|
|
|
|
// }
|
|
|
|
|
// })
|
2025-06-19 18:31:46 +08:00
|
|
|
|
|
2025-06-19 11:21:06 +08:00
|
|
|
|
}
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
<template>
|
|
|
|
|
<div class="loginBox">
|
|
|
|
|
<div ref="previewContainer" class="preview"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style lang="less" scoped>
|
|
|
|
|
.loginBox {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100vh;
|
|
|
|
|
}
|
|
|
|
|
.no-login{
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100vh;
|
|
|
|
|
background-color: #151515;
|
|
|
|
|
color: #787878;
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
line-height: 100vh;
|
|
|
|
|
text-align: center;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
}
|
|
|
|
|
</style>
|