|
|
@ -1,74 +1,199 @@ |
|
|
import type { Watermark, WatermarkOptions } from 'watermark-js-plus'; |
|
|
import type { Watermark, WatermarkOptions } from 'watermark-js-plus'; |
|
|
|
|
|
|
|
|
import { nextTick, onUnmounted, readonly, ref } from 'vue'; |
|
|
import { nextTick, onUnmounted, readonly, ref, watch } from 'vue'; |
|
|
|
|
|
|
|
|
const watermark = ref<Watermark>(); |
|
|
const watermark = ref<Watermark>(); |
|
|
const unmountedHooked = ref<boolean>(false); |
|
|
const unmountedHooked = ref<boolean>(false); |
|
|
const cachedOptions = ref<Partial<WatermarkOptions>>({ |
|
|
const mutationObserver = ref<MutationObserver>(); |
|
|
advancedStyle: { |
|
|
const isWatermarkEnabled = ref<boolean>(false); |
|
|
colorStops: [ |
|
|
|
|
|
{ |
|
|
const DEFAULT_DARK_COLOR = 'rgba(255, 255, 255, 0.8)'; |
|
|
color: 'gray', |
|
|
const DEFAULT_LIGHT_COLOR = 'rgba(0, 0, 0, 0.8)'; |
|
|
offset: 0, |
|
|
|
|
|
}, |
|
|
interface WatermarkConfig { |
|
|
{ |
|
|
content: string; |
|
|
color: 'gray', |
|
|
globalAlpha: number; |
|
|
offset: 1, |
|
|
isDark: boolean; |
|
|
}, |
|
|
} |
|
|
], |
|
|
|
|
|
type: 'linear', |
|
|
const currentConfig = ref<WatermarkConfig>({ |
|
|
}, |
|
|
|
|
|
// fontSize: '20px',
|
|
|
|
|
|
content: '', |
|
|
content: '', |
|
|
contentType: 'multi-line-text', |
|
|
|
|
|
globalAlpha: 0.25, |
|
|
globalAlpha: 0.25, |
|
|
gridLayoutOptions: { |
|
|
isDark: false, |
|
|
cols: 2, |
|
|
|
|
|
gap: [20, 20], |
|
|
|
|
|
matrix: [ |
|
|
|
|
|
[1, 0], |
|
|
|
|
|
[0, 1], |
|
|
|
|
|
], |
|
|
|
|
|
rows: 2, |
|
|
|
|
|
}, |
|
|
|
|
|
height: 200, |
|
|
|
|
|
layout: 'grid', |
|
|
|
|
|
rotate: 30, |
|
|
|
|
|
width: 160, |
|
|
|
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
function getColorStops(isDark: boolean): { color: string; offset: number }[] { |
|
|
|
|
|
const color = isDark ? DEFAULT_DARK_COLOR : DEFAULT_LIGHT_COLOR; |
|
|
|
|
|
return [ |
|
|
|
|
|
{ color, offset: 0 }, |
|
|
|
|
|
{ color, offset: 1 }, |
|
|
|
|
|
]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function createBaseOptions(config: WatermarkConfig): Partial<WatermarkOptions> { |
|
|
|
|
|
return { |
|
|
|
|
|
advancedStyle: { |
|
|
|
|
|
colorStops: getColorStops(config.isDark), |
|
|
|
|
|
type: 'linear', |
|
|
|
|
|
}, |
|
|
|
|
|
content: config.content, |
|
|
|
|
|
contentType: 'multi-line-text', |
|
|
|
|
|
globalAlpha: config.globalAlpha, |
|
|
|
|
|
gridLayoutOptions: { |
|
|
|
|
|
cols: 2, |
|
|
|
|
|
gap: [20, 20], |
|
|
|
|
|
matrix: [ |
|
|
|
|
|
[1, 0], |
|
|
|
|
|
[0, 1], |
|
|
|
|
|
], |
|
|
|
|
|
rows: 2, |
|
|
|
|
|
}, |
|
|
|
|
|
height: 200, |
|
|
|
|
|
layout: 'grid', |
|
|
|
|
|
rotate: 30, |
|
|
|
|
|
width: 160, |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function setupMutationObserver() { |
|
|
|
|
|
if (mutationObserver.value) { |
|
|
|
|
|
mutationObserver.value.disconnect(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const observer = new MutationObserver((mutations) => { |
|
|
|
|
|
if (!isWatermarkEnabled.value || !watermark.value) return; |
|
|
|
|
|
|
|
|
|
|
|
let shouldRecreate = false; |
|
|
|
|
|
|
|
|
|
|
|
for (const mutation of mutations) { |
|
|
|
|
|
if (mutation.type === 'childList') { |
|
|
|
|
|
const removedNodes = Array.from(mutation.removedNodes); |
|
|
|
|
|
for (const node of removedNodes) { |
|
|
|
|
|
if ( |
|
|
|
|
|
node instanceof HTMLElement && |
|
|
|
|
|
(node.classList.contains('watermark-container') || |
|
|
|
|
|
node.classList.contains('watermark-item')) |
|
|
|
|
|
) { |
|
|
|
|
|
shouldRecreate = true; |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} else if (mutation.type === 'attributes') { |
|
|
|
|
|
const target = mutation.target as HTMLElement; |
|
|
|
|
|
if ( |
|
|
|
|
|
target.classList.contains('watermark-container') || |
|
|
|
|
|
target.classList.contains('watermark-item') |
|
|
|
|
|
) { |
|
|
|
|
|
if (mutation.attributeName === 'style') { |
|
|
|
|
|
const style = target.getAttribute('style') || ''; |
|
|
|
|
|
if ( |
|
|
|
|
|
style.includes('display: none') || |
|
|
|
|
|
style.includes('visibility: hidden') || |
|
|
|
|
|
style.includes('opacity: 0') || |
|
|
|
|
|
style.includes('z-index: -1') |
|
|
|
|
|
) { |
|
|
|
|
|
shouldRecreate = true; |
|
|
|
|
|
} |
|
|
|
|
|
} else if (mutation.attributeName === 'class') { |
|
|
|
|
|
if ( |
|
|
|
|
|
target.classList.contains('hidden') || |
|
|
|
|
|
target.classList.contains('invisible') |
|
|
|
|
|
) { |
|
|
|
|
|
shouldRecreate = true; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (shouldRecreate) { |
|
|
|
|
|
recreateWatermark(); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
observer.observe(document.body, { |
|
|
|
|
|
childList: true, |
|
|
|
|
|
subtree: true, |
|
|
|
|
|
attributes: true, |
|
|
|
|
|
attributeFilter: ['style', 'class'], |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
mutationObserver.value = observer; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async function recreateWatermark() { |
|
|
|
|
|
if (!isWatermarkEnabled.value) return; |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
if (watermark.value) { |
|
|
|
|
|
watermark.value.destroy(); |
|
|
|
|
|
watermark.value = undefined; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
await nextTick(); |
|
|
|
|
|
|
|
|
|
|
|
const options = createBaseOptions(currentConfig.value); |
|
|
|
|
|
const { Watermark } = await import('watermark-js-plus'); |
|
|
|
|
|
watermark.value = new Watermark(options); |
|
|
|
|
|
await watermark.value?.create(); |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
console.error('Failed to recreate watermark:', e); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
export function useWatermark() { |
|
|
export function useWatermark() { |
|
|
async function initWatermark(options: Partial<WatermarkOptions>) { |
|
|
async function initWatermark(options: Partial<WatermarkOptions>) { |
|
|
const { Watermark } = await import('watermark-js-plus'); |
|
|
const { Watermark } = await import('watermark-js-plus'); |
|
|
|
|
|
|
|
|
cachedOptions.value = { |
|
|
const mergedOptions = { |
|
|
...cachedOptions.value, |
|
|
...createBaseOptions(currentConfig.value), |
|
|
...options, |
|
|
...options, |
|
|
}; |
|
|
}; |
|
|
watermark.value = new Watermark(cachedOptions.value); |
|
|
|
|
|
|
|
|
watermark.value = new Watermark(mergedOptions); |
|
|
await watermark.value?.create(); |
|
|
await watermark.value?.create(); |
|
|
|
|
|
|
|
|
|
|
|
isWatermarkEnabled.value = true; |
|
|
|
|
|
setupMutationObserver(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function updateWatermark(options: Partial<WatermarkOptions>) { |
|
|
async function updateWatermark(options: Partial<WatermarkOptions> & { isDark?: boolean }) { |
|
|
|
|
|
if (options.isDark !== undefined) { |
|
|
|
|
|
currentConfig.value.isDark = options.isDark; |
|
|
|
|
|
} |
|
|
|
|
|
if (options.content !== undefined) { |
|
|
|
|
|
currentConfig.value.content = options.content; |
|
|
|
|
|
} |
|
|
|
|
|
if (options.globalAlpha !== undefined) { |
|
|
|
|
|
currentConfig.value.globalAlpha = options.globalAlpha; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const mergedOptions = createBaseOptions(currentConfig.value); |
|
|
|
|
|
|
|
|
if (watermark.value) { |
|
|
if (watermark.value) { |
|
|
await nextTick(); |
|
|
await nextTick(); |
|
|
await watermark.value?.changeOptions({ |
|
|
await watermark.value?.changeOptions(mergedOptions); |
|
|
...cachedOptions.value, |
|
|
|
|
|
...options, |
|
|
|
|
|
}); |
|
|
|
|
|
} else { |
|
|
} else { |
|
|
await initWatermark(options); |
|
|
await initWatermark(mergedOptions); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
isWatermarkEnabled.value = true; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function destroyWatermark() { |
|
|
function destroyWatermark() { |
|
|
|
|
|
isWatermarkEnabled.value = false; |
|
|
|
|
|
|
|
|
|
|
|
if (mutationObserver.value) { |
|
|
|
|
|
mutationObserver.value.disconnect(); |
|
|
|
|
|
mutationObserver.value = undefined; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (watermark.value) { |
|
|
if (watermark.value) { |
|
|
watermark.value.destroy(); |
|
|
watermark.value.destroy(); |
|
|
watermark.value = undefined; |
|
|
watermark.value = undefined; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 只在第一次调用时注册卸载钩子,防止重复注册以致于在路由切换时销毁了水印
|
|
|
|
|
|
if (!unmountedHooked.value) { |
|
|
if (!unmountedHooked.value) { |
|
|
unmountedHooked.value = true; |
|
|
unmountedHooked.value = true; |
|
|
onUnmounted(() => { |
|
|
onUnmounted(() => { |
|
|
|