stdproject/frontend/src/views/system/userlogin/appframe_container.vue

217 lines
7.4 KiB
Vue
Raw Normal View History

2025-06-19 11:21:06 +08:00
<template>
<div class="editor">
<div ref="previewContainer" class="preview"></div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { loadModule } from 'vue3-sfc-loader'
import * as Vue from 'vue/dist/vue.esm-bundler.js'
import * as ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import less from 'less'
import * as VueRouter from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import { i18n } from '@/data-visualization/plugins/vue-i18n'
import defaultTemplate from '@/views/system/userlogin/frame.vue?raw'
// import { moduleUpdate, moduleById } from '@/api/application/module'
import { useCache } from '@/data-visualization/hooks/web/useCache'
import { getMenuTree } from '@/api/permission/menu'
import { moduleList } from '@/api/application/module'
import { any } from 'vue-types'
const route = useRoute()
const router = useRouter()
const sfcCode = ref(defaultTemplate)
const previewContainer = ref(null)
let prevApp:any = null
const projectName:any = ref('')
const applicationId:any = ref('')
const menuList:any= ref([])
// 增强的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) {
console.error('Base64转换失败:', error.message)
return ''
}
}
const runCode = async () => {
try {
if (prevApp) {
prevApp._container.innerHTML = ''
prevApp.unmount()
prevApp._container.remove()
prevApp = null
await Vue.nextTick()
}
const options = {
moduleCache: {
vue: Vue,
'element-plus': ElementPlus,
'vue/dist/vue.esm-bundler.js': Vue,
'vue-router': VueRouter,
// '@/viewsnew/application/SfcEditor/NavbarEditor/assocPage.vue': Vue.defineAsyncComponent(() =>
// import('@/viewsnew/application/SfcEditor/NavbarEditor/assocPage.vue')
// ),
// '@/viewsnew/application/permissionset/index.vue': Vue.defineAsyncComponent(() =>
// import('@/viewsnew/application/permissionset/index.vue')
// ),
// '@/viewsnew/application/permissionset/user/userinfo.vue': Vue.defineAsyncComponent(() =>
// import('@/viewsnew/application/permissionset/user/userinfo.vue')
// ),
'@/data-visualization/hooks/web/useCache': { useCache }
},
getFile: async (fileName) => {
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, '/')
}
return ''
},
//样式
addStyle: (css) => {
const style = document.createElement('style')
style.textContent = css
document.head.appendChild(style)
},
compiledCache: new Map(),
compileTemplate: true
}
const componentModule = await loadModule('dynamic.vue', options)
const component = componentModule.default || componentModule
const dynamicProps = Vue.reactive({
menuList: props.menuList,
isFixed: props.isFixed,
projectName: props.projectName,
applicationId: props.applicationId,
isExecuteEvent: props.isExecuteEvent
})
// 修改createApp方式
prevApp = Vue.createApp({
render: () => Vue.h(component, {
...dynamicProps,
router: router,
route: route
})
})
prevApp.use(router)
prevApp.use(route)
prevApp.use(i18n)
prevApp.use(ElementPlus)
prevApp.mount(previewContainer.value)
} catch (error) {
console.error('运行时错误:', error)
previewContainer.value.innerHTML = `
<div class="error">
<h3>错误</h3>
<pre>${error.message}</pre>
</div>
`
}
}
const processMenuTree = (menuNodes: any[], moduleData: any[]) => {
const typeMap = new Map(
moduleData.map(({ id, type }) => [id, type])
);
const traverseMenu = (nodes: any[]) => {
for (const node of nodes) {
if (node.module_id && typeMap.has(node.module_id)) {
node.module_type = typeMap.get(node.module_id);
}
if (node.children?.length) {
traverseMenu(node.children);
}
}
};
traverseMenu(menuNodes);
return menuNodes;
};
onMounted(() => {
if (route.query.id) {
applicationId.value = route.query.id
projectName.value = route.query.name
getmenuinfo()
}
})
function getmenuinfo() {
const params = {
appId: applicationId.value,
name: '',
isdisplay: ''
}
getMenuTree(params).then((res:any) => {
menuList.value = res.data
const paramss = { appId: applicationId.value }
moduleList(paramss).then((ress:any) => {
var arr = ress.data.data
menuList.value = processMenuTree(menuList.value, arr)
})
})
}
</script>
<style>
/* 保留原有样式并补充 */
.editor {
display: flex;
flex-direction: column;
width: 100%;
height: 100vh;
overflow: hidden;
}
textarea {
flex: 1;
margin-bottom: 10px;
font-family: Monaco, monospace;
padding: 10px;
}
.preview {
width: 100%;
height: 100vh;
flex: 1;
overflow: hidden;
}
</style>