From d43316f7c98db60f10310a91f0a27a23b33dd75c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=95=9D=E8=A3=B3?= <1923740402@qq.com> Date: Tue, 1 Mar 2022 10:44:26 +0800 Subject: [PATCH] feat: watermark (#203) * feat: add watermark --- mock/asyncRoutes.ts | 4 +- .../ReIcon/src/iconifyIconOffline.ts | 2 + src/layout/hooks/nav.ts | 1 + src/plugins/i18n/en/menus.ts | 6 +- src/plugins/i18n/zh-CN/menus.ts | 6 +- src/router/modules/able.ts | 37 ++++++ src/router/modules/components.ts | 2 +- src/router/modules/editor.ts | 2 +- src/router/modules/error.ts | 4 +- src/router/modules/externalLink.ts | 2 +- src/router/modules/flowchart.ts | 2 +- src/router/modules/guide.ts | 4 +- src/router/modules/home.ts | 2 +- src/router/modules/index.ts | 4 +- src/router/modules/menuTree.ts | 28 ----- src/router/modules/nested.ts | 4 +- src/router/modules/remaining.ts | 2 +- src/router/utils.ts | 2 +- src/utils/operate/index.ts | 15 +++ src/utils/watermark.ts | 116 ++++++++++++++++++ .../index.vue => able/menu-tree.vue} | 0 src/views/able/watermark.vue | 13 ++ 22 files changed, 209 insertions(+), 49 deletions(-) create mode 100644 src/router/modules/able.ts delete mode 100644 src/router/modules/menuTree.ts create mode 100644 src/utils/watermark.ts rename src/views/{menu-tree/index.vue => able/menu-tree.vue} (100%) create mode 100644 src/views/able/watermark.vue diff --git a/mock/asyncRoutes.ts b/mock/asyncRoutes.ts index 8581dba2a..68d45b28e 100644 --- a/mock/asyncRoutes.ts +++ b/mock/asyncRoutes.ts @@ -41,7 +41,7 @@ const permissionRouter = { title: "menus.permission", icon: "lollipop", i18n: true, - rank: 3 + rank: 7 }, children: [ { @@ -72,7 +72,7 @@ const tabsRouter = { icon: "IF-team-icontabs", title: "menus.hstabs", i18n: true, - rank: 8 + rank: 10 }, children: [ { diff --git a/src/components/ReIcon/src/iconifyIconOffline.ts b/src/components/ReIcon/src/iconifyIconOffline.ts index 8af99e907..158efbc9e 100644 --- a/src/components/ReIcon/src/iconifyIconOffline.ts +++ b/src/components/ReIcon/src/iconifyIconOffline.ts @@ -52,10 +52,12 @@ import arrowRightSLine from "@iconify-icons/ri/arrow-right-s-line"; import arrowLeftSLine from "@iconify-icons/ri/arrow-left-s-line"; import logoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line"; import nodeTree from "@iconify-icons/ri/node-tree"; +import ubuntuFill from "@iconify-icons/ri/ubuntu-fill"; addIcon("arrow-right-s-line", arrowRightSLine); addIcon("arrow-left-s-line", arrowLeftSLine); addIcon("logout-circle-r-line", logoutCircleRLine); addIcon("node-tree", nodeTree); +addIcon("ubuntu-fill", ubuntuFill); // Font Awesome 4 import faUser from "@iconify-icons/fa/user"; diff --git a/src/layout/hooks/nav.ts b/src/layout/hooks/nav.ts index bb13d7ad8..ba050ccf9 100644 --- a/src/layout/hooks/nav.ts +++ b/src/layout/hooks/nav.ts @@ -5,6 +5,7 @@ import { routeMetaType } from "../types"; import { transformI18n } from "/@/plugins/i18n"; import { storageSession } from "/@/utils/storage"; import { useAppStoreHook } from "/@/store/modules/app"; +import { remainingPaths } from "/@/router/modules/index"; import { Title } from "../../../public/serverConfig.json"; import { useEpThemeStoreHook } from "/@/store/modules/epTheme"; import { remainingPaths } from "/@/router/modules/index"; diff --git a/src/plugins/i18n/en/menus.ts b/src/plugins/i18n/en/menus.ts index b4bfd7485..cc67c1483 100644 --- a/src/plugins/i18n/en/menus.ts +++ b/src/plugins/i18n/en/menus.ts @@ -32,7 +32,9 @@ export default { permissionPage: "Page Permission", permissionButton: "Button Permission", hstabs: "Tabs Operate", - hsMenuTree: "Menu Tree", hsguide: "Guide", - externalLink: "External Link" + externalLink: "External Link", + hsAble: "Able", + hsMenuTree: "Menu Tree", + hsWatermark: "Water Mark" }; diff --git a/src/plugins/i18n/zh-CN/menus.ts b/src/plugins/i18n/zh-CN/menus.ts index 07a8894fc..7451f9bb1 100644 --- a/src/plugins/i18n/zh-CN/menus.ts +++ b/src/plugins/i18n/zh-CN/menus.ts @@ -32,7 +32,9 @@ export default { permissionPage: "页面权限", permissionButton: "按钮权限", hstabs: "标签页操作", - hsMenuTree: "菜单树结构", hsguide: "引导页", - externalLink: "外链" + externalLink: "外链", + hsAble: "功能", + hsMenuTree: "菜单树结构", + hsWatermark: "水印" }; diff --git a/src/router/modules/able.ts b/src/router/modules/able.ts new file mode 100644 index 000000000..979272eda --- /dev/null +++ b/src/router/modules/able.ts @@ -0,0 +1,37 @@ +import { $t } from "/@/plugins/i18n"; +const Layout = () => import("/@/layout/index.vue"); + +const ableRouter = { + path: "/able", + name: "components", + component: Layout, + redirect: "/able/menuTree", + meta: { + icon: "ubuntu-fill", + title: $t("menus.hsAble"), + i18n: true, + rank: 3 + }, + children: [ + { + path: "/able/menuTree", + name: "reMenuTree", + component: () => import("/@/views/able/menu-tree.vue"), + meta: { + title: $t("menus.hsMenuTree"), + i18n: true + } + }, + { + path: "/able/watermark", + name: "reWatermark", + component: () => import("/@/views/able/watermark.vue"), + meta: { + title: $t("menus.hsWatermark"), + i18n: true + } + } + ] +}; + +export default ableRouter; diff --git a/src/router/modules/components.ts b/src/router/modules/components.ts index 5107ac9d1..1cbf99b7a 100644 --- a/src/router/modules/components.ts +++ b/src/router/modules/components.ts @@ -1,5 +1,5 @@ import { $t } from "/@/plugins/i18n"; -import Layout from "/@/layout/index.vue"; +const Layout = () => import("/@/layout/index.vue"); const componentsRouter = { path: "/components", diff --git a/src/router/modules/editor.ts b/src/router/modules/editor.ts index e22970fec..c085b5915 100644 --- a/src/router/modules/editor.ts +++ b/src/router/modules/editor.ts @@ -1,5 +1,5 @@ import { $t } from "/@/plugins/i18n"; -import Layout from "/@/layout/index.vue"; +const Layout = () => import("/@/layout/index.vue"); const editorRouter = { path: "/editor", diff --git a/src/router/modules/error.ts b/src/router/modules/error.ts index a98e03bba..98fb23e3a 100644 --- a/src/router/modules/error.ts +++ b/src/router/modules/error.ts @@ -1,5 +1,5 @@ import { $t } from "/@/plugins/i18n"; -import Layout from "/@/layout/index.vue"; +const Layout = () => import("/@/layout/index.vue"); const errorRouter = { path: "/error", @@ -10,7 +10,7 @@ const errorRouter = { icon: "position", title: $t("menus.hserror"), i18n: true, - rank: 7 + rank: 9 }, children: [ { diff --git a/src/router/modules/externalLink.ts b/src/router/modules/externalLink.ts index 9fb0c93d3..e00bc77e6 100644 --- a/src/router/modules/externalLink.ts +++ b/src/router/modules/externalLink.ts @@ -1,5 +1,5 @@ import { $t } from "/@/plugins/i18n"; -import Layout from "/@/layout/index.vue"; +const Layout = () => import("/@/layout/index.vue"); const externalLink = { path: "/external", diff --git a/src/router/modules/flowchart.ts b/src/router/modules/flowchart.ts index 9fd7aafcc..cb036f32b 100644 --- a/src/router/modules/flowchart.ts +++ b/src/router/modules/flowchart.ts @@ -1,5 +1,5 @@ import { $t } from "/@/plugins/i18n"; -import Layout from "/@/layout/index.vue"; +const Layout = () => import("/@/layout/index.vue"); const flowChartRouter = { path: "/flowChart", diff --git a/src/router/modules/guide.ts b/src/router/modules/guide.ts index 8e44bcaf2..54e645506 100644 --- a/src/router/modules/guide.ts +++ b/src/router/modules/guide.ts @@ -1,5 +1,5 @@ import { $t } from "/@/plugins/i18n"; -import Layout from "/@/layout/index.vue"; +const Layout = () => import("/@/layout/index.vue"); const guideRouter = { path: "/guide", @@ -10,7 +10,7 @@ const guideRouter = { icon: "guide", title: $t("menus.hsguide"), i18n: true, - rank: 10 + rank: 11 }, children: [ { diff --git a/src/router/modules/home.ts b/src/router/modules/home.ts index f9c1ec1f6..22a4abbe0 100644 --- a/src/router/modules/home.ts +++ b/src/router/modules/home.ts @@ -1,5 +1,5 @@ import { $t } from "/@/plugins/i18n"; -import Layout from "/@/layout/index.vue"; +const Layout = () => import("/@/layout/index.vue"); const homeRouter = { path: "/", diff --git a/src/router/modules/index.ts b/src/router/modules/index.ts index 4e5f51cb8..5d05f2d47 100644 --- a/src/router/modules/index.ts +++ b/src/router/modules/index.ts @@ -1,10 +1,10 @@ // 静态路由 import homeRouter from "./home"; +import ableRouter from "./able"; import errorRouter from "./error"; import guideRouter from "./guide"; import editorRouter from "./editor"; import nestedRouter from "./nested"; -import menuTreeRouter from "./menuTree"; import externalLink from "./externalLink"; import flowChartRouter from "./flowchart"; import remainingRouter from "./remaining"; @@ -21,12 +21,12 @@ import { buildHierarchyTree } from "/@/utils/tree"; // 原始静态路由(未做任何处理) const routes = [ homeRouter, + ableRouter, errorRouter, guideRouter, nestedRouter, externalLink, editorRouter, - menuTreeRouter, flowChartRouter, componentsRouter ]; diff --git a/src/router/modules/menuTree.ts b/src/router/modules/menuTree.ts deleted file mode 100644 index 03da02029..000000000 --- a/src/router/modules/menuTree.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { $t } from "/@/plugins/i18n"; -import Layout from "/@/layout/index.vue"; - -const menuTreeRouter = { - path: "/menuTree", - name: "reMenuTree", - component: Layout, - redirect: "/menuTree/index", - meta: { - icon: "node-tree", - title: $t("menus.hsMenuTree"), - i18n: true, - rank: 9 - }, - children: [ - { - path: "/menuTree/index", - name: "reMenuTree", - component: () => import("/@/views/menu-tree/index.vue"), - meta: { - title: $t("menus.hsMenuTree"), - i18n: true - } - } - ] -}; - -export default menuTreeRouter; diff --git a/src/router/modules/nested.ts b/src/router/modules/nested.ts index 93172276f..51d69e961 100644 --- a/src/router/modules/nested.ts +++ b/src/router/modules/nested.ts @@ -1,5 +1,5 @@ import { $t } from "/@/plugins/i18n"; -import Layout from "/@/layout/index.vue"; +const Layout = () => import("/@/layout/index.vue"); const nestedRouter = { path: "/nested", @@ -10,7 +10,7 @@ const nestedRouter = { title: $t("menus.hsmenus"), icon: "histogram", i18n: true, - rank: 5 + rank: 8 }, children: [ { diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index a16c94439..5782df6e3 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -1,5 +1,5 @@ import { $t } from "/@/plugins/i18n"; -import Layout from "/@/layout/index.vue"; +const Layout = () => import("/@/layout/index.vue"); const remainingRouter = [ { diff --git a/src/router/utils.ts b/src/router/utils.ts index 6aadf85b4..9b2fc3a2d 100644 --- a/src/router/utils.ts +++ b/src/router/utils.ts @@ -8,11 +8,11 @@ import { } from "vue-router"; import { router } from "./index"; import { loadEnv } from "../../build"; -import Layout from "/@/layout/index.vue"; import { useTimeoutFn } from "@vueuse/core"; import { RouteConfigs } from "/@/layout/types"; import { buildHierarchyTree } from "/@/utils/tree"; import { usePermissionStoreHook } from "/@/store/modules/permission"; +const Layout = () => import("/@/layout/index.vue"); // https://cn.vitejs.dev/guide/features.html#glob-import const modulesRoutes = import.meta.glob("/src/views/**/*.{vue,tsx}"); diff --git a/src/utils/operate/index.ts b/src/utils/operate/index.ts index 13da7cf01..605cf047b 100644 --- a/src/utils/operate/index.ts +++ b/src/utils/operate/index.ts @@ -1,3 +1,5 @@ +import type { FunctionArgs } from "@vueuse/core"; + export const hasClass = (ele: RefType, cls: string): any => { return !!ele.className.match(new RegExp("(\\s|^)" + cls + "(\\s|$)")); }; @@ -40,3 +42,16 @@ export const toggleClass = ( className = className.replace(clsName, ""); targetEl.className = flag ? `${className} ${clsName} ` : className; }; + +export function useRafThrottle(fn: T): T { + let locked = false; + // @ts-ignore + return function (...args) { + if (locked) return; + locked = true; + window.requestAnimationFrame(() => { + fn.apply(this, args); + locked = false; + }); + }; +} diff --git a/src/utils/watermark.ts b/src/utils/watermark.ts new file mode 100644 index 000000000..5c4327bb7 --- /dev/null +++ b/src/utils/watermark.ts @@ -0,0 +1,116 @@ +import { + ref, + Ref, + unref, + shallowRef, + onBeforeUnmount, + getCurrentInstance +} from "vue"; +import { isDef } from "/@/utils/is"; +import { useRafThrottle } from "/@/utils/operate"; +import { addResizeListener, removeResizeListener } from "/@/utils/resize"; + +const domSymbol = Symbol("watermark-dom"); + +type attr = { + font?: string; + fillStyle?: string; +}; + +export function useWatermark( + appendEl: Ref = ref(document.body) as Ref +) { + const func = useRafThrottle(function () { + const el = unref(appendEl); + if (!el) return; + const { clientHeight: height, clientWidth: width } = el; + updateWatermark({ height, width }); + }); + const id = domSymbol.toString(); + const watermarkEl = shallowRef(); + + const clear = () => { + const domId = unref(watermarkEl); + watermarkEl.value = undefined; + const el = unref(appendEl); + if (!el) return; + domId && el.removeChild(domId); + removeResizeListener(el, func); + }; + + function createBase64(str: string, attr?: attr) { + const can = document.createElement("canvas"); + const width = 300; + const height = 240; + Object.assign(can, { width, height }); + + const cans = can.getContext("2d"); + if (cans) { + cans.rotate((-20 * Math.PI) / 120); + cans.font = attr?.font ?? "15px Reggae One"; + cans.fillStyle = attr?.fillStyle ?? "rgba(0, 0, 0, 0.15)"; + cans.textAlign = "left"; + cans.textBaseline = "middle"; + cans.fillText(str, width / 20, height); + } + return can.toDataURL("image/png"); + } + + function updateWatermark( + options: { + width?: number; + height?: number; + str?: string; + attr?: attr; + } = {} + ) { + const el = unref(watermarkEl); + if (!el) return; + if (isDef(options.width)) { + el.style.width = `${options.width}px`; + } + if (isDef(options.height)) { + el.style.height = `${options.height}px`; + } + if (isDef(options.str)) { + el.style.background = `url(${createBase64( + options.str, + options.attr + )}) left top repeat`; + } + } + + const createWatermark = (str: string, attr?: attr) => { + if (unref(watermarkEl)) { + updateWatermark({ str, attr }); + return id; + } + const div = document.createElement("div"); + watermarkEl.value = div; + div.id = id; + div.style.pointerEvents = "none"; + div.style.top = "0px"; + div.style.left = "0px"; + div.style.position = "absolute"; + div.style.zIndex = "100000"; + const el = unref(appendEl); + if (!el) return id; + const { clientHeight: height, clientWidth: width } = el; + updateWatermark({ str, width, height, attr }); + el.appendChild(div); + return id; + }; + + function setWatermark(str: string, attr?: attr) { + createWatermark(str, attr); + addResizeListener(document.documentElement, func); + const instance = getCurrentInstance(); + if (instance) { + onBeforeUnmount(() => { + clear(); + }); + } + } + + return { setWatermark, clear }; +} diff --git a/src/views/menu-tree/index.vue b/src/views/able/menu-tree.vue similarity index 100% rename from src/views/menu-tree/index.vue rename to src/views/able/menu-tree.vue diff --git a/src/views/able/watermark.vue b/src/views/able/watermark.vue new file mode 100644 index 000000000..7efadc1f4 --- /dev/null +++ b/src/views/able/watermark.vue @@ -0,0 +1,13 @@ + + + + +