web iframe结构实现
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="iframe-view">
|
||||
<iframe
|
||||
v-if="iframeUrl"
|
||||
:src="iframeUrl"
|
||||
class="iframe-content"
|
||||
frameborder="0"
|
||||
@load="handleLoad"
|
||||
/>
|
||||
<div v-else class="iframe-error">
|
||||
<el-icon class="error-icon"><WarningFilled /></el-icon>
|
||||
<p>无效的 iframe 地址</p>
|
||||
</div>
|
||||
<div v-if="loading" class="iframe-loading">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { Loading, WarningFilled } from '@element-plus/icons-vue'
|
||||
|
||||
const route = useRoute()
|
||||
const loading = ref(true)
|
||||
|
||||
// 从路由 meta 中获取 iframe URL
|
||||
const iframeUrl = computed(() => {
|
||||
return route.meta.iframeUrl as string || ''
|
||||
})
|
||||
|
||||
function handleLoad() {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.log('[IframeView] 加载 iframe:', iframeUrl.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.iframe-view {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.iframe-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.iframe-error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: var(--el-text-color-secondary);
|
||||
|
||||
.error-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
color: var(--el-color-warning);
|
||||
}
|
||||
}
|
||||
|
||||
.iframe-loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--el-bg-color);
|
||||
gap: 12px;
|
||||
|
||||
.el-icon {
|
||||
font-size: 32px;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,3 +1,6 @@
|
||||
export * from './fileupload'
|
||||
export * from './base'
|
||||
export * from './dynamicFormItem'
|
||||
export * from './dynamicFormItem'
|
||||
|
||||
// 通用视图组件
|
||||
export { default as IframeView } from './iframe/IframeView.vue'
|
||||
@@ -43,6 +43,12 @@ export interface AppRuntimeConfig {
|
||||
};
|
||||
publicImgPath: string;
|
||||
publicWebPath: string;
|
||||
// 单点登录配置
|
||||
sso?: {
|
||||
platformUrl: string; // platform 平台地址
|
||||
workcaseUrl: string; // workcase 服务地址
|
||||
biddingUrl: string; // bidding 服务地址
|
||||
};
|
||||
features?: {
|
||||
enableDebug?: boolean;
|
||||
enableMockData?: boolean;
|
||||
@@ -92,6 +98,15 @@ const devConfig: AppRuntimeConfig = {
|
||||
publicImgPath: 'http://localhost:5173/img',
|
||||
publicWebPath: 'http://localhost:5173',
|
||||
|
||||
// 单点登录配置
|
||||
// 推荐:开发环境也通过nginx访问(http://localhost)
|
||||
// 备选:直接访问各服务端口(platformUrl: 'http://localhost:5001')
|
||||
sso: {
|
||||
platformUrl: '/', // 通过nginx访问platform
|
||||
workcaseUrl: '/workcase', // 通过nginx访问workcase
|
||||
biddingUrl: '/bidding' // 通过nginx访问bidding
|
||||
},
|
||||
|
||||
features: {
|
||||
enableDebug: true,
|
||||
enableMockData: false
|
||||
@@ -132,6 +147,13 @@ const prodDefaultConfig: AppRuntimeConfig = {
|
||||
publicImgPath: '/img',
|
||||
publicWebPath: '/',
|
||||
|
||||
// 单点登录配置(生产环境通过nginx代理)
|
||||
sso: {
|
||||
platformUrl: '/',
|
||||
workcaseUrl: '/workcase',
|
||||
biddingUrl: '/bidding'
|
||||
},
|
||||
|
||||
features: {
|
||||
enableDebug: false,
|
||||
enableMockData: false
|
||||
@@ -218,6 +240,13 @@ export const APP_CONFIG = {
|
||||
publicImgPath: config.publicImgPath,
|
||||
publicWebPath: config.publicWebPath,
|
||||
|
||||
// 单点登录配置
|
||||
sso: config.sso || {
|
||||
platformUrl: '/',
|
||||
workcaseUrl: '/workcase',
|
||||
biddingUrl: '/bidding'
|
||||
},
|
||||
|
||||
// 功能开关
|
||||
features: config.features || {}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<div class="blank-layout">
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// BlankLayout:空白布局,只显示内容,无侧边栏、无header
|
||||
// 适用于全屏页面,如聊天页面、独立功能页等
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.blank-layout {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
1
urbanLifelineWeb/packages/shared/src/layouts/index.ts
Normal file
1
urbanLifelineWeb/packages/shared/src/layouts/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as BlankLayout } from './BlankLayout/BlankLayout.vue'
|
||||
@@ -128,6 +128,8 @@ export interface TbSysViewDTO extends BaseDTO {
|
||||
type?: number;
|
||||
/** 视图类型 route\iframe*/
|
||||
viewType?: string;
|
||||
/** 所属服务 platform\workcase\bidding */
|
||||
service?: string;
|
||||
/** 布局 */
|
||||
layout?: string;
|
||||
/** 排序 */
|
||||
|
||||
@@ -184,15 +184,22 @@ function generateRouteFromMenu(
|
||||
route.component = component
|
||||
} else {
|
||||
// 组件加载失败,使用 404
|
||||
route.component = config.notFoundComponent || (() => Promise.resolve({ default: { template: '<div>404</div>' } }))
|
||||
route.component = config.notFoundComponent || (() => import('vue').then(({ h }) => ({
|
||||
default: {
|
||||
render() { return h('div', '404') }
|
||||
}
|
||||
})))
|
||||
}
|
||||
} else {
|
||||
// 使用路由占位组件
|
||||
route.component = () => Promise.resolve({
|
||||
route.component = () => import('vue').then(({ h, resolveComponent }) => ({
|
||||
default: {
|
||||
template: '<router-view />'
|
||||
render() {
|
||||
const RouterView = resolveComponent('RouterView')
|
||||
return h(RouterView)
|
||||
}
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -677,17 +684,29 @@ function generateSimpleRoute(
|
||||
let component: any
|
||||
|
||||
if (isIframe) {
|
||||
// iframe 类型:使用占位组件
|
||||
component = iframePlaceholder || (() => Promise.resolve({
|
||||
default: {
|
||||
template: '<div class="iframe-placeholder"></div>'
|
||||
}
|
||||
}))
|
||||
// iframe 类型:使用占位组件(用于显示iframe内容)
|
||||
// 路由路径使用 url 字段(应该设置为不冲突的路径,如 /app/workcase)
|
||||
component = iframePlaceholder || (() => import('vue').then(({ h }) => ({
|
||||
default: {
|
||||
render() { return h('div', { class: 'iframe-placeholder' }, 'Loading...') }
|
||||
}
|
||||
})))
|
||||
} else if (view.component) {
|
||||
// route 类型:加载实际组件
|
||||
component = config.viewLoader(view.component)
|
||||
if (!component) {
|
||||
if (verbose) console.warn('[路由生成] 组件加载失败:', view.component)
|
||||
if (verbose) console.warn('[路由生成] 组件加载失败:', view.component, '使用占位组件')
|
||||
// 使用占位组件,避免路由无效
|
||||
const errorMsg = `组件加载失败: ${view.component}`
|
||||
component = () => import('vue').then(({ h }) => ({
|
||||
default: {
|
||||
render() {
|
||||
return h('div', {
|
||||
style: { padding: '20px', color: 'red' }
|
||||
}, errorMsg)
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -753,11 +772,14 @@ function generateSimpleRoute(
|
||||
route.component = component
|
||||
} else if (!component && hasChildren) {
|
||||
// 没有组件,只有子视图(路由容器)
|
||||
route.component = () => Promise.resolve({
|
||||
route.component = () => import('vue').then(({ h, resolveComponent }) => ({
|
||||
default: {
|
||||
template: '<router-view />'
|
||||
render() {
|
||||
const RouterView = resolveComponent('RouterView')
|
||||
return h(RouterView)
|
||||
}
|
||||
}
|
||||
})
|
||||
}))
|
||||
route.children = []
|
||||
|
||||
// 添加子路由
|
||||
@@ -785,5 +807,51 @@ function generateSimpleRoute(
|
||||
return null
|
||||
}
|
||||
|
||||
// 处理layout:如果视图指定了layout,且不是作为Root的子路由,且有有效组件,需要包裹layout
|
||||
const viewLayout = (view as any).layout
|
||||
if (viewLayout && !asRootChild && route.component && config.layoutMap[viewLayout]) {
|
||||
if (verbose) {
|
||||
console.log('[路由生成] 为视图添加布局:', view.name, '布局:', viewLayout, '路径:', routePath)
|
||||
}
|
||||
|
||||
// 创建layout路由,将原路由的组件作为其子路由
|
||||
const layoutRoute: RouteRecordRaw = {
|
||||
path: routePath,
|
||||
name: view.viewId,
|
||||
component: config.layoutMap[viewLayout],
|
||||
meta: {
|
||||
...route.meta,
|
||||
layout: viewLayout // 标记使用的布局
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: `${view.viewId}_content`,
|
||||
component: route.component,
|
||||
meta: route.meta
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 如果原路由有其他children(子视图),也添加到layout路由的children中
|
||||
if (route.children && route.children.length > 0) {
|
||||
// 跳过第一个空路径的子路由(如果存在)
|
||||
const otherChildren = route.children.filter((child: any) => child.path !== '')
|
||||
if (otherChildren.length > 0) {
|
||||
layoutRoute.children!.push(...otherChildren)
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
console.log('[路由生成] Layout路由生成完成:', {
|
||||
path: layoutRoute.path,
|
||||
name: layoutRoute.name,
|
||||
childrenCount: layoutRoute.children?.length
|
||||
})
|
||||
}
|
||||
|
||||
return layoutRoute
|
||||
}
|
||||
|
||||
return route
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user