@wecode-team/watermark 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # @wecode-team/watermark
2
+
3
+ We0 页面水印组件,支持自定义文字内容、重复平铺、旋转、透明度、字号、颜色和间距配置,适用于后台管理、预览页、演示页等场景。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install @wecode-team/watermark
9
+ # 或
10
+ pnpm add @wecode-team/watermark
11
+ # 或
12
+ yarn add @wecode-team/watermark
13
+ ```
14
+
15
+ > 需要 React >= 18 作为 peerDependency。
16
+
17
+ ## 快速开始
18
+
19
+ ```tsx
20
+ import { We0Watermark } from "@wecode-team/watermark"
21
+
22
+ export default function Page() {
23
+ return (
24
+ <We0Watermark content="WE0 INTERNAL">
25
+ <main style={{ minHeight: 400, padding: 24 }}>
26
+ 页面内容
27
+ </main>
28
+ </We0Watermark>
29
+ )
30
+ }
31
+ ```
32
+
33
+ ## 多行内容
34
+
35
+ ```tsx
36
+ <We0Watermark
37
+ content={["WE0", "Confidential"]}
38
+ rotate={-18}
39
+ opacity={0.1}
40
+ >
41
+ <section>敏感内容区域</section>
42
+ </We0Watermark>
43
+ ```
44
+
45
+ ## 自定义样式
46
+
47
+ ```tsx
48
+ <We0Watermark
49
+ content="Demo Preview"
50
+ fontSize={18}
51
+ color="#1f2937"
52
+ opacity={0.12}
53
+ gapX={120}
54
+ gapY={96}
55
+ width={200}
56
+ height={120}
57
+ zIndex={2}
58
+ >
59
+ <div>页面内容</div>
60
+ </We0Watermark>
61
+ ```
62
+
63
+ ## API
64
+
65
+ ### `<We0Watermark />`
66
+
67
+ | 参数 | 类型 | 默认值 | 说明 |
68
+ |------|------|--------|------|
69
+ | `content` | `string \| string[]` | - | 水印内容,支持单行或多行 |
70
+ | `width` | `number` | `160` | 单个水印绘制区域宽度 |
71
+ | `height` | `number` | `96` | 单个水印绘制区域高度 |
72
+ | `gapX` | `number` | `80` | 水印横向间距 |
73
+ | `gapY` | `number` | `80` | 水印纵向间距 |
74
+ | `rotate` | `number` | `-22` | 水印旋转角度 |
75
+ | `opacity` | `number` | `0.14` | 水印透明度 |
76
+ | `fontSize` | `number` | `16` | 字号 |
77
+ | `lineHeight` | `number` | `1.4` | 多行文字行高倍数 |
78
+ | `color` | `string` | `"#000000"` | 水印颜色 |
79
+ | `fontFamily` | `string` | `"sans-serif"` | 字体族 |
80
+ | `fontWeight` | `CSSProperties["fontWeight"]` | `500` | 字重 |
81
+ | `zIndex` | `number` | `1` | 水印层级 |
82
+ | `enabled` | `boolean` | `true` | 是否启用水印 |
83
+ | `className` | `string` | - | 外层容器类名 |
84
+ | `style` | `CSSProperties` | - | 外层容器样式 |
85
+ | `children` | `ReactNode` | - | 被包裹的页面内容 |
86
+
87
+ ## 说明
88
+
89
+ - 水印层默认 `pointer-events: none`,不会阻挡页面交互
90
+ - 组件通过 Canvas 生成背景图并重复平铺
91
+ - 在 SSR 场景下会先渲染内容,客户端挂载后补上水印背景
92
+ - React DevTools 中的组件展示名称为 `We0-Watermark`
93
+
94
+ ## License
95
+
96
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ We0Watermark: () => We0Watermark
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/components/Watermark.tsx
28
+ var import_react = require("react");
29
+
30
+ // src/utils.ts
31
+ var DEFAULT_OPTIONS = {
32
+ width: 160,
33
+ height: 96,
34
+ gapX: 80,
35
+ gapY: 80,
36
+ rotate: -22,
37
+ opacity: 0.14,
38
+ fontSize: 16,
39
+ lineHeight: 1.4,
40
+ color: "#000000",
41
+ fontFamily: "sans-serif",
42
+ fontWeight: 500,
43
+ zIndex: 1,
44
+ enabled: true
45
+ };
46
+ function resolveWatermarkOptions(props) {
47
+ const lines = (Array.isArray(props.content) ? props.content : [props.content]).map((line) => `${line}`.trim()).filter(Boolean);
48
+ return {
49
+ content: lines,
50
+ width: props.width ?? DEFAULT_OPTIONS.width,
51
+ height: props.height ?? DEFAULT_OPTIONS.height,
52
+ gapX: props.gapX ?? DEFAULT_OPTIONS.gapX,
53
+ gapY: props.gapY ?? DEFAULT_OPTIONS.gapY,
54
+ rotate: props.rotate ?? DEFAULT_OPTIONS.rotate,
55
+ opacity: props.opacity ?? DEFAULT_OPTIONS.opacity,
56
+ fontSize: props.fontSize ?? DEFAULT_OPTIONS.fontSize,
57
+ lineHeight: props.lineHeight ?? DEFAULT_OPTIONS.lineHeight,
58
+ color: props.color ?? DEFAULT_OPTIONS.color,
59
+ fontFamily: props.fontFamily ?? DEFAULT_OPTIONS.fontFamily,
60
+ fontWeight: props.fontWeight ?? DEFAULT_OPTIONS.fontWeight,
61
+ zIndex: props.zIndex ?? DEFAULT_OPTIONS.zIndex,
62
+ enabled: props.enabled ?? DEFAULT_OPTIONS.enabled
63
+ };
64
+ }
65
+ function buildFontStyle(fontSize, fontFamily, fontWeight) {
66
+ return `${fontWeight} ${fontSize}px ${fontFamily}`;
67
+ }
68
+ function createWatermarkDataUrl(options) {
69
+ if (typeof document === "undefined") {
70
+ return "";
71
+ }
72
+ const tileWidth = options.width + options.gapX;
73
+ const tileHeight = options.height + options.gapY;
74
+ const canvas = document.createElement("canvas");
75
+ const dpr = window.devicePixelRatio || 1;
76
+ canvas.width = tileWidth * dpr;
77
+ canvas.height = tileHeight * dpr;
78
+ canvas.style.width = `${tileWidth}px`;
79
+ canvas.style.height = `${tileHeight}px`;
80
+ const ctx = canvas.getContext("2d");
81
+ if (!ctx) return "";
82
+ ctx.scale(dpr, dpr);
83
+ ctx.clearRect(0, 0, tileWidth, tileHeight);
84
+ ctx.globalAlpha = options.opacity;
85
+ ctx.fillStyle = options.color;
86
+ ctx.font = buildFontStyle(
87
+ options.fontSize,
88
+ options.fontFamily,
89
+ options.fontWeight
90
+ );
91
+ ctx.textAlign = "center";
92
+ ctx.textBaseline = "middle";
93
+ ctx.translate(tileWidth / 2, tileHeight / 2);
94
+ ctx.rotate(options.rotate * Math.PI / 180);
95
+ const totalTextHeight = options.content.length * options.fontSize * options.lineHeight;
96
+ const startY = -totalTextHeight / 2 + options.fontSize * options.lineHeight / 2;
97
+ options.content.forEach((line, index) => {
98
+ const y = startY + index * options.fontSize * options.lineHeight;
99
+ ctx.fillText(line, 0, y);
100
+ });
101
+ return canvas.toDataURL();
102
+ }
103
+
104
+ // src/components/Watermark.tsx
105
+ var import_jsx_runtime = require("react/jsx-runtime");
106
+ function We0Watermark(props) {
107
+ const {
108
+ className,
109
+ style,
110
+ children
111
+ } = props;
112
+ const options = resolveWatermarkOptions(props);
113
+ const contentKey = options.content.join("\n");
114
+ const [dataUrl, setDataUrl] = (0, import_react.useState)("");
115
+ (0, import_react.useEffect)(() => {
116
+ if (!options.enabled || options.content.length === 0) {
117
+ setDataUrl("");
118
+ return;
119
+ }
120
+ setDataUrl(createWatermarkDataUrl(options));
121
+ }, [
122
+ options.color,
123
+ contentKey,
124
+ options.enabled,
125
+ options.fontFamily,
126
+ options.fontSize,
127
+ options.fontWeight,
128
+ options.gapX,
129
+ options.gapY,
130
+ options.height,
131
+ options.lineHeight,
132
+ options.opacity,
133
+ options.rotate,
134
+ options.width
135
+ ]);
136
+ const overlayStyle = {
137
+ position: "absolute",
138
+ inset: 0,
139
+ pointerEvents: "none",
140
+ zIndex: options.zIndex,
141
+ backgroundImage: dataUrl ? `url("${dataUrl}")` : void 0,
142
+ backgroundRepeat: "repeat",
143
+ backgroundSize: `${options.width + options.gapX}px ${options.height + options.gapY}px`
144
+ };
145
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
146
+ "div",
147
+ {
148
+ className,
149
+ style: {
150
+ position: "relative",
151
+ ...style
152
+ },
153
+ children: [
154
+ children,
155
+ options.enabled && options.content.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-hidden": "true", style: overlayStyle }) : null
156
+ ]
157
+ }
158
+ );
159
+ }
160
+ We0Watermark.displayName = "We0-Watermark";
161
+ // Annotate the CommonJS export names for ESM import in node:
162
+ 0 && (module.exports = {
163
+ We0Watermark
164
+ });
@@ -0,0 +1,57 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { CSSProperties, ReactNode } from 'react';
3
+
4
+ /** 页面水印组件属性 */
5
+ interface WatermarkProps {
6
+ /** 水印文本,支持单行或多行 */
7
+ content: string | string[];
8
+ /** 单个水印绘制区域宽度,默认 160 */
9
+ width?: number;
10
+ /** 单个水印绘制区域高度,默认 96 */
11
+ height?: number;
12
+ /** 水印之间的横向间距,默认 80 */
13
+ gapX?: number;
14
+ /** 水印之间的纵向间距,默认 80 */
15
+ gapY?: number;
16
+ /** 水印旋转角度,默认 -22 */
17
+ rotate?: number;
18
+ /** 水印透明度,默认 0.14 */
19
+ opacity?: number;
20
+ /** 字号,默认 16 */
21
+ fontSize?: number;
22
+ /** 行高倍数,默认 1.4 */
23
+ lineHeight?: number;
24
+ /** 字体颜色,默认 "#000000" */
25
+ color?: string;
26
+ /** 字体族,默认 "sans-serif" */
27
+ fontFamily?: string;
28
+ /** 字重,默认 500 */
29
+ fontWeight?: CSSProperties["fontWeight"];
30
+ /** 水印层级,默认 1 */
31
+ zIndex?: number;
32
+ /** 是否启用水印,默认 true */
33
+ enabled?: boolean;
34
+ /** 外层容器 className */
35
+ className?: string;
36
+ /** 外层容器样式 */
37
+ style?: CSSProperties;
38
+ /** 子节点内容 */
39
+ children?: ReactNode;
40
+ }
41
+
42
+ /**
43
+ * 页面水印组件
44
+ *
45
+ * @example
46
+ * ```tsx
47
+ * <We0Watermark content="WE0 INTERNAL">
48
+ * <main>页面内容</main>
49
+ * </We0Watermark>
50
+ * ```
51
+ */
52
+ declare function We0Watermark(props: WatermarkProps): react_jsx_runtime.JSX.Element;
53
+ declare namespace We0Watermark {
54
+ var displayName: string;
55
+ }
56
+
57
+ export { type WatermarkProps, We0Watermark };
@@ -0,0 +1,57 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { CSSProperties, ReactNode } from 'react';
3
+
4
+ /** 页面水印组件属性 */
5
+ interface WatermarkProps {
6
+ /** 水印文本,支持单行或多行 */
7
+ content: string | string[];
8
+ /** 单个水印绘制区域宽度,默认 160 */
9
+ width?: number;
10
+ /** 单个水印绘制区域高度,默认 96 */
11
+ height?: number;
12
+ /** 水印之间的横向间距,默认 80 */
13
+ gapX?: number;
14
+ /** 水印之间的纵向间距,默认 80 */
15
+ gapY?: number;
16
+ /** 水印旋转角度,默认 -22 */
17
+ rotate?: number;
18
+ /** 水印透明度,默认 0.14 */
19
+ opacity?: number;
20
+ /** 字号,默认 16 */
21
+ fontSize?: number;
22
+ /** 行高倍数,默认 1.4 */
23
+ lineHeight?: number;
24
+ /** 字体颜色,默认 "#000000" */
25
+ color?: string;
26
+ /** 字体族,默认 "sans-serif" */
27
+ fontFamily?: string;
28
+ /** 字重,默认 500 */
29
+ fontWeight?: CSSProperties["fontWeight"];
30
+ /** 水印层级,默认 1 */
31
+ zIndex?: number;
32
+ /** 是否启用水印,默认 true */
33
+ enabled?: boolean;
34
+ /** 外层容器 className */
35
+ className?: string;
36
+ /** 外层容器样式 */
37
+ style?: CSSProperties;
38
+ /** 子节点内容 */
39
+ children?: ReactNode;
40
+ }
41
+
42
+ /**
43
+ * 页面水印组件
44
+ *
45
+ * @example
46
+ * ```tsx
47
+ * <We0Watermark content="WE0 INTERNAL">
48
+ * <main>页面内容</main>
49
+ * </We0Watermark>
50
+ * ```
51
+ */
52
+ declare function We0Watermark(props: WatermarkProps): react_jsx_runtime.JSX.Element;
53
+ declare namespace We0Watermark {
54
+ var displayName: string;
55
+ }
56
+
57
+ export { type WatermarkProps, We0Watermark };
package/dist/index.js ADDED
@@ -0,0 +1,137 @@
1
+ // src/components/Watermark.tsx
2
+ import { useEffect, useState } from "react";
3
+
4
+ // src/utils.ts
5
+ var DEFAULT_OPTIONS = {
6
+ width: 160,
7
+ height: 96,
8
+ gapX: 80,
9
+ gapY: 80,
10
+ rotate: -22,
11
+ opacity: 0.14,
12
+ fontSize: 16,
13
+ lineHeight: 1.4,
14
+ color: "#000000",
15
+ fontFamily: "sans-serif",
16
+ fontWeight: 500,
17
+ zIndex: 1,
18
+ enabled: true
19
+ };
20
+ function resolveWatermarkOptions(props) {
21
+ const lines = (Array.isArray(props.content) ? props.content : [props.content]).map((line) => `${line}`.trim()).filter(Boolean);
22
+ return {
23
+ content: lines,
24
+ width: props.width ?? DEFAULT_OPTIONS.width,
25
+ height: props.height ?? DEFAULT_OPTIONS.height,
26
+ gapX: props.gapX ?? DEFAULT_OPTIONS.gapX,
27
+ gapY: props.gapY ?? DEFAULT_OPTIONS.gapY,
28
+ rotate: props.rotate ?? DEFAULT_OPTIONS.rotate,
29
+ opacity: props.opacity ?? DEFAULT_OPTIONS.opacity,
30
+ fontSize: props.fontSize ?? DEFAULT_OPTIONS.fontSize,
31
+ lineHeight: props.lineHeight ?? DEFAULT_OPTIONS.lineHeight,
32
+ color: props.color ?? DEFAULT_OPTIONS.color,
33
+ fontFamily: props.fontFamily ?? DEFAULT_OPTIONS.fontFamily,
34
+ fontWeight: props.fontWeight ?? DEFAULT_OPTIONS.fontWeight,
35
+ zIndex: props.zIndex ?? DEFAULT_OPTIONS.zIndex,
36
+ enabled: props.enabled ?? DEFAULT_OPTIONS.enabled
37
+ };
38
+ }
39
+ function buildFontStyle(fontSize, fontFamily, fontWeight) {
40
+ return `${fontWeight} ${fontSize}px ${fontFamily}`;
41
+ }
42
+ function createWatermarkDataUrl(options) {
43
+ if (typeof document === "undefined") {
44
+ return "";
45
+ }
46
+ const tileWidth = options.width + options.gapX;
47
+ const tileHeight = options.height + options.gapY;
48
+ const canvas = document.createElement("canvas");
49
+ const dpr = window.devicePixelRatio || 1;
50
+ canvas.width = tileWidth * dpr;
51
+ canvas.height = tileHeight * dpr;
52
+ canvas.style.width = `${tileWidth}px`;
53
+ canvas.style.height = `${tileHeight}px`;
54
+ const ctx = canvas.getContext("2d");
55
+ if (!ctx) return "";
56
+ ctx.scale(dpr, dpr);
57
+ ctx.clearRect(0, 0, tileWidth, tileHeight);
58
+ ctx.globalAlpha = options.opacity;
59
+ ctx.fillStyle = options.color;
60
+ ctx.font = buildFontStyle(
61
+ options.fontSize,
62
+ options.fontFamily,
63
+ options.fontWeight
64
+ );
65
+ ctx.textAlign = "center";
66
+ ctx.textBaseline = "middle";
67
+ ctx.translate(tileWidth / 2, tileHeight / 2);
68
+ ctx.rotate(options.rotate * Math.PI / 180);
69
+ const totalTextHeight = options.content.length * options.fontSize * options.lineHeight;
70
+ const startY = -totalTextHeight / 2 + options.fontSize * options.lineHeight / 2;
71
+ options.content.forEach((line, index) => {
72
+ const y = startY + index * options.fontSize * options.lineHeight;
73
+ ctx.fillText(line, 0, y);
74
+ });
75
+ return canvas.toDataURL();
76
+ }
77
+
78
+ // src/components/Watermark.tsx
79
+ import { jsx, jsxs } from "react/jsx-runtime";
80
+ function We0Watermark(props) {
81
+ const {
82
+ className,
83
+ style,
84
+ children
85
+ } = props;
86
+ const options = resolveWatermarkOptions(props);
87
+ const contentKey = options.content.join("\n");
88
+ const [dataUrl, setDataUrl] = useState("");
89
+ useEffect(() => {
90
+ if (!options.enabled || options.content.length === 0) {
91
+ setDataUrl("");
92
+ return;
93
+ }
94
+ setDataUrl(createWatermarkDataUrl(options));
95
+ }, [
96
+ options.color,
97
+ contentKey,
98
+ options.enabled,
99
+ options.fontFamily,
100
+ options.fontSize,
101
+ options.fontWeight,
102
+ options.gapX,
103
+ options.gapY,
104
+ options.height,
105
+ options.lineHeight,
106
+ options.opacity,
107
+ options.rotate,
108
+ options.width
109
+ ]);
110
+ const overlayStyle = {
111
+ position: "absolute",
112
+ inset: 0,
113
+ pointerEvents: "none",
114
+ zIndex: options.zIndex,
115
+ backgroundImage: dataUrl ? `url("${dataUrl}")` : void 0,
116
+ backgroundRepeat: "repeat",
117
+ backgroundSize: `${options.width + options.gapX}px ${options.height + options.gapY}px`
118
+ };
119
+ return /* @__PURE__ */ jsxs(
120
+ "div",
121
+ {
122
+ className,
123
+ style: {
124
+ position: "relative",
125
+ ...style
126
+ },
127
+ children: [
128
+ children,
129
+ options.enabled && options.content.length > 0 ? /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: overlayStyle }) : null
130
+ ]
131
+ }
132
+ );
133
+ }
134
+ We0Watermark.displayName = "We0-Watermark";
135
+ export {
136
+ We0Watermark
137
+ };
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@wecode-team/watermark",
3
+ "version": "0.0.1",
4
+ "description": "We0 页面水印组件 —— 支持自定义文字内容、重复平铺和样式配置",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "sideEffects": false,
25
+ "engines": {
26
+ "node": ">=18.0.0"
27
+ },
28
+ "scripts": {
29
+ "build": "tsup",
30
+ "dev": "tsup --watch",
31
+ "prepublishOnly": "npm run build"
32
+ },
33
+ "peerDependencies": {
34
+ "react": ">=18.0.0",
35
+ "react-dom": ">=18.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/react": "^19.0.0",
39
+ "react": "^19.0.0",
40
+ "tsup": "^8.4.0",
41
+ "typescript": "^5.9.2"
42
+ },
43
+ "keywords": [
44
+ "watermark",
45
+ "overlay",
46
+ "react",
47
+ "text-watermark",
48
+ "we0"
49
+ ],
50
+ "publishConfig": {
51
+ "access": "public"
52
+ },
53
+ "license": "MIT",
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "https://github.com/we0-org/we0-sdk.git",
57
+ "directory": "packages/watermark"
58
+ },
59
+ "homepage": "https://github.com/we0-org/we0-sdk/tree/main/packages/watermark#readme",
60
+ "bugs": {
61
+ "url": "https://github.com/we0-org/we0-sdk/issues"
62
+ }
63
+ }