pdf_export_print 0.1.0 copy "pdf_export_print: ^0.1.0" to clipboard
pdf_export_print: ^0.1.0 copied to clipboard

A powerful and highly customizable Flutter PDF generation library with modular design.

example/lib/main.dart

// ignore_for_file: avoid_print

import 'package:flutter/material.dart';
import 'package:printing/printing.dart';
import 'package:pdf_export_print/pdf_export_print.dart' as pw;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'PDF Export Print Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const PDFExamplePage(),
    );
  }
}

class PDFExamplePage extends StatefulWidget {
  const PDFExamplePage({super.key});

  @override
  State<PDFExamplePage> createState() => _PDFExamplePageState();
}

class _PDFExamplePageState extends State<PDFExamplePage> {
  bool _isGenerating = false;
  late pw.TypeSafeDataAdapter _adapter;
  late pw.PDFConfig _config;

  // 模块显示控制
  bool _showLogo = true;
  bool _showTitle = true;
  bool _showMainTable = true;
  bool _showSubTable = true;
  bool _showApproval = true;
  bool _showFooter = true;

  // 边框显示控制
  bool _globalBorder = true;
  bool _mainTableBorder = true;
  bool _mainTableInnerBorder = true;
  bool _subTableBorder = true;
  bool _approvalBorder = true;
  bool _footerBorder = true;

  // 页面方向控制
  pw.PageOrientation _orientation = pw.PageOrientation.portrait;

  @override
  void initState() {
    super.initState();
    _initializeAdapter();
  }

  /// 初始化适配器
  void _initializeAdapter() {
    // 使用自定义配置
    _adapter = pw.TypeSafeDataAdapter(config: _createCustomConfig());

    _config = pw.PDFConfig.builder()
        .pageSize(pw.PdfPageFormat.a4)
        .withOrientation(_orientation)
        .margins(pw.EdgeInsets.all(20))
        .build();
  }

  /// 创建自定义配置
  pw.DataAdapterConfig _createCustomConfig() {
    // 使用增强配置,支持全局映射和模块特定映射
    final customFieldLabels = pw.FieldLabelConfig.enhanced(
      // 全局字段映射
      globalMappings: {
        // 主表字段
        'name': '员工姓名',
        'age': '年龄',
        'department': '所属部门',
        'position': '岗位职务',
        'email': '邮箱地址',
        'phone': '联系电话',
        'salary': '薪资',
        'description': '工作描述',
        'city': '城市',

        // 页脚字段
        'preparedBy': '制单人',
        'checkedBy': '审核人',
        'approvedBy': '批准人',
        'printDate': '打印日期',
        'version': '版本号',
        'pageCount': '页数',

        // 审批字段
        'nodeName': '审批节点',
        'approver': '审批人',
        'signature': '签名',
        'approveTime': '审批时间',
        'opinion': '审批意见',

        // 通用子表字段
        'projectName': '项目名称',
        'startDate': '开始时间',
        'endDate': '结束时间',
        'status': '项目状态',
        'evaluation': '项目评价',

        // 模块标题
        pw.ModuleType.subTable.value: '子表模块',
        'testDetails': '测试详情',
        'approval': '审批记录',
        'footer': '页脚模块',
      },
      // 模块特定映射
      moduleMappings: {
        // 测试详情模块的特定字段标签
        'testDetails': {
          'testName': '测试名称',
          'testType': '测试类型',
          'testResult': '测试结果',
          'testDate': '测试日期',
          'testDuration': '测试时长',
          'docNo': '测试编号', // 与主表的docNo区分
          'remarks': '测试备注', // 与主表的remarks区分
        },
        // 月度计划详情模块的特定字段标签
        'monthPlanDetails': {
          'docNo': '计划编号', // 与主表的docNo区分
          'remarks': '计划备注', // 与主表的remarks区分
          'planDate': '计划日期',
          'planAmount': '计划金额',
        },
      },
    );

    // 自定义模块配置 - 使用具体的配置类型
    final customModuleConfigs = <pw.ModuleType, pw.AdapterModuleConfig>{
      pw.ModuleType.logo: const pw.AdapterModuleConfig(
        moduleType: pw.ModuleType.logo,
        moduleConfig: pw.LogoConfig(
          defaultWidth: pw.LogoConstants.defaultWidth,
          defaultHeight: pw.LogoConstants.defaultHeight,
          bottomSpacing: pw.LogoConstants.defaultBottomSpacing,
          enabled: true, // 控制模块是否参与PDF生成
          priority: 1, // 数值越小优先级越高,控制在PDF中的显示顺序
          required: false, // 数据验证时是否必须提供该模块的数据
        ),
      ),
      pw.ModuleType.title: const pw.AdapterModuleConfig(
        moduleType: pw.ModuleType.title,
        moduleConfig: pw.TitleConfig(
          topSpacing: 5.0,
          bottomSpacing: 15.0,
          titleSpacing: 5.0,
          color: 'red',
          enabled: true, // 启用标题模块
          priority: 2, // 在Logo之后显示
          required: true, // 必须提供标题数据,否则验证失败
        ),
      ),
      pw.ModuleType.mainTable: const pw.AdapterModuleConfig(
        moduleType: pw.ModuleType.mainTable,
        fieldConfigs: [
          pw.FieldConfig(fieldKey: 'position', widthPercent: 0.33, sort: 1),
          pw.FieldConfig(fieldKey: 'name', widthPercent: 1, sort: 2),
          pw.FieldConfig(fieldKey: 'description', widthPercent: 0.67, sort: 3),
          pw.FieldConfig(fieldKey: 'department', widthPercent: 0.33, sort: 4),
          pw.FieldConfig(fieldKey: 'email', widthPercent: 1, sort: 5),
        ],
        moduleConfig: pw.MainTableConfig(
          showBorder: true,
          showInnerBorder: true,
          cellPadding: pw.EdgeInsets.all(4),
          fieldsPerRow: 3,
          enabled: true,
          priority: 3, // 在标题之后显示
          required: false,
        ),
      ),
      pw.ModuleType.subTable: const pw.AdapterModuleConfig(
        moduleType: pw.ModuleType.subTable,
        fieldConfigs: [
          pw.FieldConfig(fieldKey: 'projectName', widthPercent: 0.4, sort: 1),
          pw.FieldConfig(fieldKey: 'status', widthPercent: 0.2, sort: 2),
          pw.FieldConfig(fieldKey: 'startDate', widthPercent: 0.2, sort: 3),
          pw.FieldConfig(fieldKey: 'endDate', widthPercent: 0.2, sort: 4),
          // 保留图片字段配置
          pw.FieldConfig(fieldKey: '项目', widthPercent: 0.25, sort: 5),
          pw.FieldConfig(fieldKey: '图片', widthPercent: 0.2, sort: 6),
          pw.FieldConfig(fieldKey: '状态', widthPercent: 0.15, sort: 7),
          pw.FieldConfig(fieldKey: '进度', widthPercent: 0.1, sort: 8),
          pw.FieldConfig(fieldKey: '负责人', widthPercent: 0.15, sort: 9),
          pw.FieldConfig(fieldKey: '开始时间', widthPercent: 0.35, sort: 10),
          // evaluation 字段自动分配剩余空间
        ],
        moduleConfig: pw.SubTableConfig(
          showBorder: true,
          cellPadding: pw.EdgeInsets.all(4),
          headerHeight: 25.0,
          dataAlignment: pw.Alignment.center,
          enabled: true, // 启用子表模块
          priority: 4,
          required: false,
        ),
      ),
      pw.ModuleType.approval: const pw.AdapterModuleConfig(
        moduleType: pw.ModuleType.approval,
        fieldConfigs: [
          pw.FieldConfig(fieldKey: 'nodeName', widthPercent: 0.25, sort: 1),
          pw.FieldConfig(fieldKey: 'approver', widthPercent: 0.20, sort: 2),
          pw.FieldConfig(fieldKey: 'approveTime', widthPercent: 0.25, sort: 3),
          pw.FieldConfig(fieldKey: 'signature', widthPercent: 0.15, sort: 4),
          pw.FieldConfig(fieldKey: 'opinion', widthPercent: 0.15, sort: 5),
          // 其他字段自动分配剩余空间
        ],
        moduleConfig: pw.SubTableConfig(
          showBorder: true,
          cellPadding: pw.EdgeInsets.all(4),
          headerHeight: 25.0,
          defaultColumnWidths: pw.SubTableConfig.approvalDefaultColumnWidths,
          dataAlignment: pw.Alignment.center,
          enabled: true,
          priority: 5,
          required: false,
        ),
      ),
      pw.ModuleType.footer: const pw.AdapterModuleConfig(
        moduleType: pw.ModuleType.footer,
        fieldConfigs: [
          pw.FieldConfig(fieldKey: 'totalAmount', widthPercent: 0.67, sort: 1),
          pw.FieldConfig(fieldKey: 'createDate', widthPercent: 0.33, sort: 2),
        ],
        moduleConfig: pw.FooterConfig(
          fieldsPerRow: 3,
          showBorder: true,
          showInnerBorder: false,
          cellPadding: pw.EdgeInsets.all(4),
          enabled: true,
          priority: 6,
          required: false,
        ),
      ),
    };

    // 扩展的模块描述符集合(支持自定义模块)
    final customModuleDescriptors = <pw.ModuleDescriptor>{
      // 标准模块描述符(使用静态常量)
      pw.ModuleDescriptor.logo.copyWith(
        data: {
          'logoUrl': 'https://picsum.photos/100/50?random=1',
          'width': 100.0,
          'height': 50.0,
        },
      ),
      pw.ModuleDescriptor.title.copyWith(
        data: {
          'titles': ['示例文档', '副标题'],
          'color': 'blue',
          'alignment': 'center',
        },
      ),
      pw.ModuleDescriptor.mainTable.copyWith(
        data: {
          'name': '张三',
          'age': 28,
          'city': '北京',
          'email': 'zhangsan@example.com',
          'department': '技术部',
          'position': '高级工程师',
          'description': '软件工程师',
        },
      ),
      pw.ModuleDescriptor.subTable.copyWith(
        data: [
          {
            'projectName': 'PDF导出系统',
            'startDate': '2024-01-01',
            'endDate': '2024-03-31',
            'status': '已完成',
            'evaluation': '优秀',
            // 保留图片展示功能
            '项目': 'Flutter PDF 组件',
            '图片': 'https://picsum.photos/200/150?random=1',
            '状态': '已发布',
            '进度': '100%',
            '负责人': '张三',
            '开始时间': '2024-01-01',
          },
          {
            'projectName': '用户管理平台',
            'startDate': '2024-04-01',
            'endDate': '2024-06-30',
            'status': '进行中',
            'evaluation': '良好',
            // 保留图片展示功能
            '项目': 'PDF 预览功能',
            '图片': 'https://picsum.photos/200/150?random=2',
            '状态': '开发中',
            '进度': '80%',
            '负责人': '李四',
            '开始时间': '2024-04-01',
          },
          {
            'projectName': '数据分析系统',
            'startDate': '2024-07-01',
            'endDate': '2024-09-30',
            'status': '计划中',
            'evaluation': '一般',
          },
          {
            'projectName': '移动应用开发',
            'startDate': '2024-10-01',
            'endDate': '2024-12-31',
            'status': '已完成',
            'evaluation': '优秀',
          },
        ],
      ),
      pw.ModuleDescriptor.approval.copyWith(
        data: [
          {
            'nodeName': '申请',
            'approver': '李四',
            'signature': '李四',
            'approveTime': '2024-01-15 09:30:00',
            'opinion': '申请提交',
          },
          {
            'nodeName': '部门审批',
            'approver': '王五',
            'signature': '王五',
            'approveTime': '2024-01-15 14:20:00',
            'opinion': '同意',
          },
          {
            'nodeName': '财务审批',
            'approver': '赵六',
            'signature': '赵六',
            'approveTime': '2024-01-16 10:15:00',
            'opinion': '财务审核通过',
          },
        ],
      ),
      pw.ModuleDescriptor.footer.copyWith(
        data: {'totalAmount': '18000元', 'createDate': '2024-01-16'},
      ),

      // 自定义模块描述符(调用方创建)
      const pw.ModuleDescriptor(pw.ModuleType.subTable, 'testDetails', [
        {
          'testName': '单元测试',
          'testType': '自动化测试',
          'testResult': '通过',
          'testDate': '2024-01-10',
          'testDuration': '2小时',
        },
        {
          'testName': '集成测试',
          'testType': '手动测试',
          'testResult': '通过',
          'testDate': '2024-01-15',
          'testDuration': '4小时',
        },
        {
          'testName': '性能测试',
          'testType': '压力测试',
          'testResult': '待优化',
          'testDate': '2024-01-20',
          'testDuration': '6小时',
        },
      ]),
    };

    return pw.DataAdapterConfig(
      fieldLabelConfig: customFieldLabels,
      moduleConfigs: customModuleConfigs,
      moduleDescriptors: customModuleDescriptors,
    );
  }

  /// 演示多子表标题配置功能
  ///
  /// 展示如何为不同的子表实例设置不同的标题和数据源:
  /// 1. 默认子表模块:数据源 'details' -> 标题 '项目详情'
  /// 2. 自定义子表模块:数据源 'testDetails' -> 标题 '测试详情表'
  /// 3. 审批记录模块:数据源 'approvals' -> 标题 '审批记录'
  void _demonstrateMultiSubTableTitles() {
    print('=== 多子表标题配置演示 ===');
    print('');
    print('配置的标题映射:');
    print('  sub_table -> 项目详情 (默认子表,使用details数据)');
    print('  testDetails -> 测试详情表 (自定义子表,使用testDetails数据)');
    print('  approval -> 审批记录 (审批模块,使用approvals数据)');
    print('');
    print('数据源配置:');
    print('  1. details: [项目数据] -> SubTableModule()');
    print(
      '  2. testDetails: [测试数据] -> SubTableModule(moduleId: "testDetails")',
    );
    print('  3. approvals: [审批数据] -> SubTableModule(moduleId: "approval")');
    print('');
    print('工作原理:');
    print('  1. 系统首先尝试使用moduleId作为数据键查找数据');
    print('  2. 如果找到,直接使用该数据进行适配');
    print('  3. 同时使用moduleId作为标题查找键');
    print('  4. 实现不同子表使用不同数据源和标题');
    print('');
  }

  /// 更新适配器配置以反映当前的控制面板设置
  void _updateAdapterConfig() {
    // 根据当前控制面板设置更新模块配置
    final updatedModuleConfigs =
        Map<pw.ModuleType, pw.AdapterModuleConfig>.from(
          _adapter.config.moduleConfigs,
        );

    // 更新各模块的enabled状态
    if (updatedModuleConfigs.containsKey(pw.ModuleType.logo)) {
      final logoConfig = updatedModuleConfigs[pw.ModuleType.logo]!;
      updatedModuleConfigs[pw.ModuleType.logo] = pw.AdapterModuleConfig(
        moduleType: logoConfig.moduleType,
        moduleConfig: (logoConfig.moduleConfig as pw.LogoConfig).copyWith(
          enabled: _showLogo,
        ),
        fieldConfigs: logoConfig.fieldConfigs,
      );
    }

    if (updatedModuleConfigs.containsKey(pw.ModuleType.title)) {
      final titleConfig = updatedModuleConfigs[pw.ModuleType.title]!;
      updatedModuleConfigs[pw.ModuleType.title] = pw.AdapterModuleConfig(
        moduleType: titleConfig.moduleType,
        moduleConfig: (titleConfig.moduleConfig as pw.TitleConfig).copyWith(
          enabled: _showTitle,
        ),
        fieldConfigs: titleConfig.fieldConfigs,
      );
    }

    if (updatedModuleConfigs.containsKey(pw.ModuleType.mainTable)) {
      final mainTableConfig = updatedModuleConfigs[pw.ModuleType.mainTable]!;
      updatedModuleConfigs[pw.ModuleType.mainTable] = pw.AdapterModuleConfig(
        moduleType: mainTableConfig.moduleType,
        moduleConfig: (mainTableConfig.moduleConfig as pw.MainTableConfig)
            .copyWith(
              enabled: _showMainTable,
              showBorder: _mainTableBorder,
              showInnerBorder: _mainTableInnerBorder,
            ),
        fieldConfigs: mainTableConfig.fieldConfigs,
      );
    }

    if (updatedModuleConfigs.containsKey(pw.ModuleType.subTable)) {
      final subTableConfig = updatedModuleConfigs[pw.ModuleType.subTable]!;
      updatedModuleConfigs[pw.ModuleType.subTable] = pw.AdapterModuleConfig(
        moduleType: subTableConfig.moduleType,
        moduleConfig: (subTableConfig.moduleConfig as pw.SubTableConfig)
            .copyWith(enabled: _showSubTable, showBorder: _subTableBorder),
        fieldConfigs: subTableConfig.fieldConfigs,
      );
    }

    if (updatedModuleConfigs.containsKey(pw.ModuleType.approval)) {
      final approvalConfig = updatedModuleConfigs[pw.ModuleType.approval]!;
      updatedModuleConfigs[pw.ModuleType.approval] = pw.AdapterModuleConfig(
        moduleType: approvalConfig.moduleType,
        moduleConfig: (approvalConfig.moduleConfig as pw.SubTableConfig)
            .copyWith(enabled: _showApproval, showBorder: _approvalBorder),
        fieldConfigs: approvalConfig.fieldConfigs,
      );
    }

    if (updatedModuleConfigs.containsKey(pw.ModuleType.footer)) {
      final footerConfig = updatedModuleConfigs[pw.ModuleType.footer]!;
      updatedModuleConfigs[pw.ModuleType.footer] = pw.AdapterModuleConfig(
        moduleType: footerConfig.moduleType,
        moduleConfig: (footerConfig.moduleConfig as pw.FooterConfig).copyWith(
          enabled: _showFooter,
          showBorder: _footerBorder,
        ),
        fieldConfigs: footerConfig.fieldConfigs,
      );
    }

    // 创建新的配置
    final updatedConfig = pw.DataAdapterConfig(
      fieldLabelConfig: _adapter.config.fieldLabelConfig,
      moduleConfigs: updatedModuleConfigs,
      moduleDescriptors: _adapter.config.moduleDescriptors,
    );

    // 重新创建适配器
    _adapter = pw.TypeSafeDataAdapter(config: updatedConfig);
  }

  // 水印配置控制
  bool _watermarkEnabled = false;
  pw.WatermarkType _watermarkType = pw.WatermarkType.text;
  String _watermarkContent = 'DRAFT';
  pw.WatermarkPosition _watermarkPosition = pw.WatermarkPosition.center;
  pw.WatermarkMode _watermarkMode = pw.WatermarkMode.background;
  double _watermarkOpacity = 0.5;
  double _watermarkRotation = 0.0;
  pw.PdfColor? _watermarkTextColor;

  /// 生成并预览PDF
  Future<void> _generatePDF() async {
    setState(() {
      _isGenerating = true;
    });

    try {
      // 演示多子表标题配置功能
      _demonstrateMultiSubTableTitles();

      // 更新适配器配置以反映当前控制面板设置
      _updateAdapterConfig();

      // 数据现在来自 ModuleDescriptor,无需单独创建
      print('📊 使用 ModuleDescriptor 中的内置数据');

      // 创建PDF构建器
      final pdfBuilder = pw.PDFPrintBuilder()
          .withConfig(_config)
          .withDataAdapter(_adapter);

      // TypeSafe适配器会自动根据配置添加模块,无需手动添加
      print('📄 开始生成PDF文档...');
      final pdfDocument = await pdfBuilder.build();
      print('✅ PDF文档生成完成');

      // 显示PDF预览
      if (mounted) {
        await Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => PdfPreview(
              build: (format) => pdfDocument.save(),
              allowPrinting: true,
              allowSharing: true,
              canChangePageFormat: false,
              canChangeOrientation: false,
              canDebug: false,
            ),
          ),
        );
      }
    } catch (e) {
      print(e);
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('PDF生成失败: $e'), backgroundColor: Colors.red),
        );
      }
    } finally {
      if (mounted) {
        setState(() {
          _isGenerating = false;
        });
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('PDF Export Print 示例'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: SingleChildScrollView(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              // 控制面板
              _buildControlPanel(),
              const SizedBox(height: 16),
              const SizedBox(height: 24),
              ElevatedButton.icon(
                onPressed: _isGenerating ? null : _generatePDF,
                icon: _isGenerating
                    ? const SizedBox(
                        width: 20,
                        height: 20,
                        child: CircularProgressIndicator(strokeWidth: 2),
                      )
                    : const Icon(Icons.create),
                label: Text(_isGenerating ? '生成中...' : '生成PDF'),
                style: ElevatedButton.styleFrom(
                  padding: const EdgeInsets.symmetric(vertical: 16),
                  textStyle: const TextStyle(fontSize: 16),
                ),
              ),

              const SizedBox(height: 16),
              const Card(
                child: Padding(
                  padding: EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        '功能特性',
                        style: TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      SizedBox(height: 8),
                      Text('• 模块化设计,支持Logo、标题、主表、子表、审批、页脚模块'),
                      Text('• 高度可配置的样式和布局,支持跨列合并'),
                      Text('• 灵活的数据适配机制,自动处理复杂数据结构'),
                      Text('• 智能分页处理,支持大数据集自动分页'),
                      Text('• 支持自定义主题和企业级样式定制'),
                      Text('• 完整的审批流程展示和页脚信息管理'),
                      Text('• 🆕 子表图片展示功能,支持网络图片自适应缩放'),
                      Text('• 🆕 TypeSafe数据适配器,提供类型安全的数据处理'),
                    ],
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  /// 构建控制面板
  Widget _buildControlPanel() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              '控制面板',
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),

            // 模块显示控制
            const Text(
              '模块显示控制',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
            ),
            const SizedBox(height: 8),
            Wrap(
              spacing: 16,
              runSpacing: 8,
              children: [
                _buildModuleSwitch('Logo模块', _showLogo, (value) {
                  setState(() => _showLogo = value);
                }),
                _buildModuleSwitch('标题模块', _showTitle, (value) {
                  setState(() => _showTitle = value);
                }),
                _buildModuleSwitch('主表模块', _showMainTable, (value) {
                  setState(() => _showMainTable = value);
                }),
                _buildModuleSwitch('子表模块', _showSubTable, (value) {
                  setState(() => _showSubTable = value);
                }),
                _buildModuleSwitch('审批模块', _showApproval, (value) {
                  setState(() => _showApproval = value);
                }),
                _buildModuleSwitch('页脚模块', _showFooter, (value) {
                  setState(() => _showFooter = value);
                }),
              ],
            ),

            const SizedBox(height: 16),
            const Divider(),
            const SizedBox(height: 16),

            // 边框显示控制
            const Text(
              '边框显示控制',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
            ),
            const SizedBox(height: 8),
            Wrap(
              spacing: 16,
              runSpacing: 8,
              children: [
                _buildModuleSwitch('全局边框', _globalBorder, (value) {
                  setState(() => _globalBorder = value);
                }),
                _buildModuleSwitch('主表边框', _mainTableBorder, (value) {
                  setState(() => _mainTableBorder = value);
                }),
                _buildModuleSwitch('主表内边框', _mainTableInnerBorder, (value) {
                  setState(() => _mainTableInnerBorder = value);
                }),
                _buildModuleSwitch('子表边框', _subTableBorder, (value) {
                  setState(() => _subTableBorder = value);
                }),
                _buildModuleSwitch('审批边框', _approvalBorder, (value) {
                  setState(() => _approvalBorder = value);
                }),
                _buildModuleSwitch('页脚边框', _footerBorder, (value) {
                  setState(() => _footerBorder = value);
                }),
              ],
            ),

            const SizedBox(height: 16),
            const Divider(),
            const SizedBox(height: 16),

            // 页面方向控制
            const Text(
              '页面方向控制',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
            ),
            const SizedBox(height: 8),
            DropdownButton<pw.PageOrientation>(
              value: _orientation,
              onChanged: (pw.PageOrientation? newValue) {
                if (newValue != null) {
                  setState(() => _orientation = newValue);
                }
              },
              items: const [
                DropdownMenuItem(
                  value: pw.PageOrientation.portrait,
                  child: Text('Portrait(竖屏)'),
                ),
                DropdownMenuItem(
                  value: pw.PageOrientation.landscape,
                  child: Text('Landscape(横屏)'),
                ),
              ],
            ),

            const SizedBox(height: 16),
            const Divider(),
            const SizedBox(height: 16),

            // 水印配置控制
            _buildWatermarkControls(),
          ],
        ),
      ),
    );
  }

  /// 构建模块开关
  Widget _buildModuleSwitch(
    String label,
    bool value,
    ValueChanged<bool> onChanged,
  ) {
    return SizedBox(
      width: 120,
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Switch(value: value, onChanged: onChanged),
          const SizedBox(width: 8),
          Expanded(child: Text(label, style: const TextStyle(fontSize: 12))),
        ],
      ),
    );
  }

  /// 构建水印配置控制
  Widget _buildWatermarkControls() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          '水印配置',
          style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
        ),
        const SizedBox(height: 8),

        // 水印启用开关
        _buildModuleSwitch('启用水印', _watermarkEnabled, (value) {
          setState(() => _watermarkEnabled = value);
        }),

        if (_watermarkEnabled) ...[
          const SizedBox(height: 16),

          // 水印类型选择
          Row(
            children: [
              const Text('水印类型:'),
              const SizedBox(width: 8),
              DropdownButton<pw.WatermarkType>(
                value: _watermarkType,
                onChanged: (pw.WatermarkType? newValue) {
                  if (newValue != null) {
                    setState(() => _watermarkType = newValue);
                  }
                },
                items: pw.WatermarkType.values.map((type) {
                  return DropdownMenuItem(
                    value: type,
                    child: Text(_getWatermarkTypeDisplayName(type)),
                  );
                }).toList(),
              ),
            ],
          ),

          const SizedBox(height: 8),

          // 水印内容输入
          Row(
            children: [
              const Text('水印内容:'),
              const SizedBox(width: 8),
              Expanded(
                child: TextField(
                  controller: TextEditingController(text: _watermarkContent),
                  onChanged: (value) {
                    setState(() => _watermarkContent = value);
                  },
                  decoration: const InputDecoration(
                    border: OutlineInputBorder(),
                    isDense: true,
                    contentPadding: EdgeInsets.symmetric(
                      horizontal: 8,
                      vertical: 8,
                    ),
                  ),
                ),
              ),
            ],
          ),

          const SizedBox(height: 8),

          // 水印位置选择
          Row(
            children: [
              const Text('水印位置:'),
              const SizedBox(width: 8),
              DropdownButton<pw.WatermarkPosition>(
                value: _watermarkPosition,
                onChanged: (pw.WatermarkPosition? newValue) {
                  if (newValue != null) {
                    setState(() => _watermarkPosition = newValue);
                  }
                },
                items: pw.WatermarkPosition.values.map((position) {
                  return DropdownMenuItem(
                    value: position,
                    child: Text(_getWatermarkPositionDisplayName(position)),
                  );
                }).toList(),
              ),
            ],
          ),

          const SizedBox(height: 8),

          // 水印模式选择
          Row(
            children: [
              const Text('水印模式:'),
              const SizedBox(width: 8),
              DropdownButton<pw.WatermarkMode>(
                value: _watermarkMode,
                onChanged: (pw.WatermarkMode? newValue) {
                  if (newValue != null) {
                    setState(() => _watermarkMode = newValue);
                  }
                },
                items: pw.WatermarkMode.values.map((mode) {
                  return DropdownMenuItem(
                    value: mode,
                    child: Text(_getWatermarkModeDisplayName(mode)),
                  );
                }).toList(),
              ),
            ],
          ),

          const SizedBox(height: 8),

          // 水印颜色选择
          Row(
            children: [
              const Text('水印颜色:'),
              const SizedBox(width: 8),
              DropdownButton<pw.PdfColor?>(
                value: _watermarkTextColor,
                onChanged: (pw.PdfColor? newValue) {
                  setState(() => _watermarkTextColor = newValue);
                },
                items: [
                  const DropdownMenuItem(value: null, child: Text('默认灰色')),
                  DropdownMenuItem(
                    value: pw.PdfColors.red,
                    child: SizedBox(
                      width: 60,
                      child: Row(
                        children: [
                          Container(width: 16, height: 16, color: Colors.red),
                          const SizedBox(width: 4),
                          const Text('红色'),
                        ],
                      ),
                    ),
                  ),
                  DropdownMenuItem(
                    value: pw.PdfColors.blue,
                    child: SizedBox(
                      width: 60,
                      child: Row(
                        children: [
                          Container(width: 16, height: 16, color: Colors.blue),
                          const SizedBox(width: 4),
                          const Text('蓝色'),
                        ],
                      ),
                    ),
                  ),
                  DropdownMenuItem(
                    value: pw.PdfColors.green,
                    child: SizedBox(
                      width: 60,
                      child: Row(
                        children: [
                          Container(width: 16, height: 16, color: Colors.green),
                          const SizedBox(width: 4),
                          const Text('绿色'),
                        ],
                      ),
                    ),
                  ),
                  DropdownMenuItem(
                    value: pw.PdfColors.orange,
                    child: SizedBox(
                      width: 60,
                      child: Row(
                        children: [
                          Container(
                            width: 16,
                            height: 16,
                            color: Colors.orange,
                          ),
                          const SizedBox(width: 4),
                          const Text('橙色'),
                        ],
                      ),
                    ),
                  ),
                  DropdownMenuItem(
                    value: pw.PdfColors.purple,
                    child: SizedBox(
                      width: 60,
                      child: Row(
                        children: [
                          Container(
                            width: 16,
                            height: 16,
                            color: Colors.purple,
                          ),
                          const SizedBox(width: 4),
                          const Text('紫色'),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
            ],
          ),

          const SizedBox(height: 8),

          // 水印透明度滑块
          Row(
            children: [
              const Text('透明度:'),
              const SizedBox(width: 8),
              Expanded(
                child: Slider(
                  value: _watermarkOpacity,
                  min: 0.1,
                  max: 1.0,
                  divisions: 9,
                  label: _watermarkOpacity.toStringAsFixed(1),
                  onChanged: (value) {
                    setState(() => _watermarkOpacity = value);
                  },
                ),
              ),
              Text(_watermarkOpacity.toStringAsFixed(1)),
            ],
          ),

          // 水印旋转角度滑块
          Row(
            children: [
              const Text('旋转角度:'),
              const SizedBox(width: 8),
              Expanded(
                child: Slider(
                  value: _watermarkRotation,
                  min: -3.14159,
                  max: 3.14159,
                  divisions: 12,
                  label: '${(_watermarkRotation * 180 / 3.14159).round()}°',
                  onChanged: (value) {
                    setState(() => _watermarkRotation = value);
                  },
                ),
              ),
              Text('${(_watermarkRotation * 180 / 3.14159).round()}°'),
            ],
          ),
        ],
      ],
    );
  }

  /// 获取水印类型显示名称
  String _getWatermarkTypeDisplayName(pw.WatermarkType type) {
    switch (type) {
      case pw.WatermarkType.text:
        return '文本水印';
      case pw.WatermarkType.image:
        return '图片水印';
    }
  }

  /// 获取水印位置显示名称
  String _getWatermarkPositionDisplayName(pw.WatermarkPosition position) {
    switch (position) {
      case pw.WatermarkPosition.topLeft:
        return '左上角';
      case pw.WatermarkPosition.topCenter:
        return '顶部居中';
      case pw.WatermarkPosition.topRight:
        return '右上角';
      case pw.WatermarkPosition.centerLeft:
        return '左侧居中';
      case pw.WatermarkPosition.center:
        return '正中央';
      case pw.WatermarkPosition.centerRight:
        return '右侧居中';
      case pw.WatermarkPosition.bottomLeft:
        return '左下角';
      case pw.WatermarkPosition.bottomCenter:
        return '底部居中';
      case pw.WatermarkPosition.bottomRight:
        return '右下角';
    }
  }

  /// 获取水印模式显示名称
  String _getWatermarkModeDisplayName(pw.WatermarkMode mode) {
    switch (mode) {
      case pw.WatermarkMode.background:
        return '背景水印';
      case pw.WatermarkMode.foreground:
        return '前景水印';
    }
  }
}
1
likes
155
points
10
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A powerful and highly customizable Flutter PDF generation library with modular design.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, http, pdf

More

Packages that depend on pdf_export_print