如何禁止用户复制当前页链接

203 阅读2分钟

如何禁止用户复制当前页链接

问题

在编写H5页面时,在微信、钉钉等容器内会默认提供更多——复制链接的功能。当用户复制链接后,可能造成页面安全性降低,用户转发链接等情况。尤其是钉钉,未提供标准禁止复制的操作。

编码背景

当前项目使用的是vue3vue-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路由进行深层次解析,后续亦可在内部嵌套一层路由模块使用。

完结撒花