From 5f0c25e7b5a2f3eb3b566e6ac15a10696d9193f3 Mon Sep 17 00:00:00 2001 From: xlsea Date: Mon, 8 Jul 2024 10:46:49 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat(sj=5F1.1.0-beta3):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=85=A8=E5=B1=80=E6=B0=B4=E5=8D=B0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/common/watermark.ts | 130 ++++++++++++++++++ .../modules/theme-drawer/modules/page-fun.vue | 6 + src/locales/langs/en-us.ts | 4 + src/locales/langs/zh-cn.ts | 4 + src/store/modules/theme/index.ts | 34 ++++- src/theme/settings.ts | 4 + src/typings/app.d.ts | 11 ++ src/utils/common.ts | 30 ++++ 8 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 src/hooks/common/watermark.ts diff --git a/src/hooks/common/watermark.ts b/src/hooks/common/watermark.ts new file mode 100644 index 0000000..0d31dd3 --- /dev/null +++ b/src/hooks/common/watermark.ts @@ -0,0 +1,130 @@ +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; + 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); + const watermarkEl = shallowRef(); + + /** 绘制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 }; +} diff --git a/src/layouts/modules/theme-drawer/modules/page-fun.vue b/src/layouts/modules/theme-drawer/modules/page-fun.vue index 8f80994..a002f3d 100644 --- a/src/layouts/modules/theme-drawer/modules/page-fun.vue +++ b/src/layouts/modules/theme-drawer/modules/page-fun.vue @@ -101,6 +101,12 @@ const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wra > + + + + + + diff --git a/src/locales/langs/en-us.ts b/src/locales/langs/en-us.ts index 704dcd9..7368054 100644 --- a/src/locales/langs/en-us.ts +++ b/src/locales/langs/en-us.ts @@ -301,6 +301,10 @@ const local: App.I18n.Schema = { copySuccessMsg: 'Copy Success, Please replace the variable "themeSettings" in "src/theme/settings.ts"', resetConfig: 'Reset Config', resetSuccessMsg: 'Reset Success' + }, + watermark: { + visible: 'Watermark Visible', + text: 'Watermark Text' } }, route: { diff --git a/src/locales/langs/zh-cn.ts b/src/locales/langs/zh-cn.ts index 8bbcc5e..9ce9603 100644 --- a/src/locales/langs/zh-cn.ts +++ b/src/locales/langs/zh-cn.ts @@ -301,6 +301,10 @@ const local: App.I18n.Schema = { copySuccessMsg: '复制成功,请替换 src/theme/settings.ts 中的变量 themeSettings', resetConfig: '重置配置', resetSuccessMsg: '重置成功' + }, + watermark: { + visible: '开启', + text: '水印文字' } }, route: { diff --git a/src/store/modules/theme/index.ts b/src/store/modules/theme/index.ts index 7685c54..e0f4125 100644 --- a/src/store/modules/theme/index.ts +++ b/src/store/modules/theme/index.ts @@ -5,6 +5,7 @@ import { useEventListener, usePreferredColorScheme } from '@vueuse/core'; import { getPaletteColorByNumber } from '@sa/color'; import { SetupStoreId } from '@/enum'; import { localStg } from '@/utils/storage'; +import { useWatermark } from '@/hooks/common/watermark'; import { addThemeVarsToHtml, createThemeToken, @@ -54,6 +55,26 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => { */ const settingsJson = computed(() => JSON.stringify(settings.value)); + /** Watermarks */ + const { setWatermark, clearWatermark } = useWatermark({ id: 'global_watermark_id' }); + + /** 开启水印 */ + function toggleWatermark(visible: boolean) { + visible ? setWatermark(settings.value.watermark.text) : clearWatermark(); + } + + /** 修改水印文案 */ + function setWatermarkText(text: string) { + if (!text) { + clearWatermark(); + return; + } + if (settings.value.watermark.visible) { + settings.value.watermark.text = text; + setWatermark(settings.value.watermark.text); + } + } + /** Reset store */ function resetStore() { const themeStore = useThemeStore(); @@ -171,6 +192,15 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => { }, { immediate: true } ); + + watch( + settings.value.watermark, + val => { + toggleWatermark(val.visible); + setWatermarkText(val.text); + }, + { immediate: true } + ); }); /** On scope dispose */ @@ -189,6 +219,8 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => { setThemeScheme, toggleThemeScheme, updateThemeColors, - setThemeLayout + setThemeLayout, + setWatermarkText, + toggleWatermark }; }); diff --git a/src/theme/settings.ts b/src/theme/settings.ts index 78ebada..339a80a 100644 --- a/src/theme/settings.ts +++ b/src/theme/settings.ts @@ -46,6 +46,10 @@ export const themeSettings: App.Theme.ThemeSetting = { fixed: false, height: 48, right: true + }, + watermark: { + visible: true, + text: 'Snail Job' } }; diff --git a/src/typings/app.d.ts b/src/typings/app.d.ts index 40c174b..5e28fc7 100644 --- a/src/typings/app.d.ts +++ b/src/typings/app.d.ts @@ -97,6 +97,13 @@ declare namespace App { /** Whether float the footer to the right when the layout is 'horizontal-mix' */ right: boolean; }; + /** Watermark */ + watermark: { + /** Whether to show the watermark */ + visible: boolean; + /** WatermarkText */ + text: string; + }; } interface OtherColor { @@ -521,6 +528,10 @@ declare namespace App { resetConfig: string; resetSuccessMsg: string; }; + watermark: { + visible: string; + text: string; + }; }; route: Record; page: { diff --git a/src/utils/common.ts b/src/utils/common.ts index 9029fa3..fc28f14 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -220,3 +220,33 @@ export function stringToContent( return result as any; } + +/** + * 函数防抖 + * + * @param func 函数 + * @param wait 延迟毫秒数 + * @param immediate true/false (是/否)即执行 + */ +export function debounce(func: () => any, wait: number, immediate?: boolean) { + let timeout: any; + + return () => { + /* eslint-disable */ + // @ts-ignore + const context = this + const args: any = arguments + if (timeout) clearTimeout(timeout); + if (immediate) { + const callNow = !timeout; + timeout = setTimeout(() => { + timeout = null; + }, wait); + if (callNow) func.apply(context, args); + } else { + timeout = setTimeout(() => { + func.apply(context, args); + }, wait); + } + }; +} From 3cebb592e8af8162a8219fcf4add6b6a049d3829 Mon Sep 17 00:00:00 2001 From: xlsea Date: Mon, 8 Jul 2024 10:49:55 +0800 Subject: [PATCH 2/4] =?UTF-8?q?style(sj=5F1.1.0-beta3):=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E4=B8=BB=E9=A2=98=E9=85=8D=E7=BD=AE=E6=8A=BD=E5=B1=89?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layouts/modules/theme-drawer/modules/page-fun.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layouts/modules/theme-drawer/modules/page-fun.vue b/src/layouts/modules/theme-drawer/modules/page-fun.vue index a002f3d..00a1d0f 100644 --- a/src/layouts/modules/theme-drawer/modules/page-fun.vue +++ b/src/layouts/modules/theme-drawer/modules/page-fun.vue @@ -105,7 +105,7 @@ const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wra - + From 8c2dc1f67090bca93559c44c2630a4de11a33774 Mon Sep 17 00:00:00 2001 From: xlsea Date: Mon, 8 Jul 2024 17:47:48 +0800 Subject: [PATCH 3/4] =?UTF-8?q?style(sj=5F1.1.0-beta3):=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=9F=A5=E7=9C=8B=E5=8F=82=E6=95=B0=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/job-task-list-table.vue | 74 +++++++++++++------ src/hooks/common/table.ts | 4 + .../modules/theme-drawer/modules/page-fun.vue | 6 +- 3 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/components/common/job-task-list-table.vue b/src/components/common/job-task-list-table.vue index 874345c..9dc95e7 100644 --- a/src/components/common/job-task-list-table.vue +++ b/src/components/common/job-task-list-table.vue @@ -1,9 +1,9 @@