如何禁止用户复制当前页链接
问题
在编写H5页面时,在微信、钉钉等容器内会默认提供更多——复制链接的功能。当用户复制链接后,可能造成页面安全性降低,用户转发链接等情况。尤其是钉钉,未提供标准禁止复制的操作。
编码背景
当前项目使用的是vue3、vue-router4的配套
方案梳理
复制链接,本质上是复制最外层页面的URL。因此思路由此而来。
方案一
外层使用标准空白页,内部使用iframe进行嵌套
<body>
<div>
<iframe src="xxxx">
</iframe
</div>
</body>
此时需协商好标准协议,例如外层地址为:/nocopy?r=${urlEncodeComponent('https://xx.com/abc/abc')},其中r参数为实际打开url,外层页面在显示完成后通过history-api将页面修改为无query参数的场景,避免用户复制到r参数。
存在问题:存在两套系统,外层为单独页面,内层为本身应用,且用户通过控制台后续可以查看到实际页面地址
方案二(当前使用)
使用vue-router进行拦截最外层路由,并全部重定向至NoCopy.vue路由,通过路由解析,将路由组件、参数全部携带至NoCopy.vue页面。
// NoCopy.vue
<script lang="ts">
import { computed, defineAsyncComponent, markRaw, ref, shallowRef } from 'vue';
import { LocationQuery, RouteComponent } from 'vue-router'
type Lazy<T> = () => Promise<T>
export const noCopyConfig = ref({
open: false,
component: shallowRef(null as null | RouteComponent | Lazy<RouteComponent>),
query: {} as LocationQuery
})
</script>
<script setup name="NoCopy" lang="ts">
const c = noCopyConfig
const com = computed(() => {
const component = c.value.component
if(!component) {return component}
if(typeof component === 'function') {
return markRaw(defineAsyncComponent(component as any))
} else {
return markRaw(component)
}
})
</script>
<template>
<template v-if="c.open">
<component :is="com" v-bind="c.query" />
</template>
<template v-else>
<div class="full center">
<span>请勿复制链接</span>
</div>
</template>
</template>
<style></style>
// router.ts
import { title } from "../components/Header";
import {
createRouter,
createWebHashHistory,
createWebHistory,
RouteRecordRaw,
} from "vue-router";
// import { useUserStore } from "@/store/userStore";
import { Dialog } from "vant";
import { useUserStore } from "@/store/userStore";
import { noCopyConfig } from "../views/NoCopy.vue";
import { markRaw } from "vue";
// import { initBMap } from "@/utils/bmap";
// import { defineAsyncComponent } from "vue";
export const routes: RouteRecordRaw[] = [
{
name: "login",
path: "/login",
component: () => import("../views/Login.vue"),
meta: {
ignoreRight: true,
},
},
{
name: "detail",
path: "/detail",
component: () => import("../views/Detail.vue"),
meta: {
title: "工单详情",
},
},
// 以上链接均会跳转
{
name: "nocopy",
path: "/nocopy",
component: () => import("../views/NoCopy.vue"),
meta: {},
},
{
name: "Not Found",
path: "/:pathMatch(.*)*",
redirect: { name: "login" },
},
];
const router = createRouter({
history: createWebHistory("/m"),
routes,
});
router.beforeEach(async (to, from) => {
const userStore = useUserStore();
if (to.name === "login") {
// 默认允许跳转登录页
return true;
}
if (to.name === "nocopy") {
// 校验是否登录
if (!(await userStore.isLogin())) {
// 尝试进行一次登录
if (await userStore.login()) {
// 登录成功,正常跳转
return true;
}
// 登录失败,回跳登录页,并记录需要进入的页面
userStore.previousUrl = { fullPath: to.fullPath, query: to.query };
console.log(userStore.previousUrl);
return { name: "login" };
}
return true;
}
// 其他页面均直接拦截,跳转至nocopy
noCopyConfig.value = {
open: true,
component: markRaw(to.matched[0].components!.default),
query: to.query,
};
return { name: "nocopy" };
});
export default router;
存在问题:仅能拦截单层路由,多层路由将会出现混乱的场景,需对多层路由的
matched路由进行深层次解析,后续亦可在内部嵌套一层路由模块使用。