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
## 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.
```js
// main.[js|ts]
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 { createApp } from 'vue';
import App from "./App.vue";
import Antd from 'ant-design-vue';
import ProLayout, { PageContainer } from '@ant-design-vue/pro-layout';
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:
......@@ -53,94 +56,36 @@ After that, you can use pro-layout in your Vue components as simply as this:
<template>
<pro-layout
:locale="locale"
v-bind="state"
v-model:openKeys="base.openKeys"
v-model:collapsed="base.collapsed"
v-model:selectedKeys="base.selectedKeys"
v-bind="layoutConf"
v-model:openKeys="state.openKeys"
v-model:collapsed="state.collapsed"
v-model:selectedKeys="state.selectedKeys"
>
<router-view />
</pro-layout>
</template>
<script>
import { defineComponent, reactive } from 'vue';
<script setup lang="ts">
import { reactive, useRouter } from 'vue';
import { getMenuData, clearMenuItem } from '@ant-design-vue/pro-layout';
const locale = (i18n: string) => i18n;
const router = useRouter();
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,
});
return {
locale,
base,
state,
};
},
});
</script>
```
or `TSX`
const { menuData } = getMenuData(clearMenuItem(router.getRoutes()));
```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({
const state = reactive({
collapsed: false, // default value
openKeys: ['/dashboard'],
selectedKeys: ['/welcome'],
})
const state = reactive({
})
const layoutConf = 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>
);
},
});
</script>
```
......@@ -176,6 +121,38 @@ export default defineComponent({
| menuSubItemRender | custom render Menu.SubItem | v-slot#menuSubItemRender="{ item, icon }" \| ({ item, icon }) => VNode | null |
| 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
......@@ -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
......
import type { CSSProperties, FunctionalComponent as FC } from 'vue';
import { computed, ref, watchEffect } from 'vue';
import { useRouteContext } from '../RouteContext';
import type { CSSProperties, PropType, ExtractPropTypes } from 'vue'
import { defineComponent, computed, ref, watchEffect } from 'vue'
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 */
zIndex?: number;
zIndex: Number,
/** 水印的宽度 */
width?: number;
width: Number,
/** 水印的高度 */
height?: number;
height: Number,
/** 水印在canvas 画布上绘制的垂直偏移量,正常情况下,水印绘制在中间位置, 即 offsetTop = gapY / 2 */
offsetTop?: number; // 水印图片距离绘制 canvas 单元的顶部距离
offsetTop: Number, // 水印图片距离绘制 canvas 单元的顶部距离
/** 水印在canvas 画布上绘制的水平偏移量, 正常情况下,水印绘制在中间位置, 即 offsetTop = gapX / 2 */
offsetLeft?: number;
offsetLeft: Number,
/** 水印绘制时,旋转的角度,单位 ° */
rotate?: number;
/** ClassName 前缀 */
prefixCls?: string;
rotate: Number,
/** 高清印图片源, 为了高清屏幕显示,建议使用 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像素分辨率之比
*
......@@ -48,7 +54,7 @@ export type WaterMarkProps = {
*/
const getPixelRatio = (context: any) => {
if (!context) {
return 1;
return 1
}
const backingStore =
context.backingStorePixelRatio ||
......@@ -57,11 +63,13 @@ const getPixelRatio = (context: any) => {
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio ||
context.backingStorePixelRatio ||
1;
return (window.devicePixelRatio || 1) / backingStore;
};
1
return (window.devicePixelRatio || 1) / backingStore
}
const WaterMark: FC<WaterMarkProps> = (props, { slots }) => {
const WaterMark = defineComponent({
props: waterMarkProps,
setup(props, { slots }) {
const {
markStyle,
markClassName,
......@@ -82,60 +90,60 @@ const WaterMark: FC<WaterMarkProps> = (props, { slots }) => {
fontSize = 16,
fontFamily = 'sans-serif',
prefixCls: customizePrefixCls,
} = props;
} = props
const { getPrefixCls } = useRouteContext();
const prefixCls = getPrefixCls('pro-layout-watermark', customizePrefixCls);
const wrapperCls = computed(() => `${prefixCls}-wrapper`);
const { getPrefixCls } = useRouteContext()
const prefixCls = getPrefixCls('pro-layout-watermark', customizePrefixCls)
const wrapperCls = computed(() => `${prefixCls}-wrapper`)
const waterMakrCls = computed(() => {
return {
[`${prefixCls}`]: prefixCls,
[`${markClassName}`]: markClassName,
};
});
const base64Url = ref('');
}
})
const base64Url = ref('')
watchEffect(() => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const ratio = getPixelRatio(ctx);
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const ratio = getPixelRatio(ctx)
const canvasWidth = `${(gapX + width) * ratio}px`;
const canvasHeight = `${(gapY + height) * ratio}px`;
const canvasOffsetLeft = offsetLeft || gapX / 2;
const canvasOffsetTop = offsetTop || gapY / 2;
const canvasWidth = `${(gapX + width) * ratio}px`
const canvasHeight = `${(gapY + height) * ratio}px`
const canvasOffsetLeft = offsetLeft || gapX / 2
const canvasOffsetTop = offsetTop || gapY / 2
canvas.setAttribute('width', canvasWidth);
canvas.setAttribute('height', canvasHeight);
canvas.setAttribute('width', canvasWidth)
canvas.setAttribute('height', canvasHeight)
if (ctx) {
// 旋转字符 rotate
ctx.translate(canvasOffsetLeft * ratio, canvasOffsetTop * ratio);
ctx.rotate((Math.PI / 180) * Number(rotate));
const markWidth = width * ratio;
const markHeight = height * ratio;
ctx.translate(canvasOffsetLeft * ratio, canvasOffsetTop * ratio)
ctx.rotate((Math.PI / 180) * Number(rotate))
const markWidth = width * ratio
const markHeight = height * ratio
if (image) {
const img = new Image();
img.crossOrigin = 'anonymous';
img.referrerPolicy = 'no-referrer';
img.src = image;
const img = new Image()
img.crossOrigin = 'anonymous'
img.referrerPolicy = 'no-referrer'
img.src = image
img.onload = () => {
ctx.drawImage(img, 0, 0, markWidth, markHeight);
base64Url.value = canvas.toDataURL();
};
ctx.drawImage(img, 0, 0, markWidth, markHeight)
base64Url.value = canvas.toDataURL()
}
} else if (content) {
const markSize = Number(fontSize) * ratio;
ctx.font = `${fontStyle} normal ${fontWeight} ${markSize}px/${markHeight}px ${fontFamily}`;
ctx.fillStyle = fontColor;
ctx.fillText(content, 0, 0);
base64Url.value = canvas.toDataURL();
const markSize = Number(fontSize) * ratio
ctx.font = `${fontStyle} normal ${fontWeight} ${markSize}px/${markHeight}px ${fontFamily}`
ctx.fillStyle = fontColor
ctx.fillText(content, 0, 0)
base64Url.value = canvas.toDataURL()
}
} else {
// eslint-disable-next-line no-console
console.error('当前环境不支持Canvas');
console.error('当前环境不支持Canvas')
}
});
})
return (
<div
......@@ -162,7 +170,8 @@ const WaterMark: FC<WaterMarkProps> = (props, { slots }) => {
}}
/>
</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