添加 cl-watermark 水印组件
This commit is contained in:
@@ -323,6 +323,10 @@
|
||||
"签名",
|
||||
"Signature"
|
||||
],
|
||||
[
|
||||
"水印",
|
||||
"Watermark"
|
||||
],
|
||||
[
|
||||
"图片裁剪",
|
||||
"Image Cropping"
|
||||
@@ -2498,5 +2502,81 @@
|
||||
[
|
||||
"Calendar 日历",
|
||||
"Calendar Calendar"
|
||||
],
|
||||
[
|
||||
"这是一段需要保护的内容",
|
||||
"This is content that needs to be protected"
|
||||
],
|
||||
[
|
||||
"水印会覆盖在内容上方,防止内容被盗用",
|
||||
"Watermarks will be overlaid on the content to prevent unauthorized use"
|
||||
],
|
||||
[
|
||||
"可自定义的水印内容区域",
|
||||
"Customizable watermark content area"
|
||||
],
|
||||
[
|
||||
"水印文本",
|
||||
"Watermark Text"
|
||||
],
|
||||
[
|
||||
"字体大小",
|
||||
"Font Size"
|
||||
],
|
||||
[
|
||||
"透明度",
|
||||
"Opacity"
|
||||
],
|
||||
[
|
||||
"旋转角度",
|
||||
"Rotation Angle"
|
||||
],
|
||||
[
|
||||
"水印宽度",
|
||||
"Watermark Width"
|
||||
],
|
||||
[
|
||||
"水印高度",
|
||||
"Watermark Height"
|
||||
],
|
||||
[
|
||||
"水平间距",
|
||||
"Horizontal Spacing"
|
||||
],
|
||||
[
|
||||
"垂直间距",
|
||||
"Vertical Spacing"
|
||||
],
|
||||
[
|
||||
"字体粗细",
|
||||
"Font Weight"
|
||||
],
|
||||
[
|
||||
"正常",
|
||||
"Normal"
|
||||
],
|
||||
[
|
||||
"加粗",
|
||||
"Bold"
|
||||
],
|
||||
[
|
||||
"多行文本水印",
|
||||
"Multi-line Text Watermark"
|
||||
],
|
||||
[
|
||||
"重要文档",
|
||||
"Important Document"
|
||||
],
|
||||
[
|
||||
"这是一份重要的文档内容,需要添加水印保护。",
|
||||
"This is an important document that requires watermark protection."
|
||||
],
|
||||
[
|
||||
"水印可以防止内容被未授权的复制和传播。",
|
||||
"Watermarks can prevent unauthorized copying and distribution of content."
|
||||
],
|
||||
[
|
||||
"图片保护",
|
||||
"Image Protection"
|
||||
]
|
||||
]
|
||||
@@ -723,6 +723,10 @@
|
||||
"签名",
|
||||
""
|
||||
],
|
||||
[
|
||||
"水印",
|
||||
""
|
||||
],
|
||||
[
|
||||
"图片裁剪",
|
||||
""
|
||||
@@ -2498,5 +2502,81 @@
|
||||
[
|
||||
"我的",
|
||||
""
|
||||
],
|
||||
[
|
||||
"这是一段需要保护的内容",
|
||||
""
|
||||
],
|
||||
[
|
||||
"水印会覆盖在内容上方,防止内容被盗用",
|
||||
""
|
||||
],
|
||||
[
|
||||
"可自定义的水印内容区域",
|
||||
""
|
||||
],
|
||||
[
|
||||
"水印文本",
|
||||
""
|
||||
],
|
||||
[
|
||||
"字体大小",
|
||||
""
|
||||
],
|
||||
[
|
||||
"透明度",
|
||||
""
|
||||
],
|
||||
[
|
||||
"旋转角度",
|
||||
""
|
||||
],
|
||||
[
|
||||
"水印宽度",
|
||||
""
|
||||
],
|
||||
[
|
||||
"水印高度",
|
||||
""
|
||||
],
|
||||
[
|
||||
"水平间距",
|
||||
""
|
||||
],
|
||||
[
|
||||
"垂直间距",
|
||||
""
|
||||
],
|
||||
[
|
||||
"字体粗细",
|
||||
""
|
||||
],
|
||||
[
|
||||
"正常",
|
||||
""
|
||||
],
|
||||
[
|
||||
"加粗",
|
||||
""
|
||||
],
|
||||
[
|
||||
"多行文本水印",
|
||||
""
|
||||
],
|
||||
[
|
||||
"重要文档",
|
||||
""
|
||||
],
|
||||
[
|
||||
"这是一份重要的文档内容,需要添加水印保护。",
|
||||
""
|
||||
],
|
||||
[
|
||||
"水印可以防止内容被未授权的复制和传播。",
|
||||
""
|
||||
],
|
||||
[
|
||||
"图片保护",
|
||||
""
|
||||
]
|
||||
]
|
||||
@@ -448,6 +448,12 @@
|
||||
"navigationBarTitleText": "Sign 签名"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "other/watermark",
|
||||
"style": {
|
||||
"navigationBarTitleText": "Watermark 水印"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "other/day-uts",
|
||||
"style": {
|
||||
|
||||
147
pages/demo/other/watermark.uvue
Normal file
147
pages/demo/other/watermark.uvue
Normal file
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<cl-page>
|
||||
<view class="p-3">
|
||||
<demo-item :label="t('自定义样式')">
|
||||
<view class="flex">
|
||||
<cl-watermark
|
||||
:text="customText"
|
||||
:font-size="fontSize"
|
||||
:color="color"
|
||||
:dark-color="darkColor"
|
||||
:opacity="opacity"
|
||||
:rotate="rotate"
|
||||
:width="width"
|
||||
:height="height"
|
||||
:gap-x="gapX"
|
||||
:gap-y="gapY"
|
||||
:font-weight="fontWeight"
|
||||
>
|
||||
<view
|
||||
class="flex flex-col p-4 rounded-xl bg-surface-50 dark:bg-surface-800 h-[400rpx]"
|
||||
>
|
||||
<cl-text>
|
||||
明月几时有?把酒问青天。 不知天上宫阙,今夕是何年。
|
||||
我欲乘风归去,又恐琼楼玉宇,高处不胜寒。 起舞弄清影,何似在人间。
|
||||
</cl-text>
|
||||
</view>
|
||||
</cl-watermark>
|
||||
</view>
|
||||
|
||||
<cl-list border class="mt-3">
|
||||
<cl-list-item :label="t('水印文本')">
|
||||
<cl-input
|
||||
v-model="customText"
|
||||
placeholder="请输入水印文本"
|
||||
:pt="{ className: 'w-[300rpx]' }"
|
||||
/>
|
||||
</cl-list-item>
|
||||
|
||||
<cl-list-item :label="t('字体大小')">
|
||||
<view class="w-[300rpx]">
|
||||
<cl-slider v-model="fontSize" :min="12" :max="32" :step="1"></cl-slider>
|
||||
</view>
|
||||
</cl-list-item>
|
||||
|
||||
<!-- #ifndef APP -->
|
||||
<cl-list-item :label="t('透明度')">
|
||||
<view class="w-[300rpx]">
|
||||
<cl-slider
|
||||
v-model="opacity"
|
||||
:min="0.1"
|
||||
:max="1"
|
||||
:step="0.05"
|
||||
></cl-slider>
|
||||
</view>
|
||||
</cl-list-item>
|
||||
<!-- #endif -->
|
||||
|
||||
<cl-list-item :label="t('旋转角度')">
|
||||
<view class="w-[300rpx]">
|
||||
<cl-slider
|
||||
v-model="rotate"
|
||||
:min="-180"
|
||||
:max="180"
|
||||
:step="5"
|
||||
></cl-slider>
|
||||
</view>
|
||||
</cl-list-item>
|
||||
|
||||
<cl-list-item :label="t('水印宽度')">
|
||||
<view class="w-[300rpx]">
|
||||
<cl-slider v-model="width" :min="80" :max="300" :step="10"></cl-slider>
|
||||
</view>
|
||||
</cl-list-item>
|
||||
|
||||
<cl-list-item :label="t('水印高度')">
|
||||
<view class="w-[300rpx]">
|
||||
<cl-slider v-model="height" :min="40" :max="200" :step="10"></cl-slider>
|
||||
</view>
|
||||
</cl-list-item>
|
||||
|
||||
<cl-list-item :label="t('水平间距')">
|
||||
<view class="w-[300rpx]">
|
||||
<cl-slider v-model="gapX" :min="20" :max="200" :step="10"></cl-slider>
|
||||
</view>
|
||||
</cl-list-item>
|
||||
|
||||
<cl-list-item :label="t('垂直间距')">
|
||||
<view class="w-[300rpx]">
|
||||
<cl-slider v-model="gapY" :min="20" :max="200" :step="10"></cl-slider>
|
||||
</view>
|
||||
</cl-list-item>
|
||||
|
||||
<cl-list-item :label="t('字体粗细')">
|
||||
<cl-tabs
|
||||
v-model="fontWeight"
|
||||
:list="fontWeightList"
|
||||
:height="60"
|
||||
show-slider
|
||||
></cl-tabs>
|
||||
</cl-list-item>
|
||||
</cl-list>
|
||||
</demo-item>
|
||||
|
||||
<demo-item :label="t('图片保护')">
|
||||
<view class="flex">
|
||||
<cl-watermark text="© Cool UI" :width="200" :height="80" :opacity="0.9">
|
||||
<image
|
||||
src="https://unix.cool-js.com/images/demo/avatar.jpg"
|
||||
mode="aspectFit"
|
||||
class="w-full"
|
||||
></image>
|
||||
</cl-watermark>
|
||||
</view>
|
||||
</demo-item>
|
||||
</view>
|
||||
</cl-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import DemoItem from "../components/item.uvue";
|
||||
import { ref } from "vue";
|
||||
import { t } from "@/locale";
|
||||
import type { ClTabsItem } from "@/uni_modules/cool-ui";
|
||||
|
||||
const customText = ref("Cool UI");
|
||||
const fontSize = ref(16);
|
||||
const color = ref("rgba(0, 0, 0, 0.15)");
|
||||
const darkColor = ref("rgba(255, 255, 255, 0.15)");
|
||||
const opacity = ref(1);
|
||||
const rotate = ref(-22);
|
||||
const width = ref(120);
|
||||
const height = ref(64);
|
||||
const gapX = ref(100);
|
||||
const gapY = ref(100);
|
||||
const fontWeight = ref("normal");
|
||||
|
||||
const fontWeightList = [
|
||||
{
|
||||
label: t("正常"),
|
||||
value: "normal"
|
||||
},
|
||||
{
|
||||
label: t("加粗"),
|
||||
value: "bold"
|
||||
}
|
||||
] as ClTabsItem[];
|
||||
</script>
|
||||
@@ -421,6 +421,11 @@ const data = computed<Item[]>(() => {
|
||||
icon: "sketching",
|
||||
path: "/pages/demo/other/sign"
|
||||
},
|
||||
{
|
||||
label: t("水印"),
|
||||
icon: "copyright-line",
|
||||
path: "/pages/demo/other/watermark"
|
||||
},
|
||||
{
|
||||
label: t("图片裁剪"),
|
||||
icon: "crop-line",
|
||||
|
||||
277
uni_modules/cool-ui/components/cl-watermark/cl-watermark.uvue
Normal file
277
uni_modules/cool-ui/components/cl-watermark/cl-watermark.uvue
Normal file
@@ -0,0 +1,277 @@
|
||||
<template>
|
||||
<view class="cl-watermark" :class="[pt.className]">
|
||||
<view class="cl-watermark__content" :class="[pt.container?.className]">
|
||||
<slot></slot>
|
||||
</view>
|
||||
|
||||
<canvas
|
||||
ref="canvasRef"
|
||||
type="2d"
|
||||
:canvas-id="canvasId"
|
||||
:id="canvasId"
|
||||
class="cl-watermark__canvas"
|
||||
:style="{
|
||||
width: containerWidth + 'px',
|
||||
height: containerHeight + 'px',
|
||||
zIndex
|
||||
}"
|
||||
></canvas>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
onMounted,
|
||||
watch,
|
||||
getCurrentInstance,
|
||||
nextTick,
|
||||
shallowRef,
|
||||
onUnmounted
|
||||
} from "vue";
|
||||
import { getDevicePixelRatio, isDark, parsePt, uuid } from "@/cool";
|
||||
import type { PassThroughProps } from "../../types";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-watermark"
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
// 透传样式
|
||||
pt: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 水印文本
|
||||
text: {
|
||||
type: String,
|
||||
default: "Watermark"
|
||||
},
|
||||
// 水印字体大小
|
||||
fontSize: {
|
||||
type: Number,
|
||||
default: 16
|
||||
},
|
||||
// 水印文字颜色(浅色模式)
|
||||
color: {
|
||||
type: String,
|
||||
default: "rgba(0, 0, 0, 0.15)"
|
||||
},
|
||||
// 水印文字颜色(深色模式)
|
||||
darkColor: {
|
||||
type: String,
|
||||
default: "rgba(255, 255, 255, 0.15)"
|
||||
},
|
||||
// 水印透明度
|
||||
opacity: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
// 水印旋转角度
|
||||
rotate: {
|
||||
type: Number,
|
||||
default: -22
|
||||
},
|
||||
// 水印宽度
|
||||
width: {
|
||||
type: Number,
|
||||
default: 120
|
||||
},
|
||||
// 水印高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 64
|
||||
},
|
||||
// 水印之间的水平间距
|
||||
gapX: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
// 水印之间的垂直间距
|
||||
gapY: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
// 水印层级
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 9
|
||||
},
|
||||
// 字体粗细
|
||||
fontWeight: {
|
||||
type: String,
|
||||
default: "normal"
|
||||
},
|
||||
// 字体样式
|
||||
fontFamily: {
|
||||
type: String,
|
||||
default: "sans-serif"
|
||||
}
|
||||
});
|
||||
|
||||
// 透传样式类型定义
|
||||
type PassThrough = {
|
||||
className?: string;
|
||||
container?: PassThroughProps;
|
||||
};
|
||||
|
||||
// 解析透传样式配置
|
||||
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
||||
|
||||
// 获取当前实例
|
||||
const { proxy } = getCurrentInstance()!;
|
||||
|
||||
// 创建canvas实例
|
||||
const canvasRef = shallowRef<UniElement | null>(null);
|
||||
// 创建canvas ID
|
||||
const canvasId = `cl-watermark-${uuid()}`;
|
||||
|
||||
// 容器高度
|
||||
const containerWidth = ref(0);
|
||||
|
||||
// 容器宽度
|
||||
const containerHeight = ref(0);
|
||||
|
||||
// 计算当前水印颜色
|
||||
const currentColor = computed(() => {
|
||||
if (isDark.value) {
|
||||
return props.darkColor;
|
||||
}
|
||||
return props.color;
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取容器尺寸
|
||||
*/
|
||||
function getContainerSize(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
uni.createSelectorQuery()
|
||||
.in(proxy)
|
||||
.select(".cl-watermark")
|
||||
.boundingClientRect((rect) => {
|
||||
containerHeight.value = (rect as NodeInfo).height ?? 0;
|
||||
containerWidth.value = (rect as NodeInfo).width ?? 0;
|
||||
|
||||
resolve();
|
||||
})
|
||||
.exec();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制水印 - 使用Canvas
|
||||
*/
|
||||
async function drawWatermark() {
|
||||
await nextTick();
|
||||
|
||||
// 获取容器尺寸
|
||||
await getContainerSize();
|
||||
if (containerWidth.value <= 0 || containerHeight.value <= 0) return;
|
||||
|
||||
uni.createCanvasContextAsync({
|
||||
id: canvasId,
|
||||
component: proxy,
|
||||
success: (canvasContext: CanvasContext) => {
|
||||
const drawCtx = canvasContext.getContext("2d")!;
|
||||
|
||||
// 设置canvas尺寸
|
||||
drawCtx.canvas.width = containerWidth.value;
|
||||
drawCtx.canvas.height = containerHeight.value;
|
||||
|
||||
// 清空画布
|
||||
drawCtx.reset();
|
||||
drawCtx.clearRect(0, 0, containerWidth.value, containerHeight.value);
|
||||
|
||||
// 缩放画布以适配高分屏
|
||||
const ratio = getDevicePixelRatio();
|
||||
drawCtx.scale(ratio, ratio);
|
||||
|
||||
// 设置全局透明度
|
||||
drawCtx.globalAlpha = props.opacity;
|
||||
|
||||
// 设置字体
|
||||
drawCtx.font = `${props.fontWeight} ${props.fontSize}px ${props.fontFamily}`;
|
||||
drawCtx.fillStyle = currentColor.value;
|
||||
drawCtx.textAlign = "center";
|
||||
drawCtx.textBaseline = "middle";
|
||||
|
||||
// 计算水印单元的总宽高(包含间距)
|
||||
const cellWidth = props.width + props.gapX;
|
||||
const cellHeight = props.height + props.gapY;
|
||||
|
||||
// 计算需要多少行和列
|
||||
const cols = Math.ceil(containerWidth.value / cellWidth) + 1;
|
||||
const rows = Math.ceil(containerHeight.value / cellHeight) + 1;
|
||||
|
||||
// 遍历绘制水印
|
||||
for (let row = 0; row < rows; row++) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
const x = col * cellWidth + props.width / 2;
|
||||
const y = row * cellHeight + props.height / 2;
|
||||
|
||||
drawCtx.save();
|
||||
drawCtx.translate(x, y);
|
||||
drawCtx.rotate((props.rotate * Math.PI) / 180);
|
||||
drawCtx.fillText(props.text, 0, 0);
|
||||
drawCtx.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 监听深色模式变化
|
||||
const stopWatchDark = watch(isDark, () => {
|
||||
drawWatermark();
|
||||
});
|
||||
|
||||
// 监听属性变化
|
||||
const stopWatchProps = watch(
|
||||
computed(() => [
|
||||
props.text,
|
||||
props.fontSize,
|
||||
props.color,
|
||||
props.darkColor,
|
||||
props.opacity,
|
||||
props.rotate,
|
||||
props.width,
|
||||
props.height,
|
||||
props.gapX,
|
||||
props.gapY,
|
||||
props.fontWeight,
|
||||
props.fontFamily
|
||||
]),
|
||||
() => {
|
||||
drawWatermark();
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
drawWatermark();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
stopWatchDark();
|
||||
stopWatchProps();
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
refresh: drawWatermark
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-watermark {
|
||||
@apply relative w-full h-full;
|
||||
|
||||
&__content {
|
||||
@apply relative w-full h-full;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&__canvas {
|
||||
@apply absolute top-0 left-0 pointer-events-none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
24
uni_modules/cool-ui/components/cl-watermark/props.ts
Normal file
24
uni_modules/cool-ui/components/cl-watermark/props.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { PassThroughProps } from "../../types";
|
||||
|
||||
export type ClWatermarkPassThrough = {
|
||||
className?: string;
|
||||
container?: PassThroughProps;
|
||||
};
|
||||
|
||||
export type ClWatermarkProps = {
|
||||
className?: string;
|
||||
pt?: ClWatermarkPassThrough;
|
||||
text?: string;
|
||||
fontSize?: number;
|
||||
color?: string;
|
||||
darkColor?: string;
|
||||
opacity?: number;
|
||||
rotate?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
gapX?: number;
|
||||
gapY?: number;
|
||||
zIndex?: number;
|
||||
fontWeight?: string;
|
||||
fontFamily?: string;
|
||||
};
|
||||
2
uni_modules/cool-ui/index.d.ts
vendored
2
uni_modules/cool-ui/index.d.ts
vendored
@@ -76,6 +76,7 @@ import type { ClTreeProps, ClTreePassThrough } from "./components/cl-tree/props"
|
||||
import type { ClTreeItemProps, ClTreeItemPassThrough } from "./components/cl-tree-item/props";
|
||||
import type { ClUploadProps, ClUploadPassThrough } from "./components/cl-upload/props";
|
||||
import type { ClWaterfallProps, ClWaterfallPassThrough } from "./components/cl-waterfall/props";
|
||||
import type { ClWatermarkProps, ClWatermarkPassThrough } from "./components/cl-watermark/props";
|
||||
|
||||
export {};
|
||||
|
||||
@@ -156,5 +157,6 @@ declare module "vue" {
|
||||
"cl-tree-item": (typeof import('./components/cl-tree-item/cl-tree-item.uvue')['default']) & import('vue').DefineComponent<ClTreeItemProps>;
|
||||
"cl-upload": (typeof import('./components/cl-upload/cl-upload.uvue')['default']) & import('vue').DefineComponent<ClUploadProps>;
|
||||
"cl-waterfall": (typeof import('./components/cl-waterfall/cl-waterfall.uvue')['default']) & import('vue').DefineComponent<ClWaterfallProps>;
|
||||
"cl-watermark": (typeof import('./components/cl-watermark/cl-watermark.uvue')['default']) & import('vue').DefineComponent<ClWatermarkProps>;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user