// src/store/modlues/template.ts
const state = {
sign: 'productLibrary', // 产品库路由标识
routePath: '/template', // 产品库跳转中间页path
isFullFrame: false, // 是否设置产品库Iframe全屏
NODE_ENV: process.env.NODE_ENV, // 环境变量
ENV_URLS: {
development: 'http://127.0.0.1:3000',
test: 'http://cms-test.shenlanbao.com',
uat: 'http://cms-uat.shenlanbao.com',
production: 'http://cms.shenlanbao.com',
},
};
const mutations: MutationTree<any> = {
SET_isFullFrame(state, val) {
state.isFullFrame = val;
},
};
// src/router/modules/page-productLibrary-router.ts
{
path: '/template', // 对应Store/template.ts 的 routePath
name: 'productLibrary', // 对应Store/template.ts 的 sign
cn: '产品库',
hidden: true,
component: () => import('@/views/productLibrary/template.vue'),
children: [],
},
// src/components/common/MenuItem.vue
<script>
methods: {
navigateTo(e): void {
const { frontPermission, url } = e;
const { sign, routePath } = this.$store.state.template;
if (frontPermission.includes(sign)) {
if (!url) return;
this.$router.push({
path: routePath,
query: { path: url },
});
} else {
if (this.$route.path === url) return;
this.$router.push({ path: url });
}
},
}
</script>
// src/views/productLibrary/template.vue
<div :class="{ wrapper: true, hidemenu: $store.state.template.isFullFrame }">
<iframe
:src="src"
frameborder="0"
class="sub-frame"
ref="frames"
@load="frameLoad"
></iframe>
</div>
<script lang="ts">
@Component
export default class Index extends Vue {
@Ref() readonly frames;
src: string = ''; // 产品库跳转路由
// 监听路由,更新产品库路由
@Watch('$route')
toggleRoute(data) {
this.$nextTick(() => {
this.frameLoad();
});
}
frameLoad() {
const { path = '' } = this.$route.query;
const auth = localStorage.getItem('auth') || '';
if (!path || !auth) return;
this.setSrc(path); // 设置产品库跳转路由
const productLibraryRoutes = this.getRoutes(); // 过滤产品库路由
const permission = {
auth,
path,
userInfo: localStorage.getItem('userInfo'),
routes: productLibraryRoutes[0].childPermissionResList,
};
// 传递用户登陆信息实现自动登陆
this.frames.contentWindow.postMessage(permission, '*');
}
getRoutes() {
const localRoutes = localStorage.getItem('routes') || '';
if (!localRoutes) return '';
const { sign } = this.$store.state.template; // 产品库跳转路由标识
const routes: any[] = JSON.parse(localRoutes) || [];
return routes.filter((i) => {
return i.frontPermission === sign && i.childPermissionResList;
});
}
setSrc(path) {
const { NODE_ENV, ENV_URLS } = this.$store.state.template;
const domain = ENV_URLS[NODE_ENV];
if (path && domain) this.src = `${domain}${path}`;
}
}
</script>
// index.html
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script>
window.addEventListener(
'message',
function (event) {
const {
data
} = event
if (data.auth) {
window.localStorage.setItem('auth', data.auth)
window.localStorage.setItem('routes', JSON.stringify(data.routes))
window.localStorage.setItem('userInfo', data.userInfo)
window.localStorage.setItem('path', data.path)
}
},
false,
)
</script>
</body>
// src/router/routerGuards.ts
export function createRouterGuards(routers: Router) {
routers.beforeEach(async (to, _, next) => {
const isFrame = JSON.parse(localStorage.getItem('isFrame') || '') // 是否是 iframe 加载
if (!isFrame) {
// 正常登陆操作
} else {
const hasToken = localStorage.getItem('auth') || ''
const { addRouter } = store.state.global
if (hasToken && addRouter) {
next()
return
}
localStorage.setItem('auth', localStorage.getItem('auth') || '')
await store.commit('global/setRoutes', JSON.parse(localStorage.getItem('routes') || ''))
await store.commit('global/setUserInfo', JSON.parse(localStorage.getItem('userInfo') || ''))
// 路由添加/标记
store.commit('global/isAddRouter', true)
const getNewRouter = store.getters['global/getNewRouter']
getNewRouter.forEach((route: Iroute) => {
routers.addRoute(route)
})
const path = localStorage.getItem('path')
to.path = path
const newTo = {
...to,
name: to.name || undefined,
}
next({ ...newTo, replace: true })
}
})
}
// src/utils/template.ts
// ifarame 内嵌时向产品测评窗口进行通信事件定义
const source = 'productLibrary' // 产品库跳转路由标识
const posMessage = function (config) {
const message = Object.assign({ source }, config)
return new Promise((resolve) => {
top.postMessage(message, '*')
resolve(true)
})
}
// 是否设置全屏遮罩
export function postFrameCover(val) {
const config = {
fnName: 'setIframeCover',
params: val,
}
return posMessage(config)
}
// 跳转产品测评页面
export function postTopRouter(path) {
if (!path) return
const config = {
fnName: 'setRoute',
params: path,
}
return posMessage(config)
}
展示产品库页面时,对应左侧菜单路由要相应高亮选中。
在监听路由跳转时,判断当跳转产品库页面时,取 query 中的 path 字段为 url 做高亮处理。
// src/components/Nav.vue
<script>
@Watch('$route', { immediate: true })
handlerRouteChange(to: Route): void {
this.active = to.path;
this.setProductRoute(to); // 判断产品库路由
}
setProductRoute(to) {
const { name = '', query = {} } = to;
const { sign } = this.$store.state.template; // 产品库跳转路由标识
const isProductLibRoute = name === sign && query.path;
if (isProductLibRoute) {
this.active = query.path;
}
}
</script>
展示产品库页面时,对应上方面包屑导航要显示正确的路由名称。
在监听路由跳转时,判断当跳转产品库页面时,取 query 中的 path 字段为 url 做高亮处理。
// src/components/common/BaseHeard.vue
<script>
@Watch('$route')
$routeWatch(to: Iobj) {
this.getBar(to);
}
getBar(route) {
let pathName = this.getRoutePath(route);
let name = '';
let barList: Array<Iobj> = [];
let pathArr = pathName.split('/').filter((_) => _);
let newArr = [
...this.getNewRouter,
...(this.$router as any).options.routes,
];
for (let i of pathArr) {
name = name + `/${i}`;
for (let j of newArr) {
if (j.path === name) {
barList.push({
cn: j.cn,
path: j.path,
});
break;
}
}
}
this.arr = barList;
}
// 获取当前路由path
getRoutePath(route) {
let { path: pathName, query } = route;
const { routePath } = this.$store.state.template;
// 如果产品库的路由,修改为取query下的path为路由名称
if (pathName === routePath) {
const { path = '' } = query;
if (path) return path;
} else {
return pathName;
}
}
</script>
当点击弹窗时,通知父窗口,同步修改外层样式。
// 产品库
<el-button @click="handleMask()">测试弹窗</el-button>
<script>
import { postFrameCover } from '@utils/template'
handleMask() {
postFrameCover(true).then(() => {
this.showDialog = true
})
}
</script>
// 产品测评 template.vue
setIframeCover(val) {
this.$store.commit('template/SET_isFullFrame', val);
}
// 产品测评 App.vue
// 修改左侧菜单的z-index,使遮罩在最顶层
<nav class="nav" :class="{ hidemenu: $store.state.template.isFullFrame }">
<app-nav />
</nav>
.hidemenu {
background: rgba(0, 0, 0, 0.5);
z-index: 100;
}
// 产品测评 template.vue
<div :class="{ wrapper: true, hidemenu: $store.state.template.isFullFrame }">
<iframe
:src="src"
frameborder="0"
class="sub-frame"
ref="frames"
@load="frameLoad"
></iframe>
</div>
.hidemenu {
background: rgba(0, 0, 0, 0.5);
z-index: 100;
}
// 产品库
<el-button type="primary" @click="handleArticle()">跳转产品测评文章详情</el-button>
<script>
import { postTopRouter } from '@utils/template'
handleArticle() {
const route = {
path: '/article/articleAdd',
query: {
id: '795964024911646720',
},
}
postTopRouter(route)
}
</script>
// index.html
<head>
<script>
// 判断是在iframe还是web
let isFrame = false
if (window.frames.length != parent.frames.length) isFrame = true
window.localStorage.setItem('isFrame', isFrame)
</script>
</head>