Browse Source

调整优化新增水印

hubin 1 year ago
parent
commit
7c0707a4f0

+ 1 - 0
.gitignore

@@ -15,6 +15,7 @@ dist-ssr
 .stylelintcache
 
 package-lock.json
+pnpm-lock.yaml
 yarn.lock
 tests/**/coverage/
 

+ 1 - 1
index.html

@@ -2,7 +2,7 @@
 <html lang="en">
   <head>
     <meta charset="UTF-8" />
-    <link rel="icon" type="image/svg+xml" href="/vue_logo_s.ico" />
+    <link rel="icon" type="image/svg+xml" href="/favicon.ico" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title><%= VITE_APP_TITLE %></title>
   </head>

BIN
public/favicon.ico


BIN
public/logo_b.jpg


BIN
public/vue_logo.ico


BIN
public/vue_logo_s.ico


+ 17 - 0
src/hooks/useTheme.ts

@@ -2,6 +2,7 @@ import { storeToRefs } from 'pinia'
 import { Theme } from './interface'
 // import { ElMessage } from 'element-plus'
 import useStore from '@/store'
+import { useWatermark } from '@/hooks/useWatermark'
 import { getLightColor, getDarkColor } from '@/utils/color'
 import { menuTheme } from '@/styles/theme/menu'
 import { asideTheme } from '@/styles/theme/aside'
@@ -15,6 +16,7 @@ import { defaultSettingState } from '@/settings.ts'
 export const useTheme = () => {
 	const { setting } = useStore()
 	const { themeColor, layout, isDark, isGrey, isWeak, asideInverted, headerInverted, footerInverted } = storeToRefs(setting)
+	const { setWatermark, clearWatermark } = useWatermark({ id: 'global_watermark_id' })
 
 	// 切换暗黑主题 ==> 同时修改主题颜色、侧边栏、头部颜色
 	const switchDark = () => {
@@ -77,6 +79,18 @@ export const useTheme = () => {
 		setMenuTheme()
 	}
 
+	// 开启水印
+	const watermarkChange = () => {
+		setting.isWatermark ? setWatermark(setting.watermarkText) : clearWatermark()
+	}
+	// 修改水印文案
+	const watermarkTextChange = (val: string) => {
+		if (!val) return clearWatermark()
+		if (setting.isWatermark) {
+			setWatermark(setting.watermarkText)
+		}
+	}
+
 	// 设置头部样式
 	const setHeaderTheme = () => {
 		let type: Theme.ThemeType = 'light'
@@ -103,6 +117,7 @@ export const useTheme = () => {
 	// init theme
 	const initTheme = () => {
 		switchDark()
+		watermarkChange()
 		if (isGrey.value) changeGreyOrWeak('grey', true)
 		if (isWeak.value) changeGreyOrWeak('weak', true)
 	}
@@ -124,6 +139,8 @@ export const useTheme = () => {
 		switchDark,
 		changeThemeColor,
 		changeGreyOrWeak,
+		watermarkChange,
+		watermarkTextChange,
 		setAsideTheme,
 		setHeaderTheme,
 		setFooterTheme

+ 127 - 0
src/hooks/useWatermark.ts

@@ -0,0 +1,127 @@
+import { Ref, ref, shallowRef, unref } from 'vue'
+import { debounce } from '@/utils'
+type Attrs = {
+	textStyles?: {
+		font?: string
+		fillStyle?: string
+		rotate?: number
+		width?: number
+		height?: number
+		// [key: string]: any
+	}
+	styles?: { [key: string]: any }
+	// [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 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 updateWatermark = (
+		opts: {
+			str?: string
+			attrs?: Attrs
+			width?: number
+			height?: number
+		} = {}
+	) => {
+		const el = unref(watermarkEl)
+		if (!el) return
+		if (typeof opts.width !== 'undefined') {
+			el.style.width = `${opts.width}px`
+		}
+		if (typeof opts.height !== 'undefined') {
+			el.style.height = `${opts.height}px`
+		}
+		if (typeof opts.str !== 'undefined') {
+			el.style.background = `url(${createCanvasBase64(opts.str, opts.attrs)}) left top repeat`
+		}
+	}
+
+	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 = 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 }
+}

+ 2 - 0
src/lang/lance-element/en.ts

@@ -156,6 +156,8 @@ export default {
 				animate_fadeScale: 'Resizing Fade Away',
 				animate_zoomFade: 'Gradual Change',
 				animate_zoomOut: 'Flash',
+				isWatermark: 'Turn On Watermark',
+				watermarkText: 'Watermark Copy',
 				reset: 'Reset The Current Configuration'
 			}
 		}

+ 2 - 0
src/lang/lance-element/zh-cn.ts

@@ -154,6 +154,8 @@ export default {
 				animate_fadeScale: '缩放消退',
 				animate_zoomFade: '渐变',
 				animate_zoomOut: '闪现',
+				isWatermark: '开启水印',
+				watermarkText: '水印文案',
 				reset: '重置当前配置'
 			}
 		}

+ 17 - 3
src/layout/components/Settings/index.vue

@@ -7,7 +7,7 @@
 				</el-divider>
 				<div class="drawer-item">
 					<span>{{ $t('le.layout.setting.themeColor') }}</span>
-					<theme-picker @change="themeChange" />
+					<ThemePicker />
 				</div>
 				<div class="drawer-item" @click.stop="">
 					<span>{{ $t('le.layout.setting.themeDark') }}</span>
@@ -34,6 +34,16 @@
 					<el-switch v-model="footerInverted" @change="setFooterTheme" />
 				</div>
 
+				<div class="drawer-item">
+					<span>{{ $t('le.layout.setting.isWatermark') }}</span>
+					<el-switch v-model="isWatermark" inline-prompt @change="watermarkChange" />
+				</div>
+
+				<div v-show="isWatermark" class="drawer-item">
+					<span>{{ $t('le.layout.setting.watermarkText') }}</span>
+					<el-input v-model="watermarkText" style="width: 160px" @input="wartermarkTextChange" />
+				</div>
+
 				<el-divider class="local-divider">
 					<el-icon><Menu /></el-icon>{{ $t('le.layout.setting.layoutMode') }}
 				</el-divider>
@@ -156,7 +166,7 @@ import { ElMessage } from 'element-plus'
 // const isDark = useDark()
 // const toggleDark = () => useToggle(isDark)
 const { setting } = useStore()
-const { switchDark, changeGreyOrWeak, setAsideTheme, setHeaderTheme, setFooterTheme, resetTheme } = useTheme()
+const { switchDark, changeGreyOrWeak, setAsideTheme, setHeaderTheme, setFooterTheme, resetTheme, watermarkChange, watermarkTextChange } = useTheme()
 const handle_resetTheme = () => {
 	resetTheme()
 	ElMessage.success('le.message.resetSuccess')
@@ -219,12 +229,16 @@ const {
 	tabsMode,
 	animate,
 	animateMode,
-	settingsVisible
+	settingsVisible,
+	isWatermark,
+	watermarkText
 } = storeToRefs(setting)
 
+/*
 function themeChange(val: any) {
 	setting.changeSetting('themeColor', val)
 }
+*/
 
 // 设置布局方式
 const setLayout = (val: LayoutType) => {

+ 4 - 0
src/settings.ts

@@ -14,6 +14,10 @@ export const defaultSettingState: SettingState = {
 	isGrey: false,
 	// 色弱模式
 	isWeak: false,
+	// 开启水印
+	isWatermark: true,
+	// 水印文案
+	watermarkText: name,
 	// 侧边栏深色
 	asideInverted: false,
 	// 头部深色

+ 4 - 0
src/types/store.d.ts

@@ -43,6 +43,10 @@ export interface SettingState {
 	isGrey: boolean
 	// 色弱模式
 	isWeak: boolean
+	// 开启水印
+	isWatermark: boolean
+	// 水印文案
+	watermarkText: string
 	// 侧边栏深色
 	asideInverted: boolean
 	// 头部深色

+ 87 - 0
src/views/components/components/Watermark.vue

@@ -0,0 +1,87 @@
+<script setup lang="ts">
+import { onMounted, reactive, ref, watch } from 'vue'
+import { useWatermark } from '@/hooks/useWatermark'
+const watermarkRef = ref()
+const textStyles = reactive({
+	text: '水印测试',
+	color: '#ff0000',
+	fontSize: 12,
+	rotate: 20,
+	width: 200,
+	height: 140
+})
+let watermark_obj = {}
+onMounted(() => {
+	watermark_obj = useWatermark({ appendEl: watermarkRef.value })
+	update_watermark()
+})
+
+const update_watermark = () => {
+	watermark_obj.setWatermark?.(textStyles.text, {
+		textStyles: {
+			fillStyle: textStyles.color,
+			font: `${textStyles.fontSize}px Vedana`,
+			rotate: textStyles.rotate,
+			width: textStyles.width,
+			height: textStyles.height
+		},
+		styles: { position: 'relative' }
+	})
+}
+watch(
+	textStyles,
+	update_watermark /*, {
+	immediate: true
+}*/
+)
+</script>
+
+<template>
+	<div class="common_title">水印文案 使用</div>
+	<div class="content">
+		<div class="watermark-wrap">
+			<div ref="watermarkRef" class="watermark" />
+			<div class="actions">
+				<el-form label-position="top" label-width="100px" :model="textStyles">
+					<el-form-item label="文字">
+						<el-input v-model="textStyles.text" />
+					</el-form-item>
+					<el-form-item label="字体颜色">
+						<el-color-picker v-model="textStyles.color" />
+					</el-form-item>
+					<el-form-item label="字体大小">
+						<el-slider v-model="textStyles.fontSize" />
+					</el-form-item>
+					<el-form-item label="字体旋转">
+						<el-slider v-model="textStyles.rotate" :min="-60" :max="60" />
+					</el-form-item>
+					<el-form-item label="canvas 宽">
+						<el-slider v-model="textStyles.width" :max="500" />
+					</el-form-item>
+					<el-form-item label="canvas 高">
+						<el-slider v-model="textStyles.height" :max="500" />
+					</el-form-item>
+				</el-form>
+			</div>
+		</div>
+	</div>
+</template>
+
+<style scoped lang="scss">
+.watermark {
+	flex: 1;
+	min-width: 500px;
+	height: 500px;
+	background: #000;
+}
+.watermark-wrap {
+	display: flex;
+	.actions {
+		flex: 1;
+		max-width: 460px;
+		margin-left: 10px;
+		padding-left: 10px;
+		border-left: 1px dashed var(--el-color-primary);
+	}
+}
+</style>

+ 2 - 0
src/views/components/index.vue

@@ -16,6 +16,7 @@
 		<LeSelectDemo v-if="true" />
 		<InputNumberDemo v-if="true" />
 		<LeDraggableNestDemo v-if="true" />
+		<Watermark v-if="true" />
 
 		<div class="common_title">le-iconfont && LeIcon</div>
 		<div class="content">
@@ -101,6 +102,7 @@ import SearchGroup2Popover from './components/SearchGroup2Popover'
 import LeSelectDemo from './components/LeSelectDemo'
 import InputNumberDemo from './components/InputNumberDemo'
 import LeDraggableNestDemo from './components/LeDraggableNestDemo'
+import Watermark from './components/Watermark'
 import useStore from '@/store/index'
 import { useI18n } from 'vue-i18n'
 // import i18n from '@/lang'