refactor(sj_1.2.0-beta1): 重构水印

This commit is contained in:
xlsea 2024-07-25 16:08:51 +08:00
parent f804e18e93
commit b5bd40c25e
8 changed files with 45 additions and 171 deletions

View File

@ -30,6 +30,7 @@ const ContextHolder = defineComponent({
<NMessageProvider> <NMessageProvider>
<ContextHolder /> <ContextHolder />
<slot></slot> <slot></slot>
<AppWatermark />
</NMessageProvider> </NMessageProvider>
</NNotificationProvider> </NNotificationProvider>
</NDialogProvider> </NDialogProvider>

View File

@ -0,0 +1,37 @@
<script setup lang="ts">
import { computed } from 'vue';
import { useAuthStore } from '@/store/modules/auth';
import { useThemeStore } from '@/store/modules/theme';
defineOptions({
name: 'AppWatermark'
});
const { watermark } = useThemeStore();
const { userInfo } = useAuthStore();
const watermarkContent = computed(() => {
const appTitle = import.meta.env.VITE_APP_TITLE || 'Snail Job';
return userInfo.userName ? `${userInfo.userName}@${appTitle}` : appTitle;
});
</script>
<template>
<NWatermark
v-if="watermark.visible"
:content="watermarkContent"
cross
fullscreen
:font-size="14"
:line-height="14"
:width="200"
:height="300"
:x-offset="12"
:y-offset="60"
:rotate="-18"
:z-index="999"
font-color="rgba(200, 200, 200, 0.3)"
/>
</template>
<style scoped></style>

View File

@ -1,130 +0,0 @@
import type { Ref } from 'vue';
import { ref, shallowRef, unref } from 'vue';
import { debounce } from '@/utils/common';
type Attrs = {
textStyles?: {
font?: string;
fillStyle?: string;
rotate?: number;
width?: number;
height?: number;
};
styles?: { [key: string]: any };
};
type WatermarkOpts = {
appendEl?: Ref<HTMLElement | null>;
id?: string;
};
export function useWatermark(opts: WatermarkOpts = {}) {
// const id = `waterMark_${Math.random().toString().slice(-10)}_${+new Date()}`
const appendEl = opts.appendEl || (ref(document.body) as Ref<HTMLElement>);
const watermarkEl = shallowRef<HTMLElement>();
/** 绘制canvas文字背景图 */
const createCanvasBase64 = (str: string, attrs: Attrs = {}) => {
const can = document.createElement('canvas');
const { rotate, font, fillStyle, width = 200, height = 140 } = attrs.textStyles || {};
Object.assign(can, { width, height });
const cans = can.getContext('2d');
if (cans) {
cans.rotate((-(rotate ?? 20) * Math.PI) / 180);
cans.font = font || '12px Vedana';
cans.fillStyle = fillStyle || 'rgba(200, 200, 200, 0.3)';
cans.textAlign = 'left';
cans.textBaseline = 'middle';
cans.fillText(str, can.width / 10, can.height / 2);
}
return can.toDataURL('image/png');
};
/** 页面随窗口调整更新水印 */
const updateWatermark = (
watermarkOpts: {
str?: string;
attrs?: Attrs;
width?: number;
height?: number;
} = {}
) => {
const el = unref(watermarkEl);
if (!el) return;
if (typeof watermarkOpts.width !== 'undefined') {
el.style.width = `${watermarkOpts.width}px`;
}
if (typeof watermarkOpts.height !== 'undefined') {
el.style.height = `${watermarkOpts.height}px`;
}
if (typeof watermarkOpts.str !== 'undefined') {
el.style.background = `url(${createCanvasBase64(watermarkOpts.str, watermarkOpts.attrs)}) left top repeat`;
}
};
/** 绘制水印层 */
const createWatermark = (str: string, attrs: Attrs = {}) => {
if (watermarkEl.value) {
updateWatermark({ str, attrs });
return watermarkEl;
}
const div = document.createElement('div');
watermarkEl.value = div;
if (opts.id) {
const last_el = document.getElementById(opts.id);
if (last_el) {
document.body.removeChild(last_el);
}
div.id = opts.id;
}
Object.assign(
div.style,
{
pointerEvents: 'none',
top: '0px',
left: '0px',
position: 'fixed',
zIndex: '100000'
},
attrs.styles || {}
);
const el = unref(appendEl);
if (!el) return watermarkEl;
const { clientHeight: height, clientWidth: width } = el;
updateWatermark({ str, attrs, width, height });
el.appendChild(div);
return watermarkEl;
};
const debounceUpdateResize = debounce(
() => {
const el = unref(appendEl);
if (!el) return;
const { clientHeight: height, clientWidth: width } = el;
updateWatermark({ width, height });
},
30,
false
);
/** 对外提供的设置水印方法 */
const setWatermark = (str: string, attrs: Attrs = {}) => {
createWatermark(str, attrs);
window.addEventListener('resize', debounceUpdateResize);
};
/** 清除水印 */
const clearWatermark = () => {
let domId: HTMLElement | null | undefined = unref(watermarkEl);
if (!domId && opts.id) {
domId = document.getElementById(opts.id);
}
watermarkEl.value = undefined;
const el = unref(appendEl);
if (!el) return;
domId && el.removeChild(domId);
window.removeEventListener('resize', debounceUpdateResize);
};
return { setWatermark, clearWatermark };
}

View File

@ -106,9 +106,6 @@ const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wra
<SettingItem v-if="isDev" key="8" :label="$t('theme.watermark.visible')"> <SettingItem v-if="isDev" key="8" :label="$t('theme.watermark.visible')">
<NSwitch v-model:value="themeStore.watermark.visible" /> <NSwitch v-model:value="themeStore.watermark.visible" />
</SettingItem> </SettingItem>
<SettingItem v-if="false" key="8-1" :label="$t('theme.watermark.text')">
<NInput v-model:value="themeStore.watermark.text" size="small" :step="1" class="max-w-180px" />
</SettingItem>
</TransitionGroup> </TransitionGroup>
</template> </template>

View File

@ -10,14 +10,11 @@ import { $t } from '@/locales';
import { roleTypeRecord } from '@/constants/business'; import { roleTypeRecord } from '@/constants/business';
import { useRouteStore } from '../route'; import { useRouteStore } from '../route';
import { useTabStore } from '../tab'; import { useTabStore } from '../tab';
import { useThemeStore } from '../theme';
import { clearAuthStorage, getToken } from './shared'; import { clearAuthStorage, getToken } from './shared';
export const useAuthStore = defineStore(SetupStoreId.Auth, () => { export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
const appTitle = import.meta.env.VITE_APP_TITLE || 'Snail Job';
const route = useRoute(); const route = useRoute();
const routeStore = useRouteStore(); const routeStore = useRouteStore();
const themeStore = useThemeStore();
const tabStore = useTabStore(); const tabStore = useTabStore();
const { toLogin, redirectFromLogin } = useRouterPush(false); const { toLogin, redirectFromLogin } = useRouterPush(false);
const { loading: loginLoading, startLoading, endLoading } = useLoading(); const { loading: loginLoading, startLoading, endLoading } = useLoading();
@ -58,8 +55,6 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
authStore.$reset(); authStore.$reset();
themeStore.setWatermarkText(appTitle);
if (!route.meta.constant) { if (!route.meta.constant) {
await toLogin(); await toLogin();
} }
@ -143,7 +138,6 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
localStg.set('userInfo', info); localStg.set('userInfo', info);
localStg.set('userInfo', info); localStg.set('userInfo', info);
Object.assign(userInfo, info); Object.assign(userInfo, info);
themeStore.setWatermarkText(`${userInfo.userName}@${appTitle}`);
return true; return true;
} }

View File

@ -5,7 +5,6 @@ import { useEventListener, usePreferredColorScheme } from '@vueuse/core';
import { getPaletteColorByNumber } from '@sa/color'; import { getPaletteColorByNumber } from '@sa/color';
import { SetupStoreId } from '@/enum'; import { SetupStoreId } from '@/enum';
import { localStg } from '@/utils/storage'; import { localStg } from '@/utils/storage';
import { useWatermark } from '@/hooks/common/watermark';
import { import {
addThemeVarsToGlobal, addThemeVarsToGlobal,
createThemeToken, createThemeToken,
@ -55,24 +54,13 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
*/ */
const settingsJson = computed(() => JSON.stringify(settings.value)); const settingsJson = computed(() => JSON.stringify(settings.value));
/** Watermarks */ /**
const { setWatermark, clearWatermark } = useWatermark({ id: 'global_watermark_id' }); * Set theme watermark
*
/** 开启水印 */ * @param visible
*/
function toggleWatermark(visible: boolean = false) { function toggleWatermark(visible: boolean = false) {
visible ? setWatermark(settings.value?.watermark.text) : clearWatermark(); settings.value.watermark.visible = visible;
}
/** 修改水印文案 */
function setWatermarkText(text: string) {
if (!text) {
clearWatermark();
return;
}
if (settings.value.watermark && settings.value.watermark?.visible) {
settings.value.watermark.text = text;
setWatermark(settings.value.watermark.text);
}
} }
/** Reset store */ /** Reset store */
@ -204,15 +192,6 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
}, },
{ immediate: true } { immediate: true }
); );
watch(
settings.value?.watermark,
val => {
toggleWatermark(val?.visible);
setWatermarkText(val?.text);
},
{ immediate: true }
);
}); });
/** On scope dispose */ /** On scope dispose */
@ -233,7 +212,6 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
updateThemeColors, updateThemeColors,
setThemeLayout, setThemeLayout,
setLayoutReverseHorizontalMix, setLayoutReverseHorizontalMix,
setWatermarkText,
toggleWatermark toggleWatermark
}; };
}); });

View File

@ -71,8 +71,7 @@ export const themeSettings: App.Theme.ThemeSetting = {
} }
}, },
watermark: { watermark: {
visible: true, visible: true
text: import.meta.env.VITE_APP_TITLE || 'Snail Job'
} }
}; };

View File

@ -104,8 +104,6 @@ declare namespace App {
watermark: { watermark: {
/** Whether to show the watermark */ /** Whether to show the watermark */
visible: boolean; visible: boolean;
/** WatermarkText */
text: string;
}; };
} }