1.参考链接:
基于创建vue3链接:Vue3前端项目创建_vscode创建vue3项目-CSDN博客
基于创建elementui链接:Vue3引入ElementPlus_vue引入element-plus-CSDN博客
2.案例内容
该案例实现了基本的app.vue的路由跳转、新建浏览页参数传入以及浏览页内的iframe容器的应用。点击Previewlist内的单元格,会新建浏览页展示。
3.案例详细内容
3.1 App.vue
搭建路由容器和配置全局的样式。(一定要配置 <router-view />,
如果没有,Vue Router 将无法渲染匹配的组件,主要用来装载页面)
<template><!-- 最基本的 App.vue 结构 --><div id="app"><router-view /> <!-- 这里会显示当前路由对应的组件内容 --></div>
</template><script>
export default {name: 'App'
}
</script><style>
/* 全局样式可以放在这里 */
#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;color: #2c3e50;margin: 0;padding: 0;
}
</style>
3.2 router\index.js
配置路由,这里配置PreviewList为默认界面,PreviewWindow为新建浏览页的界面。
import { createRouter, createWebHistory } from 'vue-router'// 公共路由
export const constantRoutes = [{path: '/',name: 'PreviewList',component: () => import('@/views/preview/PreviewList.vue'),hidden: true},{path: '/preview/PreviewWindow',name: 'PreviewWindow',component: () => import('@/views/preview/PreviewWindow.vue'),hidden: true}
]const router = createRouter({history: createWebHistory(import.meta.env.BASE_URL || '/'),routes: constantRoutes
})export default router
3.3 PreviewList
<template><div class="app-container"><div style="margin-bottom: 16px; display: flex; align-items: center;"><span style="margin-right: 8px;">公司名称</span><el-select v-model="searchCompany" placeholder="请选择" clearable style="width: 200px; margin-right: 8px;"><el-optionv-for="company in companyList":key="company":label="company":value="company"/></el-select><el-button type="primary" @click="handleSearch">搜索</el-button><el-button @click="handleReset" style="margin-left: 8px;">重置</el-button></div><el-table v-loading="loading" border :data="tableData" style="width: 100%" :header-cell-style="{background:'#f5f7fa',color:'#606266'}"><el-table-column prop="functionName" label="功能名称" width="120" fixed/><el-table-column prop="functionName2" label="功能名称2" width="120" fixed/><!-- 动态渲染测试列(companyList2) --><el-table-column v-for="(label, index) in companyList2" :key="index" :label="label" :width="120"><template v-slot="scope"><div class="clickable-cell2" @click="handleCellClick(scope.row.functionName, label, scope.row[`test${index + 1}`])">{{ scope.row[`test${index + 1}`] || '/' }}</div></template></el-table-column><el-table-column v-for="company in displayCompanyList" :key="company" :label="company" :prop="company"><template v-slot="scope"><div v-if="scope.row[company]" class="clickable-cell" @click="openContentWindow(scope.row.functionName, company, scope.row[company])">{{ scope.row[company] }}</div><div v-else>-</div></template></el-table-column></el-table><router-view /></div>
</template><script>
import { useRouter } from 'vue-router'
import { ElTable, ElTableColumn, ElSelect, ElOption, ElButton } from 'element-plus'export default {name: "FunctionUrlMatrix",components: {ElTable,ElTableColumn,ElSelect,ElOption,ElButton},setup() {const router = useRouter()return { router }},data() {return {loading: false,companyList: [],companyList2: ["测试1", "测试2", "测试3"],functionList: [],rawData: null,searchCompany: '',filteredData: null,displayCompanyList: []}},computed: {tableData() {const data = this.filteredData !== null ? this.filteredData : this.rawDataif (!data || !Array.isArray(data)) return []const allFunctions = new Set()data.forEach(company => {if (company.rpWebShows && Array.isArray(company.rpWebShows)) {company.rpWebShows.forEach(item => {const functionName = Object.keys(item)[0]if (functionName) allFunctions.add(functionName)})}})this.functionList = Array.from(allFunctions)const result = []this.functionList.forEach(func => {const row = { functionName: func, functionName2: `${func}2` }this.companyList2.forEach((label, index) => {const testCompanyData = data.find(item => item.companyName === label)if (testCompanyData && testCompanyData.rpWebShows) {const funcData = testCompanyData.rpWebShows.find(item => Object.keys(item)[0] === func)row[`test${index + 1}`] = funcData ? funcData[func] : null} else {row[`test${index + 1}`] = null}})this.displayCompanyList.forEach(company => {const companyData = data.find(item => item.companyName === company)if (companyData && companyData.rpWebShows) {const funcData = companyData.rpWebShows.find(item => Object.keys(item)[0] === func)row[company] = funcData ? funcData[func] : null} else {row[company] = null}})result.push(row)})return result}},created() {this.loadData()},methods: {loadData() {this.loading = truethis.rawData = [{companyName: "公司A",rpWebShows: [{ "功能1": "https://map.baidu.com/" },{ "功能2": "https://map.baidu.com/" },{ "功能3": "https://map.baidu.com/" }]},{companyName: "公司B",rpWebShows: [{ "功能1": "https://map.baidu.com/" },{ "功能3": "https://map.baidu.com/" },{ "功能4": "https://map.baidu.com/" }]},{companyName: "公司C",rpWebShows: [{ "功能2": "https://map.baidu.com/" },{ "功能3": "https://map.baidu.com/" },{ "功能5": "https://map.baidu.com/" }]},{companyName: "测试1",rpWebShows: [{ "功能1": "https://map.baidu.com/" },{ "功能2": "https://map.baidu.com/" },{ "功能5": "https://map.baidu.com/" }]}]this.companyList = this.rawData.map(item => item.companyName).filter(company => !this.companyList2.includes(company))this.displayCompanyList = [...this.companyList]this.filteredData = this.rawDatathis.loading = false},handleSearch() {if (!this.searchCompany) {this.filteredData = this.rawDatathis.displayCompanyList = [...this.companyList]return}this.filteredData = this.rawData.filter(item => item.companyName === this.searchCompany)this.displayCompanyList = [this.searchCompany]},handleReset() {this.searchCompany = ''this.filteredData = this.rawDatathis.displayCompanyList = [...this.companyList]},openContentWindow(functionName, company, url) {const routeData = this.$router.resolve({name: 'PreviewWindow',query: {company: company,functionName: functionName,url: encodeURIComponent(url)}})window.open(routeData.href, '_blank')},handleCellClick(functionName, company, url) {if (url) {this.openContentWindow(functionName, company, url)}}}
}
</script><style scoped>
.app-container {padding: 5px;
}.el-table {margin-top: 20px;min-height: 300px;
}.clickable-cell, .clickable-cell2 {color: #1890ff;cursor: pointer;text-decoration: underline;
}.clickable-cell:hover, .clickable-cell2:hover {color: #40a9ff;
}
</style>
3.4 PreviewWindow
<template><div class="preview-window"><!-- 顶部控制栏(居中标题+浅灰背景) --><div class="control-bar"><div class="title-wrapper"><span class="title-text">{{ company }} - {{ functionName }}</span></div><el-buttontype="primary"size="small"@click="toggleFullscreen"class="fullscreen-btn">{{ isFullscreen ? '退出全屏' : '全屏显示' }}</el-button></div><!-- 地图容器 --><div class="map-viewport"><iframeref="mapIframe":src="processedUrl"class="baidu-iframe"sandbox="allow-same-origin allow-scripts allow-popups allow-forms"@load="handleIframeLoad"></iframe></div></div>
</template><script>
import { useRoute } from 'vue-router'
import { ElButton } from 'element-plus'export default {name: 'PreviewWindow',components: { ElButton },setup() {const route = useRoute()return { route }},data() {return {company: '',functionName: '',url: '',isFullscreen: false,loading: true}},computed: {decodedUrl() {try {return decodeURIComponent(this.url)} catch {return ''}},processedUrl() {if (!this.decodedUrl) return ''let url = this.decodedUrlif (url.includes('map.baidu.com')) {const separator = url.includes('?') ? '&' : '?'url += `${separator}tn=B_NORMAL_MAP&nn=0&width=${window.innerWidth}`}return url}},mounted() {this.initParams()window.addEventListener('resize', this.handleResize)},beforeUnmount() {window.removeEventListener('resize', this.handleResize)},methods: {initParams() {const { company, functionName, url } = this.route.querythis.company = company || '未指定公司'this.functionName = functionName || '未指定功能'this.url = url || ''},toggleFullscreen() {if (!this.isFullscreen) {this.$refs.mapIframe.requestFullscreen().then(() => this.isFullscreen = true).catch(err => console.error('全屏失败:', err))} else {document.exitFullscreen()}},handleResize() {this.$refs.mapIframe.style.width = `${window.innerWidth}px`this.isFullscreen = !!document.fullscreenElement},handleIframeLoad() {this.loading = falsethis.injectIframeStyles()},injectIframeStyles() {try {const iframeDoc = this.$refs.mapIframe.contentDocumentif (iframeDoc) {const style = iframeDoc.createElement('style')style.innerHTML = `body, #wrapper, #container { margin: 0 !important; padding: 0 !important; left: 0 !important;width: 100% !important;}`iframeDoc.head.appendChild(style)}} catch (e) {console.warn('iframe样式注入失败:', e)}}}
}
</script><style>
/* 全局样式(仅限当前组件) */
.preview-window {position: fixed;top: 0;left: 0;width: 100vw !important;height: 100vh;min-width: 1280px;margin: 0 !important;padding: 0 !important;overflow: hidden;font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", Arial, sans-serif;
}/* 控制栏样式 - 浅灰背景+居中标题 */
.control-bar {position: relative;z-index: 100;height: 48px;background: #f5f7fa; /* Element Plus浅灰底色 */box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);display: flex;align-items: center;padding: 0 20px;
}/* 标题居中容器 */
.title-wrapper {position: absolute;left: 50%;transform: translateX(-50%);
}/* 标题文字样式 */
.title-text {font-size: 16px;font-weight: 500;color: #606266; /* 中灰色文字 */white-space: nowrap;letter-spacing: 0.5px;
}/* 全屏按钮右对齐 */
.fullscreen-btn {margin-left: auto;background-color: #409eff;border-color: #409eff;
}/* 地图容器 */
.map-viewport {position: absolute;top: 48px; /* 控制栏高度 */left: 0;right: 0;bottom: 0;overflow: hidden;background: #e4e7ed; /* 加载时的背景色 */
}/* iframe样式 */
.baidu-iframe {position: absolute;top: 0;left: 0;width: 100vw !important;height: 100%;border: none;margin: 0 !important;padding: 0 !important;background: white;
}
</style><style scoped>
/* 组件私有样式(无特殊需要可删除) */
</style>