From febe7812de5d7bdce7cf7f4cc3d2e3c7e8825649 Mon Sep 17 00:00:00 2001 From: hejl Date: Wed, 6 Aug 2025 14:33:24 +0800 Subject: [PATCH] =?UTF-8?q?=E5=89=A5=E7=A6=BB=E5=87=BA=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E6=98=9F=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/content_deal_service.dart | 302 ++++++++++++++ .../controllers/pdf_parse_controller.dart | 384 ++++-------------- .../pdf_parse/widgets/pdf_parse_output.dart | 3 +- .../pdf_parse/widgets/pdf_parse_view.dart | 43 +- win_text_editor/pubspec.lock | 194 +++++---- win_text_editor/pubspec.yaml | 2 + .../flutter/generated_plugin_registrant.cc | 3 + .../windows/flutter/generated_plugins.cmake | 1 + 8 files changed, 526 insertions(+), 406 deletions(-) create mode 100644 win_text_editor/lib/modules/pdf_parse/controllers/content_deal_service.dart diff --git a/win_text_editor/lib/modules/pdf_parse/controllers/content_deal_service.dart b/win_text_editor/lib/modules/pdf_parse/controllers/content_deal_service.dart new file mode 100644 index 0000000..55b4fee --- /dev/null +++ b/win_text_editor/lib/modules/pdf_parse/controllers/content_deal_service.dart @@ -0,0 +1,302 @@ +import 'package:win_text_editor/app/providers/logger.dart'; + +class _CellMatch { + final String cellText; + final int matchLength; + + _CellMatch(this.cellText, this.matchLength); +} + +class _TableData { + final String header; + final List content; + final int year; + final String month; + + _TableData(this.header, this.content, this.year, this.month); +} + +class ContentDealService { + static const String _logTag = 'ContentDealService'; + static const int _maxDisplayLines = 100; + + // Content classification identifiers + static const String rawTextSection = '原始文本'; + static const String processedTextSection = '处理后文本'; + + static const Map _headerCharMap = { + 'A': '日', + 'B': '月', + 'C': '水', + 'D': '金', + 'E': '火', + 'F': '木', + 'G': '土', + 'O': '天', + 'I': '海', + 'J': '冥', + 'L': '南', + 'K': '北', + 'M': '孛', + 'N': '凯', + }; + + Map processPdfContent(String allText) { + // Process raw text display + final rawLines = allText.split('\n'); + final rawContent = + rawLines.length > _maxDisplayLines + ? '${rawLines.take(_maxDisplayLines).join('\n')}\n<<<剩余内容未显示>>>' + : allText; + + // Extract and process all tables with their dates + final tables = _extractTablesWithDates(allText); + final processedTables = tables + .map((table) { + return _processTable(table.content, table.year, table.month); + }) + .where((table) => table.isNotEmpty) + .join('\n'); // 过滤空表并直接连接,不再添加额外空行 + + // Limit processed content display + final processedLines = processedTables.split('\n'); + final limitedProcessedContent = + processedLines.length > _maxDisplayLines + ? '${processedLines.take(_maxDisplayLines).join('\n')}\n<<<剩余内容未显示>>>' + : processedTables; + + return { + ContentDealService.rawTextSection: rawContent, + ContentDealService.processedTextSection: limitedProcessedContent, + }; + } + + List<_TableData> _extractTablesWithDates(String rawText) { + final lines = rawText.split('\n'); + final tables = <_TableData>[]; + List? currentTable; + String? currentHeader; + int currentYear = 0; + String currentMonth = ''; + + for (final line in lines) { + // Check for new table header + final dateMatch = RegExp(r'^([A-Z]+)\s+(\d{4})\s+00:00 UT').firstMatch(line); + if (dateMatch != null) { + // Save previous table if exists + if (currentTable != null && currentTable.isNotEmpty) { + tables.add(_TableData(currentHeader ?? '', currentTable, currentYear, currentMonth)); + } + + // Start new table + currentYear = int.parse(dateMatch.group(2)!); + currentMonth = dateMatch.group(1)!; + currentHeader = line; + currentTable = []; + continue; + } + + // Check for table end + if (line.contains('Delta T =') && currentTable != null) { + tables.add(_TableData(currentHeader ?? '', currentTable, currentYear, currentMonth)); + currentTable = null; + continue; + } + + // Add to current table if exists + if (currentTable != null) { + currentTable.add(line); + } + } + + // Add last table if exists + if (currentTable != null && currentTable.isNotEmpty) { + tables.add(_TableData(currentHeader ?? '', currentTable, currentYear, currentMonth)); + } + + return tables.where((table) => table.content.isNotEmpty).toList(); // 过滤掉空表 + } + + String _processTable(List tableLines, int year, String month) { + final buffer = StringBuffer(); + bool foundHeader = false; + int weekDayIndex = 0; + final weekDays = ['M', 'T', 'W', 'T', 'F', 'S', 'S']; + final monthNum = _getMonthNumber(month); + + for (final line in tableLines) { + if (line.trim().isEmpty) continue; + + // 特殊处理表头行 + if (line.contains('Day Sid.t A B C D E F G O I J L K M N Day')) { + foundHeader = true; + // 将表头按空格分割 + var headerCells = line.trim().split(RegExp(r'\s+')); + + // 替换字母为中文并移除最后一列 + headerCells = + headerCells + .take(headerCells.length - 1) // 移除最后一列Day + .map( + (cell) => + cell.length == 1 && _headerCharMap.containsKey(cell) + ? _headerCharMap[cell]! + : cell, + ) + .toList(); + + // 在开头只插入日期列,将Day改为Week + headerCells[0] = 'Week'; // 将Day改为Week + headerCells.insert(0, '日期'); + buffer.writeln(headerCells.join(',')); + continue; + } + + if (!foundHeader) continue; + + // 数据行处理 + final dayMatch = RegExp(r'^([A-Z])\s*\d+').firstMatch(line.trim()); + if (dayMatch != null) { + final weekday = dayMatch.group(1)!; // 只提取星期字母 + + // 生成日期 (YYYYMMDD) + final dateStr = + (year > 0 && monthNum > 0 && dayMatch.group(2) != null) + ? '${year}${monthNum.toString().padLeft(2, '0')}${dayMatch.group(2)!.padLeft(2, '0')}' + : ''; + + // 使用原有的_parseNormalLine方法处理数据行(修改为只保留星期字母) + final csvLine = _parseNormalLine(line, weekday); // 直接传入星期字母 + + // 写入完整行(只添加日期列,不添加月份列) + buffer.write('$dateStr,'); // 只有日期列 + buffer.writeln(csvLine); + + weekDayIndex++; + } + } + + return buffer.toString().trim(); + } + + // 恢复原有的_parseNormalLine方法(保持原有单元格解析逻辑) + String _parseNormalLine(String line, String weekDay) { + final cells = []; + var remainingLine = line.trim(); + + // 1. 处理第一单元格(星期+日期) + var firstCellMatch = RegExp(r'^([A-Za-z]\d{1,2})\b').firstMatch(remainingLine); + if (firstCellMatch == null) { + firstCellMatch = RegExp(r'^([A-Za-z])\s*(\d{1,2})\b').firstMatch(remainingLine); + if (firstCellMatch != null) { + final mergedCell = '${firstCellMatch.group(1)}${firstCellMatch.group(2)}'; + cells.add(mergedCell); + remainingLine = remainingLine.substring(firstCellMatch.end).trim(); + } else { + cells.add(weekDay); + } + } else { + cells.add('$weekDay${firstCellMatch.group(1)!.substring(1)}'); + remainingLine = remainingLine.substring(firstCellMatch.end).trim(); + } + + // 2. 处理第二单元格(三组数字) + final secondCell = _parseThreeNumbersCell(remainingLine); + if (secondCell != null) { + cells.add(secondCell); + remainingLine = remainingLine.substring(secondCell.length).trim(); + } + + // 3. 处理第三单元格(三组符号) + final thirdCell = _parseThreeSymbolsCell(remainingLine); + if (thirdCell != null) { + cells.add(_replaceZodiacLetters(thirdCell)); + remainingLine = remainingLine.substring(thirdCell.length).trim(); + } + + // 4. 处理剩余单元格 + int columnNumber = 4; + while (remainingLine.isNotEmpty && columnNumber++ <= 16) { + final match = _parseTwoSymbolsCell(remainingLine); + if (match == null) { + Logger().error( + '[$_logTag] Failed to match cells, remaining: "$remainingLine"', + source: _logTag, + ); + break; + } + cells.add(_replaceZodiacLetters(match.cellText)); + remainingLine = remainingLine.substring(match.matchLength).trim(); + } + + return cells.join(','); + } + + int _getMonthNumber(String month) { + const monthMap = { + 'JANUARY': 1, + 'FEBRUARY': 2, + 'MARCH': 3, + 'APRIL': 4, + 'MAY': 5, + 'JUNE': 6, + 'JULY': 7, + 'AUGUST': 8, + 'SEPTEMBER': 9, + 'OCTOBER': 10, + 'NOVEMBER': 11, + 'DECEMBER': 12, + }; + return monthMap[month] ?? 0; + } + + String? _parseThreeNumbersCell(String input) { + final match = RegExp(r'^(\d+\s+\d+\s+\d+)').firstMatch(input); + return match?.group(0); + } + + String? _parseThreeSymbolsCell(String input) { + final match = RegExp(r'^(\d+[^\d\s]\s*\d+[^\d\s]\s*\d+)').firstMatch(input); + return match?.group(0); + } + + _CellMatch? _parseTwoSymbolsCell(String input) { + var match = RegExp(r'^(\d+[^\d\s]{1,2})\s+(\d+)').firstMatch(input); + if (match != null) { + final fullText = match.group(0)!; + final cellText = '${match.group(1)}${match.group(2)}'; + return _CellMatch(cellText, fullText.length); + } + + match = RegExp(r'^(\d+[^\d\s]{1,2}\s*\d+)').firstMatch(input); + if (match != null) { + return _CellMatch(match.group(0)!, match.group(0)!.length); + } + + match = RegExp(r'^(\d+[^\d\s]{1,2})').firstMatch(input); + if (match != null) { + return _CellMatch(match.group(0)!, match.group(0)!.length); + } + + return null; + } + + String _replaceZodiacLetters(String text) { + const replacementMap = { + 'a': '戌', //'羊', + 'b': '酉', //'牛', + 'c': '申', //'子', + 'd': '未', //'蟹', + 'e': '午', //'狮', + 'f': '巳', //'女', + 'g': '辰', //'秤', + 'h': '卯', //'蝎', + 'i': '寅', //'射', + 'j': '丑', //'羯', + 'k': '子', //'水', + 'l': '亥', //'鱼', + }; + + return text.split('').map((char) => replacementMap[char] ?? char).join(''); + } +} diff --git a/win_text_editor/lib/modules/pdf_parse/controllers/pdf_parse_controller.dart b/win_text_editor/lib/modules/pdf_parse/controllers/pdf_parse_controller.dart index 7048e38..4d51c56 100644 --- a/win_text_editor/lib/modules/pdf_parse/controllers/pdf_parse_controller.dart +++ b/win_text_editor/lib/modules/pdf_parse/controllers/pdf_parse_controller.dart @@ -1,297 +1,31 @@ +import 'dart:io'; + import 'package:file_picker/file_picker.dart'; +import 'package:flutter/foundation.dart'; import 'package:win_text_editor/app/providers/logger.dart'; import 'package:win_text_editor/modules/outline/models/outline_node.dart'; import 'package:win_text_editor/shared/base/base_content_controller.dart'; - -// Import the PDFium bindings +import 'package:excel/excel.dart'; +import 'package:file_saver/file_saver.dart'; +import 'dart:typed_data'; +import 'content_deal_service.dart'; import 'pdfium_bindings.dart'; -class _CellMatch { - final String cellText; // 返回的单元格内容(如 "1k4") - final int matchLength; // 实际匹配的原始文本长度(如 "1k 4" 长度是4) - - _CellMatch(this.cellText, this.matchLength); -} - class PdfParseController extends BaseContentController { String _filePath = ''; String? _errorMessage; PdfDocument? _pdfDocument; - // 在类顶部添加日志标签 + final ContentDealService _contentService = ContentDealService(); static const String _logTag = 'PDFParser'; Map _contentSections = {}; - List>> _tables = []; // 修正为三维列表(多个表格,每个表格是行列表) - List _nonTableContent = []; - - // 修改内容分类标识 - static const String rawTextSection = '原始文本'; - static const String processedTextSection = '处理后文本'; + List>> _tables = []; String get filePath => _filePath; String? get errorMessage => _errorMessage; Map get contentSections => _contentSections; List>> get tables => _tables; - String _processText(String rawText) { - // 1. 先过滤非表格内容 - var processed = _removeNonTableContent(rawText); - // 2. 转换为CSV格式并处理特殊格式 - processed = _convertToCsvWithSpecialFormat(processed); - // 3. 执行字母替换 - processed = _replaceDayLetters(processed); - - return processed; - } - - String _convertToCsvWithSpecialFormat(String text) { - final lines = text.split('\n'); - final buffer = StringBuffer(); - bool inDaySection = false; - final dayStartPattern = RegExp(r'^Day'); - final weekDays = ['M', 'T', 'W', 'T', 'F', 'S', 'S']; // 星期循环 - int weekDayIndex = 0; - int monthIndex = 0; // 月份索引 - - int lineNumber = 0; // 行号从1开始 - for (final line in lines) { - if (line.trim().isEmpty) { - buffer.writeln(); - continue; - } - - // 检查是否进入Day段落 - if (dayStartPattern.hasMatch(line.trim())) { - inDaySection = true; - monthIndex++; - } else { - inDaySection = false; - } - - if (inDaySection) { - // Day段落内的行处理:简单空格转逗号 - buffer.write("Month,"); - buffer.writeln(line.replaceAll(RegExp(r'\s+'), ',')); - } else { - // 非Day段落的行处理:复杂格式解析 - lineNumber++; - final csvLine = _parseNormalLine(line, weekDays[weekDayIndex % 7], lineNumber); - buffer.write('$monthIndex,'); - buffer.writeln(csvLine); - weekDayIndex++; - } - } - - return buffer.toString().trim(); - } - - String _parseNormalLine(String line, String weekDay, int lineNumber) { - final cells = []; - var remainingLine = line.trim(); - - // 1. 处理第一单元格 - var firstCellMatch = RegExp(r'^([A-Za-z]\d{1,2})\b').firstMatch(remainingLine); - if (firstCellMatch == null) { - firstCellMatch = RegExp(r'^([A-Za-z])\s*(\d{1,2})\b').firstMatch(remainingLine); - if (firstCellMatch != null) { - final mergedCell = '${firstCellMatch.group(1)}${firstCellMatch.group(2)}'; - cells.add(mergedCell); - remainingLine = remainingLine.substring(firstCellMatch.end).trim(); - } else { - cells.add(weekDay); - } - } else { - cells.add('$weekDay${firstCellMatch.group(1)!.substring(1)}'); - remainingLine = remainingLine.substring(firstCellMatch.end).trim(); - } - - // 2. 处理第二单元格 - final secondCell = _parseThreeNumbersCell(remainingLine); - if (secondCell != null) { - cells.add(secondCell); - remainingLine = remainingLine.substring(secondCell.length).trim(); - } - - // 3. 处理第三单元格 - final thirdCell = _parseThreeSymbolsCell(remainingLine); - if (thirdCell != null) { - cells.add(_replaceZodiacLetters(thirdCell)); - remainingLine = remainingLine.substring(thirdCell.length).trim(); - } - - // 4. 处理后续单元格 - int columnNumber = 4; - while (remainingLine.isNotEmpty && columnNumber++ <= 16) { - final match = _parseTwoSymbolsCell(remainingLine); - if (match == null) { - Logger().error('[$_logTag] 无法匹配后续单元格,剩余内容: "$remainingLine"', source: _logTag); - break; // 无法匹配时退出循环 - } - - cells.add(_replaceZodiacLetters(match.cellText)); - remainingLine = remainingLine.substring(match.matchLength).trim(); - } - - var lastCellMatch = RegExp(r'^([A-Za-z]\d{1,2})\b').firstMatch(remainingLine); - if (lastCellMatch == null) { - lastCellMatch = RegExp(r'^([A-Za-z])\s*(\d{1,2})\b').firstMatch(remainingLine); - if (lastCellMatch != null) { - final mergedCell = '${lastCellMatch.group(1)}${lastCellMatch.group(2)}'; - cells.add(mergedCell); - remainingLine = remainingLine.substring(lastCellMatch.end).trim(); - } else { - cells.add(weekDay); - } - } else { - cells.add('$weekDay${lastCellMatch.group(1)!.substring(1)}'); - remainingLine = remainingLine.substring(lastCellMatch.end).trim(); - } - - final result = cells.join(','); - - return result; - } - - // 匹配三个数字,保留内部空格(如 "12 34 56") - String? _parseThreeNumbersCell(String input) { - final match = RegExp(r'^(\d+\s+\d+\s+\d+)').firstMatch(input); - return match?.group(0); - } - - // 匹配三个数字带符号(如 "12°34'56") - String? _parseThreeSymbolsCell(String input) { - // Updated to handle spaces before single digits (like "21° 0'22") - final match = RegExp(r'^(\d+[^\d\s]\s*\d+[^\d\s]\s*\d+)').firstMatch(input); - return match?.group(0); - } - - // 匹配两个数字带符号(如 "12°34") - _CellMatch? _parseTwoSymbolsCell(String input) { - // 模式1:数字+1-2符号+空格+数字(如 "1k 4") - var match = RegExp(r'^(\d+[^\d\s]{1,2})\s+(\d+)').firstMatch(input); - if (match != null) { - final fullText = match.group(0)!; - final cellText = '${match.group(1)}${match.group(2)}'; - return _CellMatch(cellText, fullText.length); - } - - // 模式2:数字+1-2符号+数字(如 "1k4") - match = RegExp(r'^(\d+[^\d\s]{1,2}\s*\d+)').firstMatch(input); // Added \s* - if (match != null) { - return _CellMatch(match.group(0)!, match.group(0)!.length); - } - - // 模式3:数字+1-2符号(如 "1k") - match = RegExp(r'^(\d+[^\d\s]{1,2})').firstMatch(input); - if (match != null) { - return _CellMatch(match.group(0)!, match.group(0)!.length); - } - - return null; - } - - String _replaceDayLetters(String text) { - // 定义替换映射表 - const replacementMap = { - 'A': '日', - 'B': '月', - 'C': '水', - 'D': '金', - 'E': '火', - 'F': '木', - 'G': '土', - 'O': '天', - 'I': '海', - 'J': '冥', - 'L': '南', - 'K': '北', - 'M': '孛', - 'N': '凯', - }; - - final lines = text.split('\n'); - final buffer = StringBuffer(); - bool inDaySection = false; - - for (final line in lines) { - var processedLine = line; - - // 检查是否进入Day段落 - if (line.trim().startsWith('Month') && line.trim().endsWith('Day')) { - inDaySection = true; - } else { - // 空行结束Day段落 - inDaySection = false; - } - - // 如果在Day段落中,执行替换 - if (inDaySection) { - // 逐个字符替换 - final chars = line.split(','); - for (var i = 0; i < chars.length; i++) { - if (replacementMap.containsKey(chars[i])) { - chars[i] = replacementMap[chars[i]]!; - } - } - processedLine = chars.join(','); - } - - buffer.writeln(processedLine); - } - - return buffer.toString().trim(); - } - - String _replaceZodiacLetters(String text) { - // Define the replacement mapping (case sensitive) - const replacementMap = { - 'a': '羊', - 'b': '牛', - 'c': '子', - 'd': '蟹', - 'e': '狮', - 'f': '女', - 'g': '秤', - 'h': '蝎', - 'i': '射', - 'j': '羯', - 'k': '水', - 'l': '鱼', - }; - - // Replace each character in the input text - return text.split('').map((char) => replacementMap[char] ?? char).join(''); - } - - String _removeNonTableContent(String rawText) { - final lines = rawText.split('\n'); - final buffer = StringBuffer(); - bool inTargetSection = false; - - for (final line in lines) { - // 检测到开始标记,开启记录 - if (line.contains('00:00 UT')) { - inTargetSection = true; - //buffer.writeln(line); // 包含开始标记本身 - continue; - } - - // 检测到结束标记,停止记录 - if (line.contains('Delta T =')) { - // buffer.writeln(line); // 包含结束标记本身 - inTargetSection = false; - continue; - } - - // 如果在目标区间内,保留内容 - if (inTargetSection) { - buffer.writeln(line); - } - } - - return buffer.toString().trim(); - } - @override void dispose() { _pdfDocument?.dispose(); @@ -318,14 +52,10 @@ class PdfParseController extends BaseContentController { Future _loadPdfContent() async { try { - // Dispose of previous document if exists _pdfDocument?.dispose(); _pdfDocument = null; - - // Load new document _pdfDocument = PdfDocument.fromFile(_filePath); - // Extract text from all pages String allText = ''; for (var i = 0; i < _pdfDocument!.pageCount; i++) { allText += _pdfDocument!.getPageText(i) + '\n'; @@ -343,82 +73,62 @@ class PdfParseController extends BaseContentController { Future _extractDocumentSections(String allText) async { _contentSections.clear(); _tables.clear(); - _nonTableContent.clear(); + // 使用修改后的ContentDealService处理内容 + _contentSections = _contentService.processPdfContent(allText); + + // 表格提取保持原逻辑 final lines = allText.split('\n').map((line) => line.trim()).where((line) => line.isNotEmpty).toList(); - _extractTablesAndNonTables(lines); - - // 修改为新的分类方式 - final rawContent = _nonTableContent.join('\n'); - _contentSections[rawTextSection] = rawContent; - _contentSections[processedTextSection] = _processText(rawContent); } void _extractTablesAndNonTables(List lines) { List>? currentTable; bool inTable = false; bool tableStartPending = false; - - // 定义表格开始和结束的特征标记 const tableStartPattern = '00:00 UT'; const tableEndPattern = 'Delta T ='; for (var i = 0; i < lines.length; i++) { final line = lines[i]; - // 检测表格开始标记(当前行是开始标记,下一行开始表格) if (line.contains(tableStartPattern)) { tableStartPending = true; - _nonTableContent.add(line); // 开始标记本身是非表格内容 continue; } - // 检测表格结束标记 if (line.contains(tableEndPattern)) { if (inTable && currentTable != null && currentTable.isNotEmpty) { _tables.add(currentTable); } currentTable = null; inTable = false; - _nonTableContent.add(line); // 结束标记本身是非表格内容 continue; } - // 如果检测到开始标记且未进入表格状态 if (tableStartPending && !inTable) { inTable = true; tableStartPending = false; currentTable = []; } - // 表格内容处理 if (inTable) { - // 假设表格行至少包含2个非空列(可根据实际调整) final columns = - line - .split(RegExp(r'\s{2,}')) // 至少2个空格分隔 - .where((col) => col.trim().isNotEmpty) - .toList(); + line.split(RegExp(r'\s{2,}')).where((col) => col.trim().isNotEmpty).toList(); if (columns.length >= 2) { currentTable!.add(columns); } else { - // 不符合表格行特征,可能意味着表格结束 if (currentTable != null && currentTable.isNotEmpty) { _tables.add(currentTable); } currentTable = null; inTable = false; - _nonTableContent.add(line); } - } else { - _nonTableContent.add(line); } } - // 处理文档末尾可能未结束的表格 if (inTable && currentTable != null && currentTable.isNotEmpty) { _tables.add(currentTable); } @@ -426,18 +136,78 @@ class PdfParseController extends BaseContentController { String? genContentString(List sections) { final buffer = StringBuffer(); - for (final section in sections) { if (_contentSections.containsKey(section)) { - // buffer.writeln('===== $section ====='); buffer.writeln(_contentSections[section]); buffer.writeln(); } } - return buffer.isEmpty ? null : buffer.toString(); } + Future exportExcel(Sheet sheet) async { + try { + final processedContent = _contentSections[ContentDealService.processedTextSection]; + if (processedContent == null || processedContent.isEmpty) return; + + final lines = processedContent.split('\n'); + for (int rowIndex = 0; rowIndex < lines.length; rowIndex++) { + final line = lines[rowIndex]; + if (line.trim().isEmpty) continue; + + final cells = line.split(','); + for (int col = 0; col < cells.length; col++) { + sheet + .cell(CellIndex.indexByColumnRow(columnIndex: col, rowIndex: rowIndex)) + .value = TextCellValue(cells[col]); + } + } + + for (var i = 0; i < 5; i++) { + sheet.setColumnWidth(i, 15.0); + } + } catch (e) { + _errorMessage = '导出Excel失败: ${e.toString()}'; + notifyListeners(); + Logger().error(_errorMessage!); + } + } + + Future saveAsExcel() async { + try { + final excel = Excel.createExcel(); + final sheet = excel['Sheet1']!; + + await exportExcel(sheet); + + final bytes = excel.save()!; + + // Show save dialog to let user choose location + final String? outputPath = await FilePicker.platform.saveFile( + dialogTitle: 'Save Excel File', + fileName: 'pdf_export_${DateTime.now().millisecondsSinceEpoch}.xlsx', + allowedExtensions: ['xlsx'], + type: FileType.custom, + ); + + // If user cancels the dialog, return without saving + if (outputPath == null) { + Logger().info('User cancelled Excel export'); + return; + } + + // Save the file to the selected location + final file = File(outputPath); + await file.writeAsBytes(bytes); + + Logger().info('文件已导出至: $outputPath'); + } catch (e) { + _errorMessage = '保存Excel失败: ${e.toString()}'; + notifyListeners(); + Logger().error(_errorMessage!); + } + } + @override Future onOpenFile(String filePath, {dynamic appendArg}) async { await setFilePath(filePath); diff --git a/win_text_editor/lib/modules/pdf_parse/widgets/pdf_parse_output.dart b/win_text_editor/lib/modules/pdf_parse/widgets/pdf_parse_output.dart index d149136..f0fb1e7 100644 --- a/win_text_editor/lib/modules/pdf_parse/widgets/pdf_parse_output.dart +++ b/win_text_editor/lib/modules/pdf_parse/widgets/pdf_parse_output.dart @@ -1,5 +1,6 @@ // uft_component_right_side.dart import 'package:flutter/material.dart'; +import 'package:win_text_editor/modules/pdf_parse/controllers/content_deal_service.dart'; import 'package:win_text_editor/modules/pdf_parse/controllers/pdf_parse_controller.dart'; import 'package:win_text_editor/shared/components/code_generation_components.dart'; @@ -38,7 +39,7 @@ class _PdfParseOutputState extends State { @override Widget build(BuildContext context) { - final operations = [PdfParseController.rawTextSection, PdfParseController.processedTextSection]; + final operations = [ContentDealService.rawTextSection, ContentDealService.processedTextSection]; return CodeGenerationSection( title: '生成内容', diff --git a/win_text_editor/lib/modules/pdf_parse/widgets/pdf_parse_view.dart b/win_text_editor/lib/modules/pdf_parse/widgets/pdf_parse_view.dart index 5377841..8e077b0 100644 --- a/win_text_editor/lib/modules/pdf_parse/widgets/pdf_parse_view.dart +++ b/win_text_editor/lib/modules/pdf_parse/widgets/pdf_parse_view.dart @@ -54,20 +54,37 @@ class _PdfParseViewState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 上方的文件路径输入框 - TextField( - decoration: InputDecoration( - labelText: 'PDF File', - hintText: 'Select an PDF file', - suffixIcon: IconButton( - icon: const Icon(Icons.folder_open), - onPressed: _controller.pickFile, + // 修改为 Row 布局,TextField + 导出按钮 + Row( + children: [ + // TextField 保持不变,但移除 suffixIcon 中的按钮 + Expanded( + child: TextField( + decoration: InputDecoration( + labelText: 'PDF File', + hintText: 'Select a PDF file', + suffixIcon: IconButton( + icon: const Icon(Icons.folder_open), + onPressed: _controller.pickFile, + ), + border: const OutlineInputBorder(), + errorText: controller.errorMessage, + ), + controller: TextEditingController(text: _controller.filePath), + readOnly: true, + ), ), - border: const OutlineInputBorder(), - errorText: controller.errorMessage, - ), - controller: TextEditingController(text: _controller.filePath), - readOnly: true, + const SizedBox(width: 8), // 添加间距 + // 新增的导出按钮 + ElevatedButton.icon( + icon: const Icon(Icons.download, size: 20), + label: const Text('导出Excel'), + onPressed: () => _controller.saveAsExcel(), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), + ), + ), + ], ), const SizedBox(height: 4), Expanded( diff --git a/win_text_editor/pubspec.lock b/win_text_editor/pubspec.lock index 2a21392..5872f1f 100644 --- a/win_text_editor/pubspec.lock +++ b/win_text_editor/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: archive sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.6.1" async: @@ -14,7 +14,7 @@ packages: description: name: async sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.12.0" bitsdojo_window: @@ -22,7 +22,7 @@ packages: description: name: bitsdojo_window sha256: "88ef7765dafe52d97d7a3684960fb5d003e3151e662c18645c1641c22b873195" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.1.6" bitsdojo_window_linux: @@ -30,7 +30,7 @@ packages: description: name: bitsdojo_window_linux sha256: "9519c0614f98be733e0b1b7cb15b827007886f6fe36a4fb62cf3d35b9dd578ab" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.1.4" bitsdojo_window_macos: @@ -38,7 +38,7 @@ packages: description: name: bitsdojo_window_macos sha256: f7c5be82e74568c68c5b8449e2c5d8fd12ec195ecd70745a7b9c0f802bb0268f - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.1.4" bitsdojo_window_platform_interface: @@ -46,7 +46,7 @@ packages: description: name: bitsdojo_window_platform_interface sha256: "65daa015a0c6dba749bdd35a0f092e7a8ba8b0766aa0480eb3ef808086f6e27c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.1.2" bitsdojo_window_windows: @@ -54,7 +54,7 @@ packages: description: name: bitsdojo_window_windows sha256: fa982cf61ede53f483e50b257344a1c250af231a3cdc93a7064dd6dc0d720b68 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.1.6" boolean_selector: @@ -62,7 +62,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.2" characters: @@ -70,7 +70,7 @@ packages: description: name: characters sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.0" clock: @@ -78,7 +78,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.2" collection: @@ -86,7 +86,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.19.1" cross_file: @@ -94,7 +94,7 @@ packages: description: name: cross_file sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.3.4+2" crypto: @@ -102,7 +102,7 @@ packages: description: name: crypto sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.6" csv: @@ -110,7 +110,7 @@ packages: description: name: csv sha256: c6aa2679b2a18cb57652920f674488d89712efaf4d3fdf2e537215b35fc19d6c - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.0.0" docx_to_text: @@ -118,15 +118,31 @@ packages: description: name: docx_to_text sha256: d94e199bf9f36321cbd7701f82ce4874d7b94fd362a007fd4923ba9d57794664 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.1" + equatable: + dependency: transitive + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" + excel: + dependency: "direct main" + description: + name: excel + sha256: "1a15327dcad260d5db21d1f6e04f04838109b39a2f6a84ea486ceda36e468780" + url: "https://pub.dev" + source: hosted + version: "4.0.6" expandable: dependency: "direct main" description: name: expandable sha256: "9604d612d4d1146dafa96c6d8eec9c2ff0994658d6d09fed720ab788c7f5afc2" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.0.1" fake_async: @@ -134,7 +150,7 @@ packages: description: name: fake_async sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.2" ffi: @@ -142,7 +158,7 @@ packages: description: name: ffi sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.4" file: @@ -150,7 +166,7 @@ packages: description: name: file sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.0.1" file_picker: @@ -158,9 +174,17 @@ packages: description: name: file_picker sha256: "13ba4e627ef24503a465d1d61b32596ce10eb6b8903678d362a528f9939b4aa8" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "10.2.1" + file_saver: + dependency: "direct main" + description: + name: file_saver + sha256: "9efc615b43127952aa394e98b23d9e932715e8c008dc10710177008882b3d141" + url: "https://pub.dev" + source: hosted + version: "0.1.1" flutter: dependency: "direct main" description: flutter @@ -171,7 +195,7 @@ packages: description: name: flutter_charset_detector sha256: "21f6fe8172fbfe3ba9d2fe0dba3702ba07f682315e829a68d49185a0c80d5ad0" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.0.0" flutter_charset_detector_android: @@ -179,7 +203,7 @@ packages: description: name: flutter_charset_detector_android sha256: "617345b0f78ad56c2633ea6132e57c2e374f6970792afbe9743237f683eeae8e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.1" flutter_charset_detector_darwin: @@ -187,7 +211,7 @@ packages: description: name: flutter_charset_detector_darwin sha256: "8cf51c3e16c2fb4ec4e309f16f6046a0ddf1ff57d1b6b696410d077a9ffbfb15" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.1" flutter_charset_detector_platform_interface: @@ -195,7 +219,7 @@ packages: description: name: flutter_charset_detector_platform_interface sha256: "1c09ed7b314a5a9dde76057b98b7d35458ba881eed03d5e5b6f7f74b4869d18c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.0" flutter_charset_detector_web: @@ -203,7 +227,7 @@ packages: description: name: flutter_charset_detector_web sha256: e3ac65f94b12f4887937b21a19365d7927db816840cb93274e3861241cb0e9f2 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.0" flutter_js: @@ -211,7 +235,7 @@ packages: description: name: flutter_js sha256: "5bf5db354fe78fe24cb90a5fa6b4423d38712440c88e3445c3dc88bc134c452f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.8.0" flutter_lints: @@ -219,7 +243,7 @@ packages: description: name: flutter_lints sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.3" flutter_plugin_android_lifecycle: @@ -227,7 +251,7 @@ packages: description: name: flutter_plugin_android_lifecycle sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.28" flutter_syntax_view: @@ -235,7 +259,7 @@ packages: description: name: flutter_syntax_view sha256: c5017bbedfdcf538daba765e16541fcb26434071655ca00cea7cbc205a70246a - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.1.7" flutter_test: @@ -248,7 +272,7 @@ packages: description: name: flutter_treeview sha256: ce7a66452e02877700890cb674773ea0af28d914192acfb5bf55a50ce35b5819 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.7+1" flutter_web_plugins: @@ -261,7 +285,7 @@ packages: description: name: hive sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.3" hive_flutter: @@ -269,7 +293,7 @@ packages: description: name: hive_flutter sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.0" http: @@ -277,7 +301,7 @@ packages: description: name: http sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.0" http_parser: @@ -285,7 +309,7 @@ packages: description: name: http_parser sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.1.2" intl: @@ -293,7 +317,7 @@ packages: description: name: intl sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.19.0" jieba_flutter: @@ -301,7 +325,7 @@ packages: description: name: jieba_flutter sha256: "01d645633a05c67e526d165b3d17343b99acf77afd2230f699cc587705247fd6" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.2.0" leak_tracker: @@ -309,7 +333,7 @@ packages: description: name: leak_tracker sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "10.0.8" leak_tracker_flutter_testing: @@ -317,7 +341,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.9" leak_tracker_testing: @@ -325,7 +349,7 @@ packages: description: name: leak_tracker_testing sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.1" lints: @@ -333,7 +357,7 @@ packages: description: name: lints sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.1" matcher: @@ -341,7 +365,7 @@ packages: description: name: matcher sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.12.17" material_color_utilities: @@ -349,7 +373,7 @@ packages: description: name: material_color_utilities sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.11.1" meta: @@ -357,7 +381,7 @@ packages: description: name: meta sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.16.0" mustache_template: @@ -365,7 +389,7 @@ packages: description: name: mustache_template sha256: a46e26f91445bfb0b60519be280555b06792460b27b19e2b19ad5b9740df5d1c - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.0" nested: @@ -373,7 +397,7 @@ packages: description: name: nested sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.0" path: @@ -381,7 +405,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.9.1" path_provider: @@ -389,7 +413,7 @@ packages: description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.5" path_provider_android: @@ -397,7 +421,7 @@ packages: description: name: path_provider_android sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.17" path_provider_foundation: @@ -405,7 +429,7 @@ packages: description: name: path_provider_foundation sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.1" path_provider_linux: @@ -413,7 +437,7 @@ packages: description: name: path_provider_linux sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.1" path_provider_platform_interface: @@ -421,7 +445,7 @@ packages: description: name: path_provider_platform_interface sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.2" path_provider_windows: @@ -429,7 +453,7 @@ packages: description: name: path_provider_windows sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.3.0" petitparser: @@ -437,7 +461,7 @@ packages: description: name: petitparser sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.1.0" platform: @@ -445,7 +469,7 @@ packages: description: name: platform sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.6" plugin_platform_interface: @@ -453,7 +477,7 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.8" pluto_grid: @@ -461,7 +485,7 @@ packages: description: name: pluto_grid sha256: "1d4cd9d2652742b556aa9b3230cc64672a3f63c34a9acc80fef794ab36ad903b" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "8.0.0" provider: @@ -469,7 +493,7 @@ packages: description: name: provider sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.1.5" quiver: @@ -477,7 +501,7 @@ packages: description: name: quiver sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.2.2" rxdart: @@ -485,7 +509,7 @@ packages: description: name: rxdart sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.27.7" screen_retriever: @@ -493,7 +517,7 @@ packages: description: name: screen_retriever sha256: "6ee02c8a1158e6dae7ca430da79436e3b1c9563c8cf02f524af997c201ac2b90" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.1.9" shared_preferences: @@ -501,7 +525,7 @@ packages: description: name: shared_preferences sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.5.3" shared_preferences_android: @@ -509,7 +533,7 @@ packages: description: name: shared_preferences_android sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.10" shared_preferences_foundation: @@ -517,7 +541,7 @@ packages: description: name: shared_preferences_foundation sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.5.4" shared_preferences_linux: @@ -525,7 +549,7 @@ packages: description: name: shared_preferences_linux sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.1" shared_preferences_platform_interface: @@ -533,7 +557,7 @@ packages: description: name: shared_preferences_platform_interface sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.1" shared_preferences_web: @@ -541,7 +565,7 @@ packages: description: name: shared_preferences_web sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.3" shared_preferences_windows: @@ -549,7 +573,7 @@ packages: description: name: shared_preferences_windows sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.1" sky_engine: @@ -562,7 +586,7 @@ packages: description: name: source_span sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.10.1" stack_trace: @@ -570,7 +594,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.12.1" stream_channel: @@ -578,7 +602,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.4" string_scanner: @@ -586,7 +610,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.1" sync_http: @@ -594,7 +618,7 @@ packages: description: name: sync_http sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.3.1" syncfusion_flutter_core: @@ -602,7 +626,7 @@ packages: description: name: syncfusion_flutter_core sha256: a2427697bfad5b611db78ea4c4daef82d3350b83c729a8dc37959662a31547f9 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "23.2.7" syncfusion_flutter_datagrid: @@ -610,7 +634,7 @@ packages: description: name: syncfusion_flutter_datagrid sha256: "9f621f6344d2ed7ea3a8d0ff5c145c174f1e227d6d8851290591ceb718e44600" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "23.2.7" term_glyph: @@ -618,7 +642,7 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.2" test_api: @@ -626,7 +650,7 @@ packages: description: name: test_api sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.7.4" typed_data: @@ -634,7 +658,7 @@ packages: description: name: typed_data sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.0" vector_math: @@ -642,7 +666,7 @@ packages: description: name: vector_math sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.4" vm_service: @@ -650,7 +674,7 @@ packages: description: name: vm_service sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "14.3.1" web: @@ -658,7 +682,7 @@ packages: description: name: web sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.1" win32: @@ -666,7 +690,7 @@ packages: description: name: win32 sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.13.0" window_manager: @@ -674,7 +698,7 @@ packages: description: name: window_manager sha256: "8699323b30da4cdbe2aa2e7c9de567a6abd8a97d9a5c850a3c86dcd0b34bbfbf" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.3.9" xdg_directories: @@ -682,7 +706,7 @@ packages: description: name: xdg_directories sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.0" xml: @@ -690,7 +714,7 @@ packages: description: name: xml sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.5.0" yaml: @@ -698,7 +722,7 @@ packages: description: name: yaml sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.3" sdks: diff --git a/win_text_editor/pubspec.yaml b/win_text_editor/pubspec.yaml index ab0ac15..2c37d52 100644 --- a/win_text_editor/pubspec.yaml +++ b/win_text_editor/pubspec.yaml @@ -34,6 +34,8 @@ dependencies: docx_to_text: ^1.0.1 flutter_js: 0.8.0 ffi: ^2.0.1 + excel: 4.0.6 # 用于创建和编辑Excel + file_saver: ^0.1.1 # 用于保存文件对话框 dev_dependencies: flutter_test: diff --git a/win_text_editor/windows/flutter/generated_plugin_registrant.cc b/win_text_editor/windows/flutter/generated_plugin_registrant.cc index 760476a..02cc995 100644 --- a/win_text_editor/windows/flutter/generated_plugin_registrant.cc +++ b/win_text_editor/windows/flutter/generated_plugin_registrant.cc @@ -7,6 +7,7 @@ #include "generated_plugin_registrant.h" #include +#include #include #include #include @@ -14,6 +15,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { BitsdojoWindowPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("BitsdojoWindowPlugin")); + FileSaverPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSaverPlugin")); FlutterJsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterJsPlugin")); ScreenRetrieverPluginRegisterWithRegistrar( diff --git a/win_text_editor/windows/flutter/generated_plugins.cmake b/win_text_editor/windows/flutter/generated_plugins.cmake index 08bf885..b876d8c 100644 --- a/win_text_editor/windows/flutter/generated_plugins.cmake +++ b/win_text_editor/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST bitsdojo_window_windows + file_saver flutter_js screen_retriever window_manager