From 88b4f13185cda24284b279c5e746ffee9ad9395e Mon Sep 17 00:00:00 2001 From: engineer Date: Tue, 14 Apr 2026 18:17:24 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat(watermark):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=B0=B4=E5=8D=B0=E7=BB=84=E4=BB=B6=E5=8F=8A=E7=A4=BA=E4=BE=8B?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 TWatermark 组件支持文本水印功能 - 实现单行、多行文本水印类型 - 提供水平、垂直、网格三种排列方式 - 支持自定义颜色、透明度、字体大小等样式 - 添加水印旋转和间距调整功能 - 集成水印组件到示例应用中 - 更新 iOS 最低版本要求至 13.0 - 添加完整的单元测试覆盖各种使用场景 --- .../ios/Flutter/AppFrameworkInfo.plist | 2 +- tdesign-component/example/ios/Podfile | 2 +- tdesign-component/example/ios/Podfile.lock | 13 +- .../ios/Runner.xcodeproj/project.pbxproj | 6 +- .../lib/component_test/watermark_demo.dart | 241 ++++++++++++++ tdesign-component/example/lib/config.dart | 5 + .../example/lib/page/t_watermark_page.dart | 313 ++++++++++++++++++ .../src/components/watermark/t_watermark.dart | 307 +++++++++++++++++ tdesign-component/lib/tdesign_flutter.dart | 1 + tdesign-component/test/t_watermark_test.dart | 166 ++++++++++ 10 files changed, 1048 insertions(+), 8 deletions(-) create mode 100644 tdesign-component/example/lib/component_test/watermark_demo.dart create mode 100644 tdesign-component/example/lib/page/t_watermark_page.dart create mode 100644 tdesign-component/lib/src/components/watermark/t_watermark.dart create mode 100644 tdesign-component/test/t_watermark_test.dart diff --git a/tdesign-component/example/ios/Flutter/AppFrameworkInfo.plist b/tdesign-component/example/ios/Flutter/AppFrameworkInfo.plist index 7c5696400..1dc6cf765 100644 --- a/tdesign-component/example/ios/Flutter/AppFrameworkInfo.plist +++ b/tdesign-component/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 12.0 + 13.0 diff --git a/tdesign-component/example/ios/Podfile b/tdesign-component/example/ios/Podfile index b331c7b2b..5981b62b3 100644 --- a/tdesign-component/example/ios/Podfile +++ b/tdesign-component/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '12.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/tdesign-component/example/ios/Podfile.lock b/tdesign-component/example/ios/Podfile.lock index 4c9913399..98199cfb8 100644 --- a/tdesign-component/example/ios/Podfile.lock +++ b/tdesign-component/example/ios/Podfile.lock @@ -2,21 +2,28 @@ PODS: - Flutter (1.0.0) - image_picker_ios (0.0.1): - Flutter + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS DEPENDENCIES: - Flutter (from `Flutter`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) EXTERNAL SOURCES: Flutter: :path: Flutter image_picker_ios: :path: ".symlinks/plugins/image_picker_ios/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" SPEC CHECKSUMS: - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326 + shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb -PODFILE CHECKSUM: cf0c950f7e9a456b4e325f5b8fc0f98906a3705a +PODFILE CHECKSUM: 53026eaf67b63baa2e316e457f9701dbdf33f41a COCOAPODS: 1.16.2 diff --git a/tdesign-component/example/ios/Runner.xcodeproj/project.pbxproj b/tdesign-component/example/ios/Runner.xcodeproj/project.pbxproj index a00363849..79f9c833d 100644 --- a/tdesign-component/example/ios/Runner.xcodeproj/project.pbxproj +++ b/tdesign-component/example/ios/Runner.xcodeproj/project.pbxproj @@ -357,7 +357,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -438,7 +438,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -487,7 +487,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/tdesign-component/example/lib/component_test/watermark_demo.dart b/tdesign-component/example/lib/component_test/watermark_demo.dart new file mode 100644 index 000000000..bf6bf9ec9 --- /dev/null +++ b/tdesign-component/example/lib/component_test/watermark_demo.dart @@ -0,0 +1,241 @@ +import 'package:flutter/material.dart'; +import 'package:tdesign_flutter/tdesign_flutter.dart'; + +/// TWatermark 水印组件使用示例 +class WatermarkDemo extends StatelessWidget { + const WatermarkDemo({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('TWatermark 水印组件演示'), + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 示例1: 基础水印 + _buildSection( + context, + '基础水印', + Container( + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: 'TDesign Flutter', + ), + ), + ), + + const SizedBox(height: 24), + + // 示例2: 不同布局 + _buildSection( + context, + '水平布局', + Container( + height: 150, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '内部资料', + layout: TWatermarkLayout.horizontal, + gapX: 150, + ), + ), + ), + + const SizedBox(height: 24), + + // 示例3: 自定义样式 + _buildSection( + context, + '自定义颜色和透明度', + Container( + height: 150, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '保密文档', + textColor: TTheme.of(context).errorNormalColor, + opacity: 0.2, + textSize: 18, + ), + ), + ), + + const SizedBox(height: 24), + + // 示例4: 图片上的水印 + _buildSection( + context, + '图片水印', + Container( + height: 250, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '仅供查看', + opacity: 0.15, + rotate: -30, + child: Center( + child: Container( + width: 200, + height: 200, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + TTheme.of(context).brandLightColor, + TTheme.of(context).brandFocusColor, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + TIcons.image, + size: 80, + color: TTheme.of(context).brandNormalColor, + ), + ), + ), + ), + ), + ), + + const SizedBox(height: 24), + + // 示例5: 列表上的水印 + _buildSection( + context, + '列表现水印', + Container( + height: 300, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '内部数据', + opacity: 0.08, + child: ListView.builder( + itemCount: 8, + itemBuilder: (context, index) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: TTheme.of(context).componentStrokeColor, + width: 0.5, + ), + ), + ), + child: Row( + children: [ + CircleAvatar( + backgroundColor: TTheme.of(context).brandLightColor, + child: Text('${index + 1}'), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TText( + '数据项 ${index + 1}', + font: TTheme.of(context).fontBodyMedium, + ), + const SizedBox(height: 4), + TText( + '这是第 ${index + 1} 条数据', + font: TTheme.of(context).fontBodySmall, + textColor: TTheme.of(context).textColorSecondary, + ), + ], + ), + ), + ], + ), + ); + }, + ), + ), + ), + ), + + const SizedBox(height: 24), + + // 示例6: 表单草稿状态 + _buildSection( + context, + '表单草稿标识', + Container( + height: 300, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '草稿', + opacity: 0.08, + textSize: 48, + fontWeight: FontWeight.bold, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + TInput( + leftLabel: '标题', + hintText: '请输入标题', + ), + const SizedBox(height: 12), + TInput( + leftLabel: '内容', + hintText: '请输入内容', + ), + const SizedBox(height: 12), + TButton( + text: '保存草稿', + theme: TButtonTheme.primary, + onTap: () {}, + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildSection(BuildContext context, String title, Widget content) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TText( + title, + font: TTheme.of(context).fontTitleMedium, + textColor: TTheme.of(context).textColorPrimary, + ), + const SizedBox(height: 12), + content, + ], + ); + } +} diff --git a/tdesign-component/example/lib/config.dart b/tdesign-component/example/lib/config.dart index 137502e39..d31812202 100644 --- a/tdesign-component/example/lib/config.dart +++ b/tdesign-component/example/lib/config.dart @@ -68,6 +68,7 @@ import 'page/t_time_counter_page.dart'; import 'page/t_toast_page.dart'; import 'page/t_tree_select_page.dart'; import 'page/t_upload_page.dart'; +import 'page/t_watermark_page.dart'; import 'page/todo_page.dart'; PageBuilder _wrapInheritedTheme(WidgetBuilder builder) { @@ -287,6 +288,10 @@ Map> exampleMap = { text: 'Tag 标签', name: 'tag', pageBuilder: _wrapInheritedTheme((context) => const TTagPage())), + ExamplePageModel( + text: 'Watermark 水印', + name: 'watermark', + pageBuilder: _wrapInheritedTheme((context) => const TWatermarkPage())), ], '反馈': [ ExamplePageModel( diff --git a/tdesign-component/example/lib/page/t_watermark_page.dart b/tdesign-component/example/lib/page/t_watermark_page.dart new file mode 100644 index 000000000..d409d14d7 --- /dev/null +++ b/tdesign-component/example/lib/page/t_watermark_page.dart @@ -0,0 +1,313 @@ +import 'package:flutter/material.dart'; +import 'package:tdesign_flutter/tdesign_flutter.dart'; + +import '../../annotation/demo.dart'; +import '../../base/example_widget.dart'; + +class TWatermarkPage extends StatelessWidget { + const TWatermarkPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ExamplePage( + title: '水印 Watermark', + exampleCodeGroup: 'watermark', + children: [ + ExampleModule(title: '基础用法', children: [ + ExampleItem(desc: '单行文本水印:', builder: _buildSingleLine), + ExampleItem(desc: '多行文本水印:', builder: _buildMultiLine), + ]), + ExampleModule(title: '排列方式', children: [ + ExampleItem(desc: '水平排列:', builder: _buildHorizontalLayout), + ExampleItem(desc: '垂直排列:', builder: _buildVerticalLayout), + ExampleItem(desc: '网格排列:', builder: _buildGridLayout), + ]), + ExampleModule(title: '自定义样式', children: [ + ExampleItem(desc: '自定义颜色和透明度:', builder: _buildCustomColor), + ExampleItem(desc: '自定义字体大小:', builder: _buildCustomSize), + ExampleItem(desc: '自定义旋转角度:', builder: _buildCustomRotate), + ExampleItem(desc: '自定义间距:', builder: _buildCustomGap), + ]), + ExampleModule(title: '带内容的水印', children: [ + ExampleItem(desc: '图片上的水印:', builder: _buildImageWatermark), + ExampleItem(desc: '列表上的水印:', builder: _buildListWatermark), + ExampleItem(desc: '表单上的水印:', builder: _buildFormWatermark), + ]), + ], + ); + } + + @Demo(group: 'watermark') + Widget _buildSingleLine(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: 'TDesign Flutter', + type: TWatermarkType.singleLine, + ), + ); + } + + @Demo(group: 'watermark') + Widget _buildMultiLine(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: 'TDesign\nFlutter', + type: TWatermarkType.multiLine, + ), + ); + } + + @Demo(group: 'watermark') + Widget _buildHorizontalLayout(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '内部资料', + layout: TWatermarkLayout.horizontal, + gapX: 150, + ), + ); + } + + @Demo(group: 'watermark') + Widget _buildVerticalLayout(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '机密', + layout: TWatermarkLayout.vertical, + gapY: 80, + ), + ); + } + + @Demo(group: 'watermark') + Widget _buildGridLayout(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: 'TDesign', + layout: TWatermarkLayout.grid, + gapX: 120, + gapY: 80, + ), + ); + } + + @Demo(group: 'watermark') + Widget _buildCustomColor(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '保密文档', + textColor: TTheme.of(context).errorNormalColor, + opacity: 0.2, + ), + ); + } + + @Demo(group: 'watermark') + Widget _buildCustomSize(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '大字体水印', + textSize: 24, + fontWeight: FontWeight.bold, + ), + ); + } + + @Demo(group: 'watermark') + Widget _buildCustomRotate(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '旋转45度', + rotate: -45, + gapX: 150, + gapY: 100, + ), + ); + } + + @Demo(group: 'watermark') + Widget _buildCustomGap(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '密集', + gapX: 60, + gapY: 40, + textSize: 12, + ), + ); + } + + @Demo(group: 'watermark') + Widget _buildImageWatermark(BuildContext context) { + return Container( + height: 300, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '仅供查看', + child: Center( + child: Container( + width: 200, + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).brandFocusColor, + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + TIcons.image, + size: 80, + color: TTheme.of(context).brandNormalColor, + ), + ), + ), + ), + ); + } + + @Demo(group: 'watermark') + Widget _buildListWatermark(BuildContext context) { + return Container( + height: 300, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '内部数据', + opacity: 0.1, + child: ListView.builder( + itemCount: 10, + itemBuilder: (context, index) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: TTheme.of(context).componentStrokeColor, + width: 0.5, + ), + ), + ), + child: Row( + children: [ + CircleAvatar( + backgroundColor: TTheme.of(context).brandLightColor, + child: Text('${index + 1}'), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TText( + '列表项 ${index + 1}', + font: TTheme.of(context).fontBodyMedium, + ), + const SizedBox(height: 4), + TText( + '这是第 ${index + 1} 条数据的描述信息', + font: TTheme.of(context).fontBodySmall, + textColor: TTheme.of(context).textColorSecondary, + ), + ], + ), + ), + ], + ), + ); + }, + ), + ), + ); + } + + @Demo(group: 'watermark') + Widget _buildFormWatermark(BuildContext context) { + return Container( + height: 350, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '草稿', + opacity: 0.08, + textSize: 48, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + TInput( + leftLabel: '姓名', + hintText: '请输入姓名', + ), + const SizedBox(height: 16), + TInput( + leftLabel: '邮箱', + hintText: '请输入邮箱', + ), + const SizedBox(height: 16), + TInput( + leftLabel: '电话', + hintText: '请输入电话号码', + ), + const SizedBox(height: 16), + TButton( + text: '提交', + theme: TButtonTheme.primary, + onTap: () {}, + ), + ], + ), + ), + ), + ); + } +} diff --git a/tdesign-component/lib/src/components/watermark/t_watermark.dart b/tdesign-component/lib/src/components/watermark/t_watermark.dart new file mode 100644 index 000000000..ccd794109 --- /dev/null +++ b/tdesign-component/lib/src/components/watermark/t_watermark.dart @@ -0,0 +1,307 @@ +import 'package:flutter/material.dart'; +import '../../../tdesign_flutter.dart'; + +/// 水印类型 +enum TWatermarkType { + /// 单行文本 + singleLine, + + /// 多行文本 + multiLine, +} + +/// 水印排列方式 +enum TWatermarkLayout { + /// 水平排列 + horizontal, + + /// 垂直排列 + vertical, + + /// 网格排列 + grid, +} + +/// 水印组件 +class TWatermark extends StatefulWidget { + const TWatermark({ + Key? key, + required this.text, + this.type = TWatermarkType.multiLine, + this.layout = TWatermarkLayout.grid, + this.textColor, + this.textSize = 14, + this.fontWeight = FontWeight.normal, + this.opacity = 0.15, + this.rotate = -20, + this.gapX = 100, + this.gapY = 100, + this.offsetX = 0, + this.offsetY = 0, + this.zIndex = 1, + this.width, + this.height, + this.child, + }) : super(key: key); + + /// 水印文本内容 + final String text; + + /// 水印类型 + final TWatermarkType type; + + /// 水印排列方式 + final TWatermarkLayout layout; + + /// 水印文字颜色 + final Color? textColor; + + /// 水印文字大小 + final double textSize; + + /// 水印文字粗细 + final FontWeight fontWeight; + + /// 水印透明度 (0.0 - 1.0) + final double opacity; + + /// 水印旋转角度(度) + final double rotate; + + /// 水平间距 + final double gapX; + + /// 垂直间距 + final double gapY; + + /// 水平偏移量 + final double offsetX; + + /// 垂直偏移量 + final double offsetY; + + /// z-index层级 + final int zIndex; + + /// 水印区域宽度 + final double? width; + + /// 水印区域高度 + final double? height; + + /// 子组件(水印将覆盖在此组件上方) + final Widget? child; + + @override + State createState() => _TWatermarkState(); +} + +class _TWatermarkState extends State { + @override + Widget build(BuildContext context) { + final theme = TTheme.of(context); + final textColor = widget.textColor ?? theme.textColorPlaceholder; + + return Stack( + children: [ + // 子组件 + if (widget.child != null) widget.child!, + + // 水印层 + Positioned.fill( + child: IgnorePointer( + ignoring: true, + child: CustomPaint( + painter: _WatermarkPainter( + text: widget.text, + type: widget.type, + layout: widget.layout, + textColor: textColor.withOpacity(widget.opacity), + textSize: widget.textSize, + fontWeight: widget.fontWeight, + rotate: widget.rotate, + gapX: widget.gapX, + gapY: widget.gapY, + offsetX: widget.offsetX, + offsetY: widget.offsetY, + ), + ), + ), + ), + ], + ); + } +} + +/// 水印绘制器 +class _WatermarkPainter extends CustomPainter { + final String text; + final TWatermarkType type; + final TWatermarkLayout layout; + final Color textColor; + final double textSize; + final FontWeight fontWeight; + final double rotate; + final double gapX; + final double gapY; + final double offsetX; + final double offsetY; + + _WatermarkPainter({ + required this.text, + required this.type, + required this.layout, + required this.textColor, + required this.textSize, + required this.fontWeight, + required this.rotate, + required this.gapX, + required this.gapY, + required this.offsetX, + required this.offsetY, + }); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = textColor + ..isAntiAlias = true; + + final textPainter = TextPainter( + text: TextSpan( + text: text, + style: TextStyle( + color: textColor, + fontSize: textSize, + fontWeight: fontWeight, + ), + ), + textDirection: TextDirection.ltr, + ); + + textPainter.layout(); + + final textWidth = textPainter.width; + final textHeight = textPainter.height; + + // 根据布局方式绘制水印 + switch (layout) { + case TWatermarkLayout.horizontal: + _drawHorizontal(canvas, textPainter, size, textWidth, textHeight); + break; + case TWatermarkLayout.vertical: + _drawVertical(canvas, textPainter, size, textWidth, textHeight); + break; + case TWatermarkLayout.grid: + _drawGrid(canvas, textPainter, size, textWidth, textHeight); + break; + } + } + + /// 水平排列 + void _drawHorizontal( + Canvas canvas, + TextPainter textPainter, + Size size, + double textWidth, + double textHeight, + ) { + final centerY = size.height / 2 + offsetY; + var currentX = offsetX; + + while (currentX < size.width) { + _drawTextWithRotation( + canvas, + textPainter, + Offset(currentX, centerY - textHeight / 2), + ); + currentX += textWidth + gapX; + } + } + + /// 垂直排列 + void _drawVertical( + Canvas canvas, + TextPainter textPainter, + Size size, + double textWidth, + double textHeight, + ) { + final centerX = size.width / 2 + offsetX; + var currentY = offsetY; + + while (currentY < size.height) { + _drawTextWithRotation( + canvas, + textPainter, + Offset(centerX - textWidth / 2, currentY), + ); + currentY += textHeight + gapY; + } + } + + /// 网格排列 + void _drawGrid( + Canvas canvas, + TextPainter textPainter, + Size size, + double textWidth, + double textHeight, + ) { + var currentY = -textHeight + offsetY; + + while (currentY < size.height + textHeight) { + var currentX = -textWidth + offsetX; + + while (currentX < size.width + textWidth) { + _drawTextWithRotation( + canvas, + textPainter, + Offset(currentX, currentY), + ); + currentX += textWidth + gapX; + } + currentY += textHeight + gapY; + } + } + + /// 绘制带旋转的文本 + void _drawTextWithRotation( + Canvas canvas, + TextPainter textPainter, + Offset position, + ) { + canvas.save(); + + // 移动到文本中心点 + final center = Offset( + position.dx + textPainter.width / 2, + position.dy + textPainter.height / 2, + ); + + canvas.translate(center.dx, center.dy); + // 旋转 + canvas.rotate(rotate * 3.1415926535897932 / 180); + canvas.translate(-center.dx, -center.dy); + + // 绘制文本 + textPainter.paint(canvas, position); + + canvas.restore(); + } + + @override + bool shouldRepaint(covariant _WatermarkPainter oldDelegate) { + return oldDelegate.text != text || + oldDelegate.type != type || + oldDelegate.layout != layout || + oldDelegate.textColor != textColor || + oldDelegate.textSize != textSize || + oldDelegate.fontWeight != fontWeight || + oldDelegate.rotate != rotate || + oldDelegate.gapX != gapX || + oldDelegate.gapY != gapY || + oldDelegate.offsetX != offsetX || + oldDelegate.offsetY != offsetY; + } +} diff --git a/tdesign-component/lib/tdesign_flutter.dart b/tdesign-component/lib/tdesign_flutter.dart index 675a6500d..cf1cc5bb0 100644 --- a/tdesign-component/lib/tdesign_flutter.dart +++ b/tdesign-component/lib/tdesign_flutter.dart @@ -94,6 +94,7 @@ export 'src/components/time_counter/t_time_counter_style.dart'; export 'src/components/toast/t_toast.dart'; export 'src/components/tree/t_tree_select.dart'; export 'src/components/upload/t_upload.dart'; +export 'src/components/watermark/t_watermark.dart'; export 'src/theme/basic.dart'; export 'src/theme/resource_delegate.dart'; export 'src/theme/t_colors.dart'; diff --git a/tdesign-component/test/t_watermark_test.dart b/tdesign-component/test/t_watermark_test.dart new file mode 100644 index 000000000..87f3ad9b5 --- /dev/null +++ b/tdesign-component/test/t_watermark_test.dart @@ -0,0 +1,166 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tdesign_flutter/tdesign_flutter.dart'; + +void main() { + group('TWatermark', () { + testWidgets('水印组件基本渲染测试', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: TWatermark( + text: '测试水印', + ), + ), + ), + ); + + expect(find.byType(TWatermark), findsOneWidget); + }); + + testWidgets('水印组件带子组件测试', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: TWatermark( + text: '测试水印', + child: const Text('子组件内容'), + ), + ), + ), + ); + + expect(find.byType(TWatermark), findsOneWidget); + expect(find.text('子组件内容'), findsOneWidget); + }); + + testWidgets('水印组件单行类型测试', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: TWatermark( + text: '单行水印', + type: TWatermarkType.singleLine, + ), + ), + ), + ); + + expect(find.byType(TWatermark), findsOneWidget); + }); + + testWidgets('水印组件多行类型测试', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: TWatermark( + text: '多行\n水印', + type: TWatermarkType.multiLine, + ), + ), + ), + ); + + expect(find.byType(TWatermark), findsOneWidget); + }); + + testWidgets('水印组件水平布局测试', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SizedBox( + height: 200, + child: TWatermark( + text: '水平', + layout: TWatermarkLayout.horizontal, + ), + ), + ), + ), + ); + + expect(find.byType(TWatermark), findsOneWidget); + }); + + testWidgets('水印组件垂直布局测试', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SizedBox( + height: 200, + child: TWatermark( + text: '垂直', + layout: TWatermarkLayout.vertical, + ), + ), + ), + ), + ); + + expect(find.byType(TWatermark), findsOneWidget); + }); + + testWidgets('水印组件网格布局测试', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: SizedBox( + height: 200, + child: TWatermark( + text: '网格', + layout: TWatermarkLayout.grid, + ), + ), + ), + ), + ); + + expect(find.byType(TWatermark), findsOneWidget); + }); + + testWidgets('水印组件自定义样式测试', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: TWatermark( + text: '自定义', + textColor: Colors.red, + textSize: 20, + opacity: 0.5, + rotate: -45, + gapX: 100, + gapY: 80, + ), + ), + ), + ); + + expect(find.byType(TWatermark), findsOneWidget); + }); + + testWidgets('水印组件忽略指针测试', (WidgetTester tester) async { + var buttonClicked = false; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: TWatermark( + text: '测试', + child: ElevatedButton( + onPressed: () { + buttonClicked = true; + }, + child: const Text('点击我'), + ), + ), + ), + ), + ); + + await tester.tap(find.text('点击我')); + await tester.pump(); + + expect(buttonClicked, true); + }); + }); +} From 06e18e937c48487b75575f1221de0fdb66eaf426 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 10:34:19 +0000 Subject: [PATCH 2/4] [autofix.ci] apply automated fixes --- .../code/watermark._buildCustomColor.txt | 15 + .../assets/code/watermark._buildCustomGap.txt | 16 + .../code/watermark._buildCustomRotate.txt | 16 + .../code/watermark._buildCustomSize.txt | 15 + .../code/watermark._buildFormWatermark.txt | 42 ++ .../code/watermark._buildGridLayout.txt | 16 + .../code/watermark._buildHorizontalLayout.txt | 15 + .../code/watermark._buildImageWatermark.txt | 28 ++ .../code/watermark._buildListWatermark.txt | 56 +++ .../assets/code/watermark._buildMultiLine.txt | 14 + .../code/watermark._buildSingleLine.txt | 14 + .../code/watermark._buildVerticalLayout.txt | 15 + tdesign-site/src/watermark/README.md | 390 ++++++++++++++++++ 13 files changed, 652 insertions(+) create mode 100644 tdesign-component/example/assets/code/watermark._buildCustomColor.txt create mode 100644 tdesign-component/example/assets/code/watermark._buildCustomGap.txt create mode 100644 tdesign-component/example/assets/code/watermark._buildCustomRotate.txt create mode 100644 tdesign-component/example/assets/code/watermark._buildCustomSize.txt create mode 100644 tdesign-component/example/assets/code/watermark._buildFormWatermark.txt create mode 100644 tdesign-component/example/assets/code/watermark._buildGridLayout.txt create mode 100644 tdesign-component/example/assets/code/watermark._buildHorizontalLayout.txt create mode 100644 tdesign-component/example/assets/code/watermark._buildImageWatermark.txt create mode 100644 tdesign-component/example/assets/code/watermark._buildListWatermark.txt create mode 100644 tdesign-component/example/assets/code/watermark._buildMultiLine.txt create mode 100644 tdesign-component/example/assets/code/watermark._buildSingleLine.txt create mode 100644 tdesign-component/example/assets/code/watermark._buildVerticalLayout.txt create mode 100644 tdesign-site/src/watermark/README.md diff --git a/tdesign-component/example/assets/code/watermark._buildCustomColor.txt b/tdesign-component/example/assets/code/watermark._buildCustomColor.txt new file mode 100644 index 000000000..4c5d21928 --- /dev/null +++ b/tdesign-component/example/assets/code/watermark._buildCustomColor.txt @@ -0,0 +1,15 @@ + + Widget _buildCustomColor(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '保密文档', + textColor: TTheme.of(context).errorNormalColor, + opacity: 0.2, + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/watermark._buildCustomGap.txt b/tdesign-component/example/assets/code/watermark._buildCustomGap.txt new file mode 100644 index 000000000..c691d99ab --- /dev/null +++ b/tdesign-component/example/assets/code/watermark._buildCustomGap.txt @@ -0,0 +1,16 @@ + + Widget _buildCustomGap(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '密集', + gapX: 60, + gapY: 40, + textSize: 12, + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/watermark._buildCustomRotate.txt b/tdesign-component/example/assets/code/watermark._buildCustomRotate.txt new file mode 100644 index 000000000..7495053be --- /dev/null +++ b/tdesign-component/example/assets/code/watermark._buildCustomRotate.txt @@ -0,0 +1,16 @@ + + Widget _buildCustomRotate(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '旋转45度', + rotate: -45, + gapX: 150, + gapY: 100, + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/watermark._buildCustomSize.txt b/tdesign-component/example/assets/code/watermark._buildCustomSize.txt new file mode 100644 index 000000000..ea90ae0b2 --- /dev/null +++ b/tdesign-component/example/assets/code/watermark._buildCustomSize.txt @@ -0,0 +1,15 @@ + + Widget _buildCustomSize(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '大字体水印', + textSize: 24, + fontWeight: FontWeight.bold, + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/watermark._buildFormWatermark.txt b/tdesign-component/example/assets/code/watermark._buildFormWatermark.txt new file mode 100644 index 000000000..671c6b878 --- /dev/null +++ b/tdesign-component/example/assets/code/watermark._buildFormWatermark.txt @@ -0,0 +1,42 @@ + + Widget _buildFormWatermark(BuildContext context) { + return Container( + height: 350, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '草稿', + opacity: 0.08, + textSize: 48, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + TInput( + leftLabel: '姓名', + hintText: '请输入姓名', + ), + const SizedBox(height: 16), + TInput( + leftLabel: '邮箱', + hintText: '请输入邮箱', + ), + const SizedBox(height: 16), + TInput( + leftLabel: '电话', + hintText: '请输入电话号码', + ), + const SizedBox(height: 16), + TButton( + text: '提交', + theme: TButtonTheme.primary, + onTap: () {}, + ), + ], + ), + ), + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/watermark._buildGridLayout.txt b/tdesign-component/example/assets/code/watermark._buildGridLayout.txt new file mode 100644 index 000000000..29d73f993 --- /dev/null +++ b/tdesign-component/example/assets/code/watermark._buildGridLayout.txt @@ -0,0 +1,16 @@ + + Widget _buildGridLayout(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: 'TDesign', + layout: TWatermarkLayout.grid, + gapX: 120, + gapY: 80, + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/watermark._buildHorizontalLayout.txt b/tdesign-component/example/assets/code/watermark._buildHorizontalLayout.txt new file mode 100644 index 000000000..3590882de --- /dev/null +++ b/tdesign-component/example/assets/code/watermark._buildHorizontalLayout.txt @@ -0,0 +1,15 @@ + + Widget _buildHorizontalLayout(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '内部资料', + layout: TWatermarkLayout.horizontal, + gapX: 150, + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/watermark._buildImageWatermark.txt b/tdesign-component/example/assets/code/watermark._buildImageWatermark.txt new file mode 100644 index 000000000..3bc7983b4 --- /dev/null +++ b/tdesign-component/example/assets/code/watermark._buildImageWatermark.txt @@ -0,0 +1,28 @@ + + Widget _buildImageWatermark(BuildContext context) { + return Container( + height: 300, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '仅供查看', + child: Center( + child: Container( + width: 200, + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).brandFocusColor, + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + TIcons.image, + size: 80, + color: TTheme.of(context).brandNormalColor, + ), + ), + ), + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/watermark._buildListWatermark.txt b/tdesign-component/example/assets/code/watermark._buildListWatermark.txt new file mode 100644 index 000000000..eb9bd3c01 --- /dev/null +++ b/tdesign-component/example/assets/code/watermark._buildListWatermark.txt @@ -0,0 +1,56 @@ + + Widget _buildListWatermark(BuildContext context) { + return Container( + height: 300, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '内部数据', + opacity: 0.1, + child: ListView.builder( + itemCount: 10, + itemBuilder: (context, index) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: TTheme.of(context).componentStrokeColor, + width: 0.5, + ), + ), + ), + child: Row( + children: [ + CircleAvatar( + backgroundColor: TTheme.of(context).brandLightColor, + child: Text('${index + 1}'), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TText( + '列表项 ${index + 1}', + font: TTheme.of(context).fontBodyMedium, + ), + const SizedBox(height: 4), + TText( + '这是第 ${index + 1} 条数据的描述信息', + font: TTheme.of(context).fontBodySmall, + textColor: TTheme.of(context).textColorSecondary, + ), + ], + ), + ), + ], + ), + ); + }, + ), + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/watermark._buildMultiLine.txt b/tdesign-component/example/assets/code/watermark._buildMultiLine.txt new file mode 100644 index 000000000..3cb8b6418 --- /dev/null +++ b/tdesign-component/example/assets/code/watermark._buildMultiLine.txt @@ -0,0 +1,14 @@ + + Widget _buildMultiLine(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: 'TDesign\nFlutter', + type: TWatermarkType.multiLine, + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/watermark._buildSingleLine.txt b/tdesign-component/example/assets/code/watermark._buildSingleLine.txt new file mode 100644 index 000000000..8f35774f9 --- /dev/null +++ b/tdesign-component/example/assets/code/watermark._buildSingleLine.txt @@ -0,0 +1,14 @@ + + Widget _buildSingleLine(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: 'TDesign Flutter', + type: TWatermarkType.singleLine, + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/watermark._buildVerticalLayout.txt b/tdesign-component/example/assets/code/watermark._buildVerticalLayout.txt new file mode 100644 index 000000000..12b8962ad --- /dev/null +++ b/tdesign-component/example/assets/code/watermark._buildVerticalLayout.txt @@ -0,0 +1,15 @@ + + Widget _buildVerticalLayout(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: TWatermark( + text: '机密', + layout: TWatermarkLayout.vertical, + gapY: 80, + ), + ); + } \ No newline at end of file diff --git a/tdesign-site/src/watermark/README.md b/tdesign-site/src/watermark/README.md new file mode 100644 index 000000000..be16828f3 --- /dev/null +++ b/tdesign-site/src/watermark/README.md @@ -0,0 +1,390 @@ +--- +title: Watermark 水印 +description: +spline: base +isComponent: true +--- + + +## 引入 + +在tdesign_flutter/tdesign_flutter.dart中有所有组件的路径。 + +```dart +import 'package:tdesign_flutter/tdesign_flutter.dart'; +``` + +## 代码演示 + +[td_watermark_page.dart](https://github.com/Tencent/tdesign-flutter/blob/main/tdesign-component/example/lib/page/td_watermark_page.dart) + +### 1 基础用法 + +单行文本水印: + + + +
+  Widget _buildSingleLine(BuildContext context) {
+    return Container(
+      height: 200,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: 'TDesign Flutter',
+        type: TWatermarkType.singleLine,
+      ),
+    );
+  }
+ +
+ + +多行文本水印: + + + +
+  Widget _buildMultiLine(BuildContext context) {
+    return Container(
+      height: 200,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: 'TDesign\nFlutter',
+        type: TWatermarkType.multiLine,
+      ),
+    );
+  }
+ +
+ +### 1 排列方式 + +水平排列: + + + +
+  Widget _buildHorizontalLayout(BuildContext context) {
+    return Container(
+      height: 200,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: '内部资料',
+        layout: TWatermarkLayout.horizontal,
+        gapX: 150,
+      ),
+    );
+  }
+ +
+ + +垂直排列: + + + +
+  Widget _buildVerticalLayout(BuildContext context) {
+    return Container(
+      height: 200,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: '机密',
+        layout: TWatermarkLayout.vertical,
+        gapY: 80,
+      ),
+    );
+  }
+ +
+ + +网格排列: + + + +
+  Widget _buildGridLayout(BuildContext context) {
+    return Container(
+      height: 200,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: 'TDesign',
+        layout: TWatermarkLayout.grid,
+        gapX: 120,
+        gapY: 80,
+      ),
+    );
+  }
+ +
+ +### 1 自定义样式 + +自定义颜色和透明度: + + + +
+  Widget _buildCustomColor(BuildContext context) {
+    return Container(
+      height: 200,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: '保密文档',
+        textColor: TTheme.of(context).errorNormalColor,
+        opacity: 0.2,
+      ),
+    );
+  }
+ +
+ + +自定义字体大小: + + + +
+  Widget _buildCustomSize(BuildContext context) {
+    return Container(
+      height: 200,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: '大字体水印',
+        textSize: 24,
+        fontWeight: FontWeight.bold,
+      ),
+    );
+  }
+ +
+ + +自定义旋转角度: + + + +
+  Widget _buildCustomRotate(BuildContext context) {
+    return Container(
+      height: 200,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: '旋转45度',
+        rotate: -45,
+        gapX: 150,
+        gapY: 100,
+      ),
+    );
+  }
+ +
+ + +自定义间距: + + + +
+  Widget _buildCustomGap(BuildContext context) {
+    return Container(
+      height: 200,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: '密集',
+        gapX: 60,
+        gapY: 40,
+        textSize: 12,
+      ),
+    );
+  }
+ +
+ +### 1 带内容的水印 + +图片上的水印: + + + +
+  Widget _buildImageWatermark(BuildContext context) {
+    return Container(
+      height: 300,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: '仅供查看',
+        child: Center(
+          child: Container(
+            width: 200,
+            height: 200,
+            decoration: BoxDecoration(
+              color: TTheme.of(context).brandFocusColor,
+              borderRadius: BorderRadius.circular(8),
+            ),
+            child: Icon(
+              TIcons.image,
+              size: 80,
+              color: TTheme.of(context).brandNormalColor,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+ +
+ + +列表上的水印: + + + +
+  Widget _buildListWatermark(BuildContext context) {
+    return Container(
+      height: 300,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: '内部数据',
+        opacity: 0.1,
+        child: ListView.builder(
+          itemCount: 10,
+          itemBuilder: (context, index) {
+            return Container(
+              padding: const EdgeInsets.all(16),
+              decoration: BoxDecoration(
+                border: Border(
+                  bottom: BorderSide(
+                    color: TTheme.of(context).componentStrokeColor,
+                    width: 0.5,
+                  ),
+                ),
+              ),
+              child: Row(
+                children: [
+                  CircleAvatar(
+                    backgroundColor: TTheme.of(context).brandLightColor,
+                    child: Text('${index + 1}'),
+                  ),
+                  const SizedBox(width: 12),
+                  Expanded(
+                    child: Column(
+                      crossAxisAlignment: CrossAxisAlignment.start,
+                      children: [
+                        TText(
+                          '列表项 ${index + 1}',
+                          font: TTheme.of(context).fontBodyMedium,
+                        ),
+                        const SizedBox(height: 4),
+                        TText(
+                          '这是第 ${index + 1} 条数据的描述信息',
+                          font: TTheme.of(context).fontBodySmall,
+                          textColor: TTheme.of(context).textColorSecondary,
+                        ),
+                      ],
+                    ),
+                  ),
+                ],
+              ),
+            );
+          },
+        ),
+      ),
+    );
+  }
+ +
+ + +表单上的水印: + + + +
+  Widget _buildFormWatermark(BuildContext context) {
+    return Container(
+      height: 350,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: '草稿',
+        opacity: 0.08,
+        textSize: 48,
+        child: Padding(
+          padding: const EdgeInsets.all(16),
+          child: Column(
+            children: [
+              TInput(
+                leftLabel: '姓名',
+                hintText: '请输入姓名',
+              ),
+              const SizedBox(height: 16),
+              TInput(
+                leftLabel: '邮箱',
+                hintText: '请输入邮箱',
+              ),
+              const SizedBox(height: 16),
+              TInput(
+                leftLabel: '电话',
+                hintText: '请输入电话号码',
+              ),
+              const SizedBox(height: 16),
+              TButton(
+                text: '提交',
+                theme: TButtonTheme.primary,
+                onTap: () {},
+              ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+ +
+ + + +## API + +暂无对应api + + + \ No newline at end of file From 7393f16f463461ef016b83fe03a4bbfb1c45f8fa Mon Sep 17 00:00:00 2001 From: engineer Date: Tue, 14 Apr 2026 21:33:59 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat(watermark):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=B0=B4=E5=8D=B0=E7=BB=84=E4=BB=B6=EF=BC=8C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=B0=B4=E5=8D=B0=E7=BB=84=E4=BB=B6=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/watermark/t_watermark.dart | 23 ++- tdesign-site/site/docs/getting-started.md | 1 + tdesign-site/site/site.config.mjs | 7 + tdesign-site/src/watermark/README.md | 193 ++++++++++++++++++ 4 files changed, 221 insertions(+), 3 deletions(-) create mode 100644 tdesign-site/src/watermark/README.md diff --git a/tdesign-component/lib/src/components/watermark/t_watermark.dart b/tdesign-component/lib/src/components/watermark/t_watermark.dart index ccd794109..ee804ad57 100644 --- a/tdesign-component/lib/src/components/watermark/t_watermark.dart +++ b/tdesign-component/lib/src/components/watermark/t_watermark.dart @@ -167,6 +167,9 @@ class _WatermarkPainter extends CustomPainter { ..color = textColor ..isAntiAlias = true; + // 裁剪到容器边界,确保水印不超出容器 + canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); + final textPainter = TextPainter( text: TextSpan( text: text, @@ -209,6 +212,11 @@ class _WatermarkPainter extends CustomPainter { final centerY = size.height / 2 + offsetY; var currentX = offsetX; + // 向左延伸绘制,确保旋转后能覆盖左边缘 + while (currentX > -textWidth) { + currentX -= textWidth + gapX; + } + while (currentX < size.width) { _drawTextWithRotation( canvas, @@ -230,6 +238,11 @@ class _WatermarkPainter extends CustomPainter { final centerX = size.width / 2 + offsetX; var currentY = offsetY; + // 向上延伸绘制,确保旋转后能覆盖上边缘 + while (currentY > -textHeight) { + currentY -= textHeight + gapY; + } + while (currentY < size.height) { _drawTextWithRotation( canvas, @@ -248,11 +261,15 @@ class _WatermarkPainter extends CustomPainter { double textWidth, double textHeight, ) { - var currentY = -textHeight + offsetY; + // 从原点开始绘制,配合clipRect确保水印在容器内 + var startY = offsetY; + var startX = offsetX; + // 绘制网格,向右下扩展到容器边界 + // 扩展一些以确保旋转后的水印也能覆盖边缘 + var currentY = startY; while (currentY < size.height + textHeight) { - var currentX = -textWidth + offsetX; - + var currentX = startX; while (currentX < size.width + textWidth) { _drawTextWithRotation( canvas, diff --git a/tdesign-site/site/docs/getting-started.md b/tdesign-site/site/docs/getting-started.md index 53de7e554..563519751 100644 --- a/tdesign-site/site/docs/getting-started.md +++ b/tdesign-site/site/docs/getting-started.md @@ -19,6 +19,7 @@

+[English](./README.md) | 简体中文 **TDesign Flutter** 是基于腾讯设计体系的跨平台 UI 组件库,使用 Flutter 框架开发,可快速构建美观、一致的移动端/Web 应用,提供丰富的预制组件和主题定制能力,支持 iOS、Android、Web 多端运行。 diff --git a/tdesign-site/site/site.config.mjs b/tdesign-site/site/site.config.mjs index 75d15c202..6bb1b3b29 100644 --- a/tdesign-site/site/site.config.mjs +++ b/tdesign-site/site/site.config.mjs @@ -426,6 +426,13 @@ export default { path: '/flutter/components/tag', component: () => import('@/tag/README.md'), }, + { + title: 'Watermark 水印', + name: 'watermark', + meta: { docType: 'data' }, + path: '/flutter/components/watermark', + component: () => import('@/watermark/README.md'), + }, ], }, { diff --git a/tdesign-site/src/watermark/README.md b/tdesign-site/src/watermark/README.md new file mode 100644 index 000000000..fd27323fa --- /dev/null +++ b/tdesign-site/src/watermark/README.md @@ -0,0 +1,193 @@ +--- +title: Watermark 水印 +description: 给页面的某个区域加上水印,常用于防止信息截图泄露。 +spline: base +isComponent: true +--- + +## 引入 + +在tdesign_flutter/tdesign_flutter.dart中有所有组件的路径。 + +```dart +import 'package:tdesign_flutter/tdesign_flutter.dart'; +``` + +## 代码演示 + +### 基础水印 + +默认使用多行文本和网格排列的水印。 + + + + +
+  Widget _buildBasicWatermark() {
+    return const TWatermark(
+      text: 'TDesign Watermark',
+    );
+  }
+ +
+ + +### 单行水印 + +使用单行文本类型的水印。 + + + + +
+  Widget _buildSingleLineWatermark() {
+    return const TWatermark(
+      text: 'TDesign Watermark',
+      type: TWatermarkType.singleLine,
+    );
+  }
+ +
+ + +### 自定义样式 + +自定义水印的颜色、大小、透明度和旋转角度。 + + + + +
+  Widget _buildCustomStyleWatermark(BuildContext context) {
+    return TWatermark(
+      text: '机密信息',
+      textColor: Colors.red,
+      textSize: 16,
+      opacity: 0.2,
+      rotate: -30,
+      fontWeight: FontWeight.bold,
+    );
+  }
+ +
+ + +### 自定义间距 + +调整水印之间的水平和垂直间距。 + + + + +
+  Widget _buildCustomGapWatermark() {
+    return const TWatermark(
+      text: 'TDesign Watermark',
+      gapX: 150,
+      gapY: 150,
+    );
+  }
+ +
+ + +### 带内容的水印 + +在子组件上方添加水印。 + + + + +
+  Widget _buildWatermarkWithContent(BuildContext context) {
+    return TWatermark(
+      text: '内部资料',
+      child: Container(
+        padding: const EdgeInsets.all(16),
+        color: TTheme.of(context).bgColorContainer,
+        child: Column(
+          children: [
+            Text('这是一段重要内容', style: TTheme.of(context).fontTitleLarge),
+            const SizedBox(height: 8),
+            Text('水印会覆盖在这段内容上方', style: TTheme.of(context).fontBodyMedium),
+          ],
+        ),
+      ),
+    );
+  }
+ +
+ + +### 不同排列方式 + +使用水平、垂直和网格排列。 + + + + +
+  Widget _buildHorizontalWatermark() {
+    return const TWatermark(
+      text: 'TDesign Watermark',
+      layout: TWatermarkLayout.horizontal,
+    );
+  }
+
+  Widget _buildVerticalWatermark() {
+    return const TWatermark(
+      text: 'TDesign Watermark',
+      layout: TWatermarkLayout.vertical,
+    );
+  }
+
+  Widget _buildGridWatermark() {
+    return const TWatermark(
+      text: 'TDesign Watermark',
+      layout: TWatermarkLayout.grid,
+    );
+  }
+ +
+ + +## API +### TWatermark +#### 默认构造方法 + +| 参数 | 类型 | 默认值 | 说明 | +| --- | --- | --- | --- | +| text | String | required | 水印文本内容 | +| type | TWatermarkType | TWatermarkType.multiLine | 水印类型(单行/多行) | +| layout | TWatermarkLayout | TWatermarkLayout.grid | 水印排列方式(水平/垂直/网格) | +| textColor | Color? | theme.textColorPlaceholder | 水印文字颜色 | +| textSize | double | 14 | 水印文字大小 | +| fontWeight | FontWeight | FontWeight.normal | 水印文字粗细 | +| opacity | double | 0.15 | 水印透明度 (0.0 - 1.0) | +| rotate | double | -20 | 水印旋转角度(度) | +| gapX | double | 100 | 水平间距 | +| gapY | double | 100 | 垂直间距 | +| offsetX | double | 0 | 水平偏移量 | +| offsetY | double | 0 | 垂直偏移量 | +| zIndex | int | 1 | z-index层级 | +| width | double? | null | 水印区域宽度 | +| height | double? | null | 水印区域高度 | +| child | Widget? | null | 子组件(水印将覆盖在此组件上方) | + +### TWatermarkType +水印类型枚举 + +| 值 | 说明 | +| --- | --- | +| singleLine | 单行文本 | +| multiLine | 多行文本 | + +### TWatermarkLayout +水印排列方式枚举 + +| 值 | 说明 | +| --- | --- | +| horizontal | 水平排列 | +| vertical | 垂直排列 | +| grid | 网格排列 | + From c626d5886d44ba015e1c4c8dcc69103ecd4d50cb Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 13:44:36 +0000 Subject: [PATCH 4/4] [autofix.ci] apply automated fixes --- tdesign-site/site/docs/getting-started.md | 1 - tdesign-site/src/watermark/README.md | 417 ++++++++++++++++------ 2 files changed, 307 insertions(+), 111 deletions(-) diff --git a/tdesign-site/site/docs/getting-started.md b/tdesign-site/site/docs/getting-started.md index 563519751..53de7e554 100644 --- a/tdesign-site/site/docs/getting-started.md +++ b/tdesign-site/site/docs/getting-started.md @@ -19,7 +19,6 @@

-[English](./README.md) | 简体中文 **TDesign Flutter** 是基于腾讯设计体系的跨平台 UI 组件库,使用 Flutter 框架开发,可快速构建美观、一致的移动端/Web 应用,提供丰富的预制组件和主题定制能力,支持 iOS、Android、Web 多端运行。 diff --git a/tdesign-site/src/watermark/README.md b/tdesign-site/src/watermark/README.md index fd27323fa..be16828f3 100644 --- a/tdesign-site/src/watermark/README.md +++ b/tdesign-site/src/watermark/README.md @@ -1,10 +1,11 @@ --- title: Watermark 水印 -description: 给页面的某个区域加上水印,常用于防止信息截图泄露。 +description: spline: base isComponent: true --- + ## 引入 在tdesign_flutter/tdesign_flutter.dart中有所有组件的路径。 @@ -15,179 +16,375 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; ## 代码演示 -### 基础水印 +[td_watermark_page.dart](https://github.com/Tencent/tdesign-flutter/blob/main/tdesign-component/example/lib/page/td_watermark_page.dart) -默认使用多行文本和网格排列的水印。 +### 1 基础用法 - +单行文本水印: +
-  Widget _buildBasicWatermark() {
-    return const TWatermark(
-      text: 'TDesign Watermark',
+  Widget _buildSingleLine(BuildContext context) {
+    return Container(
+      height: 200,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: 'TDesign Flutter',
+        type: TWatermarkType.singleLine,
+      ),
     );
   }
- + + +多行文本水印: + + -### 单行水印 +
+  Widget _buildMultiLine(BuildContext context) {
+    return Container(
+      height: 200,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: 'TDesign\nFlutter',
+        type: TWatermarkType.multiLine,
+      ),
+    );
+  }
-使用单行文本类型的水印。 +
+ +### 1 排列方式 - +水平排列: +
-  Widget _buildSingleLineWatermark() {
-    return const TWatermark(
-      text: 'TDesign Watermark',
-      type: TWatermarkType.singleLine,
+  Widget _buildHorizontalLayout(BuildContext context) {
+    return Container(
+      height: 200,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: '内部资料',
+        layout: TWatermarkLayout.horizontal,
+        gapX: 150,
+      ),
     );
   }
- + -### 自定义样式 +垂直排列: + + -自定义水印的颜色、大小、透明度和旋转角度。 +
+  Widget _buildVerticalLayout(BuildContext context) {
+    return Container(
+      height: 200,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: '机密',
+        layout: TWatermarkLayout.vertical,
+        gapY: 80,
+      ),
+    );
+  }
- +
+ + +网格排列: +
-  Widget _buildCustomStyleWatermark(BuildContext context) {
-    return TWatermark(
-      text: '机密信息',
-      textColor: Colors.red,
-      textSize: 16,
-      opacity: 0.2,
-      rotate: -30,
-      fontWeight: FontWeight.bold,
+  Widget _buildGridLayout(BuildContext context) {
+    return Container(
+      height: 200,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: 'TDesign',
+        layout: TWatermarkLayout.grid,
+        gapX: 120,
+        gapY: 80,
+      ),
     );
   }
- + +### 1 自定义样式 -### 自定义间距 +自定义颜色和透明度: + + -调整水印之间的水平和垂直间距。 +
+  Widget _buildCustomColor(BuildContext context) {
+    return Container(
+      height: 200,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: '保密文档',
+        textColor: TTheme.of(context).errorNormalColor,
+        opacity: 0.2,
+      ),
+    );
+  }
- +
+ + +自定义字体大小: +
-  Widget _buildCustomGapWatermark() {
-    return const TWatermark(
-      text: 'TDesign Watermark',
-      gapX: 150,
-      gapY: 150,
+  Widget _buildCustomSize(BuildContext context) {
+    return Container(
+      height: 200,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: '大字体水印',
+        textSize: 24,
+        fontWeight: FontWeight.bold,
+      ),
     );
   }
- + -### 带内容的水印 +自定义旋转角度: + + + +
+  Widget _buildCustomRotate(BuildContext context) {
+    return Container(
+      height: 200,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: '旋转45度',
+        rotate: -45,
+        gapX: 150,
+        gapY: 100,
+      ),
+    );
+  }
-在子组件上方添加水印。 +
+ - +自定义间距: +
-  Widget _buildWatermarkWithContent(BuildContext context) {
-    return TWatermark(
-      text: '内部资料',
-      child: Container(
-        padding: const EdgeInsets.all(16),
+  Widget _buildCustomGap(BuildContext context) {
+    return Container(
+      height: 200,
+      decoration: BoxDecoration(
         color: TTheme.of(context).bgColorContainer,
-        child: Column(
-          children: [
-            Text('这是一段重要内容', style: TTheme.of(context).fontTitleLarge),
-            const SizedBox(height: 8),
-            Text('水印会覆盖在这段内容上方', style: TTheme.of(context).fontBodyMedium),
-          ],
-        ),
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: '密集',
+        gapX: 60,
+        gapY: 40,
+        textSize: 12,
       ),
     );
   }
- + +### 1 带内容的水印 + +图片上的水印: + + -### 不同排列方式 +
+  Widget _buildImageWatermark(BuildContext context) {
+    return Container(
+      height: 300,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: '仅供查看',
+        child: Center(
+          child: Container(
+            width: 200,
+            height: 200,
+            decoration: BoxDecoration(
+              color: TTheme.of(context).brandFocusColor,
+              borderRadius: BorderRadius.circular(8),
+            ),
+            child: Icon(
+              TIcons.image,
+              size: 80,
+              color: TTheme.of(context).brandNormalColor,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
-使用水平、垂直和网格排列。 +
+ - +列表上的水印: +
-  Widget _buildHorizontalWatermark() {
-    return const TWatermark(
-      text: 'TDesign Watermark',
-      layout: TWatermarkLayout.horizontal,
+  Widget _buildListWatermark(BuildContext context) {
+    return Container(
+      height: 300,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: '内部数据',
+        opacity: 0.1,
+        child: ListView.builder(
+          itemCount: 10,
+          itemBuilder: (context, index) {
+            return Container(
+              padding: const EdgeInsets.all(16),
+              decoration: BoxDecoration(
+                border: Border(
+                  bottom: BorderSide(
+                    color: TTheme.of(context).componentStrokeColor,
+                    width: 0.5,
+                  ),
+                ),
+              ),
+              child: Row(
+                children: [
+                  CircleAvatar(
+                    backgroundColor: TTheme.of(context).brandLightColor,
+                    child: Text('${index + 1}'),
+                  ),
+                  const SizedBox(width: 12),
+                  Expanded(
+                    child: Column(
+                      crossAxisAlignment: CrossAxisAlignment.start,
+                      children: [
+                        TText(
+                          '列表项 ${index + 1}',
+                          font: TTheme.of(context).fontBodyMedium,
+                        ),
+                        const SizedBox(height: 4),
+                        TText(
+                          '这是第 ${index + 1} 条数据的描述信息',
+                          font: TTheme.of(context).fontBodySmall,
+                          textColor: TTheme.of(context).textColorSecondary,
+                        ),
+                      ],
+                    ),
+                  ),
+                ],
+              ),
+            );
+          },
+        ),
+      ),
     );
-  }
+  }
- Widget _buildVerticalWatermark() { - return const TWatermark( - text: 'TDesign Watermark', - layout: TWatermarkLayout.vertical, - ); - } +
+ - Widget _buildGridWatermark() { - return const TWatermark( - text: 'TDesign Watermark', - layout: TWatermarkLayout.grid, +表单上的水印: + + + +
+  Widget _buildFormWatermark(BuildContext context) {
+    return Container(
+      height: 350,
+      decoration: BoxDecoration(
+        color: TTheme.of(context).bgColorContainer,
+        borderRadius: BorderRadius.circular(8),
+      ),
+      child: TWatermark(
+        text: '草稿',
+        opacity: 0.08,
+        textSize: 48,
+        child: Padding(
+          padding: const EdgeInsets.all(16),
+          child: Column(
+            children: [
+              TInput(
+                leftLabel: '姓名',
+                hintText: '请输入姓名',
+              ),
+              const SizedBox(height: 16),
+              TInput(
+                leftLabel: '邮箱',
+                hintText: '请输入邮箱',
+              ),
+              const SizedBox(height: 16),
+              TInput(
+                leftLabel: '电话',
+                hintText: '请输入电话号码',
+              ),
+              const SizedBox(height: 16),
+              TButton(
+                text: '提交',
+                theme: TButtonTheme.primary,
+                onTap: () {},
+              ),
+            ],
+          ),
+        ),
+      ),
     );
   }
- + + ## API -### TWatermark -#### 默认构造方法 - -| 参数 | 类型 | 默认值 | 说明 | -| --- | --- | --- | --- | -| text | String | required | 水印文本内容 | -| type | TWatermarkType | TWatermarkType.multiLine | 水印类型(单行/多行) | -| layout | TWatermarkLayout | TWatermarkLayout.grid | 水印排列方式(水平/垂直/网格) | -| textColor | Color? | theme.textColorPlaceholder | 水印文字颜色 | -| textSize | double | 14 | 水印文字大小 | -| fontWeight | FontWeight | FontWeight.normal | 水印文字粗细 | -| opacity | double | 0.15 | 水印透明度 (0.0 - 1.0) | -| rotate | double | -20 | 水印旋转角度(度) | -| gapX | double | 100 | 水平间距 | -| gapY | double | 100 | 垂直间距 | -| offsetX | double | 0 | 水平偏移量 | -| offsetY | double | 0 | 垂直偏移量 | -| zIndex | int | 1 | z-index层级 | -| width | double? | null | 水印区域宽度 | -| height | double? | null | 水印区域高度 | -| child | Widget? | null | 子组件(水印将覆盖在此组件上方) | - -### TWatermarkType -水印类型枚举 - -| 值 | 说明 | -| --- | --- | -| singleLine | 单行文本 | -| multiLine | 多行文本 | - -### TWatermarkLayout -水印排列方式枚举 - -| 值 | 说明 | -| --- | --- | -| horizontal | 水平排列 | -| vertical | 垂直排列 | -| grid | 网格排列 | +暂无对应api + + + \ No newline at end of file