stdproject/frontend/src/views/system/userlogin/appframe_container.vue
2025-06-19 14:07:56 +08:00

220 lines
7.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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/data-visualization/manage/menu'
import { moduleList } from '@/api/data-visualization/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: menuList.value,
isFixed: true,
projectName: '测试',
applicationId: applicationId.value,
isExecuteEvent: false
})
// 修改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
if(ress.data.length>0){
menuList.value = processMenuTree(menuList.value, arr)
}
runCode()
})
})
}
</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>