Commit 0f46541a authored by Sendya's avatar Sendya

fix: watermark

parent 02f6d46b
...@@ -28,23 +28,26 @@ npm i @ant-design-vue/pro-layout@next -S ...@@ -28,23 +28,26 @@ npm i @ant-design-vue/pro-layout@next -S
## Basic Usage ## Basic Usage
look [Examples](./examples/) | Use Template https://github.com/sendya/preview-pro Recommend look [Examples](./examples/) | **Use Template https://github.com/sendya/preview-pro **
### Simple Usage
First, you should add the `@ant-design-vue/pro-layout` that you need into the library. First, you should add the `@ant-design-vue/pro-layout` that you need into the library.
```js ```js
// main.[js|ts]
import 'ant-design-vue/dist/antd.less'; // antd css import 'ant-design-vue/dist/antd.less'; // antd css
import '@ant-design-vue/pro-layout/dist/style.css'; // pro-layout css or style.less import '@ant-design-vue/pro-layout/dist/style.css'; // pro-layout css or style.less
import { createApp } from 'vue'; import { createApp } from 'vue';
import App from "./App.vue"; import App from "./App.vue";
import Antd from 'ant-design-vue';
import ProLayout, { PageContainer } from '@ant-design-vue/pro-layout'; import ProLayout, { PageContainer } from '@ant-design-vue/pro-layout';
const app = createApp(App); const app = createApp(App);
app.use(ProLayout).use(PageContainer).mount('#app'); app.use(Antd).use(ProLayout).use(PageContainer).mount('#app');
``` ```
After that, you can use pro-layout in your Vue components as simply as this: After that, you can use pro-layout in your Vue components as simply as this:
...@@ -53,96 +56,38 @@ After that, you can use pro-layout in your Vue components as simply as this: ...@@ -53,96 +56,38 @@ After that, you can use pro-layout in your Vue components as simply as this:
<template> <template>
<pro-layout <pro-layout
:locale="locale" :locale="locale"
v-bind="state" v-bind="layoutConf"
v-model:openKeys="base.openKeys" v-model:openKeys="state.openKeys"
v-model:collapsed="base.collapsed" v-model:collapsed="state.collapsed"
v-model:selectedKeys="base.selectedKeys" v-model:selectedKeys="state.selectedKeys"
> >
<router-view /> <router-view />
</pro-layout> </pro-layout>
</template> </template>
<script> <script setup lang="ts">
import { defineComponent, reactive } from 'vue'; import { reactive, useRouter } from 'vue';
import { getMenuData, clearMenuItem } from '@ant-design-vue/pro-layout'; import { getMenuData, clearMenuItem } from '@ant-design-vue/pro-layout';
const locale = (i18n: string) => i18n; const locale = (i18n: string) => i18n;
const router = useRouter();
export default defineComponent({
setup() { const { menuData } = getMenuData(clearMenuItem(router.getRoutes()));
const router = useRouter();
const { menuData } = getMenuData(clearMenuItem(router.getRoutes())); const state = reactive({
const base = reactive({ collapsed: false, // default value
collapsed: false, // default value openKeys: ['/dashboard'],
openKeys: ['/dashboard'], selectedKeys: ['/welcome'],
selectedKeys: ['/welcome'], })
}) const layoutConf = reactive({
const state = reactive({ navTheme: 'dark',
navTheme: 'dark', layout: 'mix',
layout: 'mix', splitMenus: false,
splitMenus: false, menuData,
menuData,
});
return {
locale,
base,
state,
};
},
}); });
</script> </script>
``` ```
or `TSX`
```tsx
import { defineComponent, reactive } from 'vue';
import { RouterView } from 'vue-router';
import ProLayout from '@ant-design-vue/pro-layout';
import '@ant-design-vue/pro-layout/dist/style.css'; // pro-layout css or style.less
export default defineComponent({
setup() {
const router = useRouter();
const { menuData } = getMenuData(clearMenuItem(router.getRoutes()));
const base = reactive({
collapsed: false, // default value
openKeys: ['/dashboard'],
selectedKeys: ['/welcome'],
})
const state = reactive({
navTheme: 'dark',
layout: 'mix',
splitMenus: false,
menuData,
});
const handleCollapse = (collapsed: boolean) => {
base.collapsed = collapsed;
}
const handleSelect = (selectedKeys: string[]) => {
base.selectedKeys = selectedKeys;
}
const handleOpenKeys = (openKeys: string[]) => {
base.openKeys = openKeys;
}
return () => (
<ProLayout
{...state}
{...base}
locale={(i18n: string) => i18n}
onCollapse={handleCollapse}
onSelect={handleSelect}
onOpenKeys={handleOpenKeys}
>
<RouterView />
</ProLayout>
);
},
});
```
## API ## API
...@@ -176,6 +121,38 @@ export default defineComponent({ ...@@ -176,6 +121,38 @@ export default defineComponent({
| menuSubItemRender | custom render Menu.SubItem | v-slot#menuSubItemRender="{ item, icon }" \| ({ item, icon }) => VNode | null | | menuSubItemRender | custom render Menu.SubItem | v-slot#menuSubItemRender="{ item, icon }" \| ({ item, icon }) => VNode | null |
| locale | i18n | Function (key: string) => string \| `false` | `false` | | locale | i18n | Function (key: string) => string \| `false` | `false` |
### PageContainer
| Property | Description | Type | Default Value |
| --- | --- | --- | --- |
| content | Content area | VNode \| v-slot | - |
| extra | Extra content area, on the right side of content | VNode \| v-slot | - |
| extraContent | Extra content area, on the right side of content | VNode \| v-slot | - |
| tabList | Tabs title list | `Array<{key: string, tab: sting}>` | - |
| tab-change | Switch panel callback | (key) => void | - |
| tab-active-key | The currently highlighted tab item | string | - |
### WaterMark
| Property | Description | Type | Default Value |
| ------------- | ------------------------------------- | ----------------- | ---------------------- |
| markStyle | mark style | CSSProperties | - |
| markClassName | mark class | string | - |
| gapX | Horizontal spacing between water-mark | number | 212 |
| gapY | Vertical spacing between watermark | number | 222 |
| offsetLeft | Horizontal offset | number | `offsetTop = gapX / 2` |
| offsetTop | Vertical offset | number | `offsetTop = gapY / 2` |
| | | | |
| width | | number | 120 |
| height | | number | 64 |
| rotate | Angle of rotation, unit ° | number | -22 |
| image | image src | string | - |
| zIndex | water-mark z-index | number | 9 |
| content | water-mark Content | string | - |
| fontColor | font-color | string | `rgba(0,0,0,.15)` |
| fontSize | font-size | string` | `number | 16 |
### Custom Render ### Custom Render
...@@ -248,17 +225,6 @@ export default defineComponent({ ...@@ -248,17 +225,6 @@ export default defineComponent({
``` ```
### PageContainer
| Property | Description | Type | Default Value |
| --- | --- | --- | --- |
| content | Content area | VNode \| v-slot | - |
| extra | Extra content area, on the right side of content | VNode \| v-slot | - |
| extraContent | Extra content area, on the right side of content | VNode \| v-slot | - |
| tabList | Tabs title list | `Array<{key: string, tab: sting}>` | - |
| tab-change | Switch panel callback | (key) => void | - |
| tab-active-key | The currently highlighted tab item | string | - |
## Build project ## Build project
......
import type { CSSProperties, FunctionalComponent as FC } from 'vue'; import type { CSSProperties, PropType, ExtractPropTypes } from 'vue'
import { computed, ref, watchEffect } from 'vue'; import { defineComponent, computed, ref, watchEffect } from 'vue'
import { useRouteContext } from '../RouteContext'; import { useRouteContext } from '../RouteContext'
export type WaterMarkProps = { export type FontStyle = 'none' | 'normal' | 'italic' | 'oblique'
export type FontWeight = 'normal' | 'light' | 'weight' | number
export const waterMarkProps = {
/** ClassName 前缀 */
prefixCls: String,
/** 水印样式 */ /** 水印样式 */
markStyle?: CSSProperties; markStyle: [String, Object] as PropType<CSSProperties>,
/** 水印类名 */ /** 水印类名 */
markClassName?: string; markClassName: String,
/** 水印之间的水平间距 */ /** 水印之间的水平间距 */
gapX?: number; gapX: Number,
/** 水印之间的垂直间距 */ /** 水印之间的垂直间距 */
gapY?: number; gapY: Number,
/** 追加的水印元素的z-index */ /** 追加的水印元素的z-index */
zIndex?: number; zIndex: Number,
/** 水印的宽度 */ /** 水印的宽度 */
width?: number; width: Number,
/** 水印的高度 */ /** 水印的高度 */
height?: number; height: Number,
/** 水印在canvas 画布上绘制的垂直偏移量,正常情况下,水印绘制在中间位置, 即 offsetTop = gapY / 2 */ /** 水印在canvas 画布上绘制的垂直偏移量,正常情况下,水印绘制在中间位置, 即 offsetTop = gapY / 2 */
offsetTop?: number; // 水印图片距离绘制 canvas 单元的顶部距离 offsetTop: Number, // 水印图片距离绘制 canvas 单元的顶部距离
/** 水印在canvas 画布上绘制的水平偏移量, 正常情况下,水印绘制在中间位置, 即 offsetTop = gapX / 2 */ /** 水印在canvas 画布上绘制的水平偏移量, 正常情况下,水印绘制在中间位置, 即 offsetTop = gapX / 2 */
offsetLeft?: number; offsetLeft: Number,
/** 水印绘制时,旋转的角度,单位 ° */ /** 水印绘制时,旋转的角度,单位 ° */
rotate?: number; rotate: Number,
/** ClassName 前缀 */
prefixCls?: string;
/** 高清印图片源, 为了高清屏幕显示,建议使用 2倍或3倍图,优先使用图片渲染水印。 */ /** 高清印图片源, 为了高清屏幕显示,建议使用 2倍或3倍图,优先使用图片渲染水印。 */
image?: string; image: String,
/** 水印文字内容 */ /** 水印文字内容 */
content?: string; content: String,
/** 文字颜色 */ /** 文字颜色 */
fontColor?: string; fontColor: String,
/** 文字样式 */ /** 文字样式 */
fontStyle?: 'none' | 'normal' | 'italic' | 'oblique'; fontStyle: String as PropType<FontStyle>,
/** 文字族 */ /** 文字族 */
fontFamily?: string; fontFamily: String,
/** 文字粗细 */ /** 文字粗细 */
fontWeight?: 'normal' | 'light' | 'weight' | number; fontWeight: [String, Number] as PropType<FontWeight>,
/** 文字大小 */ /** 文字大小 */
fontSize?: number | string; fontSize: [String, Number] as PropType<string | number>,
}; }
export type WaterMarkProps = Partial<ExtractPropTypes<typeof waterMarkProps>>
/** /**
* 返回当前显示设备的物理像素分辨率与CSS像素分辨率之比 * 返回当前显示设备的物理像素分辨率与CSS像素分辨率之比
* *
...@@ -48,7 +54,7 @@ export type WaterMarkProps = { ...@@ -48,7 +54,7 @@ export type WaterMarkProps = {
*/ */
const getPixelRatio = (context: any) => { const getPixelRatio = (context: any) => {
if (!context) { if (!context) {
return 1; return 1
} }
const backingStore = const backingStore =
context.backingStorePixelRatio || context.backingStorePixelRatio ||
...@@ -57,112 +63,115 @@ const getPixelRatio = (context: any) => { ...@@ -57,112 +63,115 @@ const getPixelRatio = (context: any) => {
context.msBackingStorePixelRatio || context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio || context.oBackingStorePixelRatio ||
context.backingStorePixelRatio || context.backingStorePixelRatio ||
1; 1
return (window.devicePixelRatio || 1) / backingStore; return (window.devicePixelRatio || 1) / backingStore
}; }
const WaterMark: FC<WaterMarkProps> = (props, { slots }) => { const WaterMark = defineComponent({
const { props: waterMarkProps,
markStyle, setup(props, { slots }) {
markClassName, const {
// antd 内容层 zIndex 基本上在 10 以下 https://github.com/ant-design/ant-design/blob/6192403b2ce517c017f9e58a32d58774921c10cd/components/style/themes/default.less#L335 markStyle,
zIndex = 9, markClassName,
gapX = 212, // antd 内容层 zIndex 基本上在 10 以下 https://github.com/ant-design/ant-design/blob/6192403b2ce517c017f9e58a32d58774921c10cd/components/style/themes/default.less#L335
gapY = 222, zIndex = 9,
width = 120, gapX = 212,
height = 64, gapY = 222,
rotate = -22, // 默认旋转 -22 度 width = 120,
image, height = 64,
content, rotate = -22, // 默认旋转 -22 度
offsetLeft, image,
offsetTop, content,
fontStyle = 'normal', offsetLeft,
fontWeight = 'normal', offsetTop,
fontColor = 'rgba(0,0,0,.15)', fontStyle = 'normal',
fontSize = 16, fontWeight = 'normal',
fontFamily = 'sans-serif', fontColor = 'rgba(0,0,0,.15)',
prefixCls: customizePrefixCls, fontSize = 16,
} = props; fontFamily = 'sans-serif',
prefixCls: customizePrefixCls,
} = props
const { getPrefixCls } = useRouteContext(); const { getPrefixCls } = useRouteContext()
const prefixCls = getPrefixCls('pro-layout-watermark', customizePrefixCls); const prefixCls = getPrefixCls('pro-layout-watermark', customizePrefixCls)
const wrapperCls = computed(() => `${prefixCls}-wrapper`); const wrapperCls = computed(() => `${prefixCls}-wrapper`)
const waterMakrCls = computed(() => { const waterMakrCls = computed(() => {
return { return {
[`${prefixCls}`]: prefixCls, [`${prefixCls}`]: prefixCls,
[`${markClassName}`]: markClassName, [`${markClassName}`]: markClassName,
}; }
}); })
const base64Url = ref(''); const base64Url = ref('')
watchEffect(() => { watchEffect(() => {
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d')
const ratio = getPixelRatio(ctx); const ratio = getPixelRatio(ctx)
const canvasWidth = `${(gapX + width) * ratio}px`; const canvasWidth = `${(gapX + width) * ratio}px`
const canvasHeight = `${(gapY + height) * ratio}px`; const canvasHeight = `${(gapY + height) * ratio}px`
const canvasOffsetLeft = offsetLeft || gapX / 2; const canvasOffsetLeft = offsetLeft || gapX / 2
const canvasOffsetTop = offsetTop || gapY / 2; const canvasOffsetTop = offsetTop || gapY / 2
canvas.setAttribute('width', canvasWidth); canvas.setAttribute('width', canvasWidth)
canvas.setAttribute('height', canvasHeight); canvas.setAttribute('height', canvasHeight)
if (ctx) { if (ctx) {
// 旋转字符 rotate // 旋转字符 rotate
ctx.translate(canvasOffsetLeft * ratio, canvasOffsetTop * ratio); ctx.translate(canvasOffsetLeft * ratio, canvasOffsetTop * ratio)
ctx.rotate((Math.PI / 180) * Number(rotate)); ctx.rotate((Math.PI / 180) * Number(rotate))
const markWidth = width * ratio; const markWidth = width * ratio
const markHeight = height * ratio; const markHeight = height * ratio
if (image) { if (image) {
const img = new Image(); const img = new Image()
img.crossOrigin = 'anonymous'; img.crossOrigin = 'anonymous'
img.referrerPolicy = 'no-referrer'; img.referrerPolicy = 'no-referrer'
img.src = image; img.src = image
img.onload = () => { img.onload = () => {
ctx.drawImage(img, 0, 0, markWidth, markHeight); ctx.drawImage(img, 0, 0, markWidth, markHeight)
base64Url.value = canvas.toDataURL(); base64Url.value = canvas.toDataURL()
}; }
} else if (content) { } else if (content) {
const markSize = Number(fontSize) * ratio; const markSize = Number(fontSize) * ratio
ctx.font = `${fontStyle} normal ${fontWeight} ${markSize}px/${markHeight}px ${fontFamily}`; ctx.font = `${fontStyle} normal ${fontWeight} ${markSize}px/${markHeight}px ${fontFamily}`
ctx.fillStyle = fontColor; ctx.fillStyle = fontColor
ctx.fillText(content, 0, 0); ctx.fillText(content, 0, 0)
base64Url.value = canvas.toDataURL(); base64Url.value = canvas.toDataURL()
}
} else {
// eslint-disable-next-line no-console
console.error('当前环境不支持Canvas')
} }
} else { })
// eslint-disable-next-line no-console
console.error('当前环境不支持Canvas');
}
});
return ( return (
<div
style={{
position: 'relative',
}}
class={wrapperCls.value}
>
{slots.default?.()}
<div <div
class={waterMakrCls.value}
style={{ style={{
zIndex, position: 'relative',
position: 'absolute',
left: 0,
top: 0,
width: '100%',
height: '100%',
backgroundSize: `${gapX + width}px`,
pointerEvents: 'none',
backgroundRepeat: 'repeat',
backgroundImage: `url('${base64Url.value}')`,
...markStyle,
}} }}
/> class={wrapperCls.value}
</div> >
); {slots.default?.()}
}; <div
class={waterMakrCls.value}
style={{
zIndex,
position: 'absolute',
left: 0,
top: 0,
width: '100%',
height: '100%',
backgroundSize: `${gapX + width}px`,
pointerEvents: 'none',
backgroundRepeat: 'repeat',
backgroundImage: `url('${base64Url.value}')`,
...markStyle,
}}
/>
</div>
)
},
})
export default WaterMark; export default WaterMark
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment