You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

233 lines
6.3 KiB
TypeScript

1 year ago
import type { CSSProperties, PropType } from 'vue'
import { getPrefixCls } from '../../RouteContext'
export type WaterMarkProps = {
/** 水印样式 */
markStyle?: CSSProperties
/** 水印类名 */
markClassName?: string
/** 水印之间的水平间距 */
gapX?: number
/** 水印之间的垂直间距 */
gapY?: number
/** 追加的水印元素的z-index */
zIndex?: number
/** 水印的宽度 */
width?: number
/** 水印的高度 */
height?: number
/** 水印在canvas 画布上绘制的垂直偏移量,正常情况下,水印绘制在中间位置, 即 offsetTop = gapY / 2 */
offsetTop?: number // 水印图片距离绘制 canvas 单元的顶部距离
/** 水印在canvas 画布上绘制的水平偏移量, 正常情况下,水印绘制在中间位置, 即 offsetTop = gapX / 2 */
offsetLeft?: number
/** 水印绘制时,旋转的角度,单位 ° */
rotate?: number
/** ClassName 前缀 */
prefixCls?: string
/** 高清印图片源, 为了高清屏幕显示,建议使用 2倍或3倍图优先使用图片渲染水印。 */
image?: string
/** 水印文字内容 */
content?: string | string[]
/** 文字颜色 */
fontColor?: string
/** 文字样式 */
fontStyle?: 'none' | 'normal' | 'italic' | 'oblique'
/** 文字族 */
fontFamily?: string
/** 文字粗细 */
fontWeight?: 'normal' | 'light' | 'weight' | number
/** 文字大小 */
fontSize?: number | string
}
const waterMarkProps = {
markStyle: {
type: Object as PropType<WaterMarkProps['markStyle']>,
default: () => undefined
},
markClassName: {
type: String as PropType<WaterMarkProps['markClassName']>,
default: ''
},
gapX: {
type: Number,
default: 212
},
gapY: {
type: Number,
default: 222
},
// antd 内容层 zIndex 基本上在 10 以下 https://github.com/ant-design/ant-design/blob/6192403b2ce517c017f9e58a32d58774921c10cd/components/style/themes/default.less#L335
zIndex: {
type: Number,
default: 9
},
width: {
type: Number,
default: 120
},
height: {
type: Number,
default: 64
},
offsetTop: {
type: Number,
default: undefined
},
offsetLeft: {
type: Number,
default: undefined
},
// 默认旋转 -22 度
rotate: {
type: Number,
default: -22
},
prefixCls: {
type: String,
default: ''
},
image: {
type: String,
default: ''
},
content: {
type: [String, Array] as PropType<WaterMarkProps['content']>,
default: ''
},
fontColor: {
type: String,
default: 'rgba(0,0,0,.15)'
},
fontStyle: {
type: String,
default: 'normal'
},
fontFamily: {
type: String,
default: 'sans-serif'
},
fontWeight: {
type: [Number, String] as PropType<WaterMarkProps['fontWeight']>,
default: 'normal'
},
fontSize: {
type: [Number, String] as PropType<WaterMarkProps['fontSize']>,
default: 16
}
}
/**
* CSS
*
* @param context
* @see api CanvasRenderingContext2D
*/
const getPixelRatio = (context: any) => {
if (!context) {
return 1
}
const backingStore =
context.backingStorePixelRatio ||
context.webkitBackingStorePixelRatio ||
context.mozBackingStorePixelRatio ||
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio ||
context.backingStorePixelRatio ||
1
return (window.devicePixelRatio || 1) / backingStore
}
const WaterMark = defineComponent({
name: 'WaterMark',
props: waterMarkProps,
setup(props, { slots, attrs }) {
const prefixCls = getPrefixCls('pro-layout-watermark', props.prefixCls)
const wrapperCls = [`${prefixCls}-wrapper`, attrs.class]
const waterMakrCls = [prefixCls, props.markClassName]
const base64Url = ref('')
watchEffect(() => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const ratio = getPixelRatio(ctx)
const canvasWidth = `${(props.gapX + props.width) * ratio}px`
const canvasHeight = `${(props.gapY + props.height) * ratio}px`
const canvasOffsetLeft = props.offsetLeft || props.gapX / 2
const canvasOffsetTop = props.offsetTop || props.gapY / 2
canvas.setAttribute('width', canvasWidth)
canvas.setAttribute('height', canvasHeight)
if (ctx) {
// 旋转字符 rotate
ctx.translate(canvasOffsetLeft * ratio, canvasOffsetTop * ratio)
ctx.rotate((Math.PI / 180) * Number(props.rotate))
const markWidth = props.width * ratio
const markHeight = props.height * ratio
if (props.image) {
const img = new Image()
img.crossOrigin = 'anonymous'
img.referrerPolicy = 'no-referrer'
img.src = props.image
img.onload = () => {
ctx.drawImage(img, 0, 0, markWidth, markHeight)
base64Url.value = canvas.toDataURL()
}
} else if (props.content) {
const markSize = Number(props.fontSize) * ratio
ctx.font = `${props.fontStyle} normal ${props.fontWeight} ${markSize}px/${markHeight}px ${props.fontFamily}`
ctx.fillStyle = props.fontColor
if (Array.isArray(props.content)) {
props.content?.forEach((item: string, index: number) =>
ctx.fillText(item, 0, index * 50)
)
} else {
ctx.fillText(props.content, 0, 0)
}
base64Url.value = canvas.toDataURL()
}
} else {
// eslint-disable-next-line no-console
console.error('当前环境不支持Canvas')
}
})
return () => (
<div
style={{
position: 'relative',
...(attrs.style as CSSProperties)
}}
class={wrapperCls}
>
{slots.default?.()}
<div
class={waterMakrCls}
style={{
zIndex: props.zIndex,
position: 'absolute',
left: 0,
top: 0,
width: '100%',
height: '100%',
backgroundSize: `${props.gapX + props.width}px`,
pointerEvents: 'none',
backgroundRepeat: 'repeat',
...(base64Url
? {
backgroundImage: `url('${base64Url}')`
}
: null),
...props.markStyle
}}
/>
</div>
)
}
})
export default WaterMark