import 'dart:io'; import 'package:flutter/material.dart'; import 'package:qhd_prevention/customWidget/custom_alert_dialog.dart'; import 'package:qhd_prevention/customWidget/custom_button.dart'; import 'package:qhd_prevention/customWidget/dotted_border_box.dart'; import 'package:qhd_prevention/customWidget/photo_picker_row.dart'; import 'package:qhd_prevention/customWidget/single_image_viewer.dart'; import 'package:qhd_prevention/customWidget/single_images_viewer.dart'; import 'package:qhd_prevention/http/ApiService.dart'; import 'package:qhd_prevention/tools/tools.dart'; // 操作类型枚举 enum OperationType { // 检查 check, // 安全措施 measure, } class MultiTextFieldWithTitle extends StatefulWidget { final String label; final List items; // 改为包含文本和图片的列表(外部结构任意字段都会保留) final bool isEditable; final bool isAddImage; final String hintText; final double fontSize; final bool isRequired; final bool isDeletFirst; final int imageCount; final OperationType operationType; final ValueChanged>> onItemsChanged; const MultiTextFieldWithTitle({ super.key, required this.label, required this.isEditable, required this.hintText, required this.onItemsChanged, this.fontSize = 15, this.items = const [], this.isRequired = true, this.isAddImage = true, this.isDeletFirst = false, this.imageCount = 1, this.operationType = OperationType.check, }); @override State createState() => _MultiTextFieldWithTitleState(); } class _MultiTextFieldWithTitleState extends State { final List _controllers = []; final List _focusNodes = []; /// 保存每一项的完整 map(来自 widget.items 的深复制),这样可以保留额外字段 final List> _itemsData = []; @override void initState() { super.initState(); _initializeFromItems(); } void _initializeFromItems() { // 释放旧资源 for (var c in _controllers) { c.dispose(); } for (var n in _focusNodes) { n.dispose(); } _controllers.clear(); _focusNodes.clear(); _itemsData.clear(); if (widget.items.isNotEmpty) { for (final rawItem in widget.items) { // 保守地把外部 item 转成 Map 并深复制一份(避免引用同一对象) Map item; if (rawItem is Map) { item = Map.from(rawItem); } else { // 如果传入不是 map,尝试包成 map item = {'content': rawItem?.toString() ?? ''}; } // 兼容字段:优先 content,再兜底空字符串 final text = item['content'] ?? ''; // 规范化:如果只有 imgPath(字符串)存在,也在 imgPaths 中保持 list 表示 if (item.containsKey('imgPath') && !item.containsKey('imgPaths')) { final v = item['imgPath']; if (v == null) { item['imgPaths'] = []; } else if (v is String && v.isNotEmpty) { item['imgPaths'] = [v]; } else if (v is List) { item['imgPaths'] = List.from(v.map((e) => e.toString())); } else { item['imgPaths'] = []; } } else if (!item.containsKey('imgPaths')) { item['imgPaths'] = []; } else { // 确保 imgPaths 是 List final v = item['imgPaths']; if (v == null) { item['imgPaths'] = []; } else if (v is List) { item['imgPaths'] = List.from(v.map((e) => e.toString())); } else if (v is String && v.isNotEmpty) { item['imgPaths'] = [v]; } else { item['imgPaths'] = []; } } final controller = TextEditingController(text: text); final node = FocusNode(); controller.addListener(_onDataChanged); _controllers.add(controller); _focusNodes.add(node); _itemsData.add(item); } } else { // 没有初始值,创建一个空项 if (widget.operationType == OperationType.check) _addNewItem(initialize: true); } // 触发一次回调(保证外面拿到初始结构) WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) _onDataChanged(); }); } @override void didUpdateWidget(covariant MultiTextFieldWithTitle oldWidget) { super.didUpdateWidget(oldWidget); // 当外部传入的 items 发生变化时,重新初始化 if (oldWidget.items != widget.items) { _initializeFromItems(); } } @override void dispose() { for (var c in _controllers) { c.dispose(); } for (var n in _focusNodes) { n.dispose(); } super.dispose(); } void _onDataChanged() { widget.onItemsChanged(_getAllItems()); } // 获取当前每一项的图片路径列表(List) List _getImageListForIndex(int index) { if (index < 0 || index >= _itemsData.length) return []; final v = _itemsData[index]['imgPaths']; if (v == null) return []; if (v is List) return List.from(v); if (v is List) return List.from(v.map((e) => e.toString())); if (v is String && v.isNotEmpty) return [v]; return []; } // 将新的图片列表写回 itemsData,同时也同步 imgPath 单字符串字段以兼容外部期待 void _setImageListForIndex(int index, List list) { if (index < 0 || index >= _itemsData.length) return; _itemsData[index]['imgPaths'] = List.from(list); // 同时更新单值字段,保持兼容(取第一张或空字符串) _itemsData[index]['imgPath'] = list.isNotEmpty ? list.first : ''; } // 添加新条目 void _addNewItem({bool initialize = false}) { // initialize 标识初始化阶段(避免重复触发回调两次,但我们仍然会触发 onDataChanged) setState(() { final newController = TextEditingController(); final newFocusNode = FocusNode(); newController.addListener(_onDataChanged); // 新建项:尽量包含 content、imgPath、imgPaths,其他字段为空(外部已有项的字段会被保留) final Map newItem = { 'content': '', 'imgPath': '', 'imgPaths': [], }; _controllers.add(newController); _focusNodes.add(newFocusNode); _itemsData.add(newItem); // 只有在非初始化时触发回调;但仍然需要回调让调用方获取最新结构(加上 initialize 选项) if (!initialize) _onDataChanged(); }); } // 删除条目 void _removeItem(int index) async { if (_controllers.length <= 1) return; final confirmed = await CustomAlertDialog.showConfirm( context, title: '提示', content: '确定删除此项吗?', cancelText: '取消', confirmText: '确定', ); if (!confirmed) return; setState(() { _controllers[index].dispose(); _focusNodes[index].dispose(); _controllers.removeAt(index); _focusNodes.removeAt(index); _itemsData.removeAt(index); _onDataChanged(); }); } // 返回当前所有项的完整 Map 列表(保留原有字段,只更新 content/imgPaths/imgPath) List> _getAllItems() { final List> items = []; for (int i = 0; i < _itemsData.length; i++) { final Map copy = Map.from( _itemsData[i], ); // 确保 content 与图片字段同步最新值 copy['content'] = _controllers[i].text; // 保证 imgPaths 是 List final imgs = _getImageListForIndex(i); copy['imgPaths'] = imgs; copy['imgPath'] = imgs.isNotEmpty ? imgs.first : ''; items.add(copy); } return items; } @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 标题行 InkWell( child: Row( children: [ Flexible( fit: FlexFit.loose, child: Row( children: [ if (widget.isRequired && widget.isEditable) const Text('* ', style: TextStyle(color: Colors.red)), Flexible( child: Text( widget.label, style: TextStyle( fontSize: widget.fontSize, fontWeight: FontWeight.bold, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ), ), const SizedBox(width: 8), if (widget.isEditable) CustomButton( text: " 添加 ", height: 30, padding: const EdgeInsets.symmetric( vertical: 2, horizontal: 5, ), backgroundColor: Colors.blue, onPressed: _addNewItem, ), ], ), ), const SizedBox(height: 8), // 条目区域 Column( children: [ ..._controllers.asMap().entries.map((entry) { final index = entry.key; final itemMap = _itemsData[index]; final item = { 'content': _controllers[index].text, 'imgPaths': _getImageListForIndex(index), // 这里只是便于 _buildItem 使用;真实的完整 map 存在于 _itemsData }; return _buildItem(index, itemMap); }).toList(), ], ), ], ), ); } Widget _buildItem(int index, Map fullItemMap) { final text = _controllers[index].text; final imageList = _getImageListForIndex(index); final itemTitle = widget.operationType == OperationType.check ? '检查情况${index + 1}' : '其他安全措施${index + 1}'; return Container( margin: const EdgeInsets.only(bottom: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 标题行(显示"检查情况1、2、3...") if (_controllers.length > 1) Padding( padding: const EdgeInsets.only(top: 0, left: 8, right: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ if (index > 0) Text( itemTitle, style: TextStyle( fontSize: widget.fontSize, fontWeight: FontWeight.bold, ), ), if (widget.isEditable && index > 0) IconButton( onPressed: () => _removeItem(index), icon: const Icon( Icons.close, color: Colors.red, size: 30, ), ), ], ), ), // 内容区域 Padding( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 7), child: SizedBox( width: double.maxFinite, child: DottedBorderBox( child: widget.isEditable ? TextField( controller: _controllers[index], decoration: InputDecoration( hintText: widget.hintText, ), focusNode: _focusNodes[index], keyboardType: TextInputType.multiline, maxLines: 3, style: TextStyle(fontSize: widget.fontSize), ) : Padding( padding: const EdgeInsets.all(12), child: Text( text.isEmpty ? '暂无内容' : text, style: TextStyle( fontSize: widget.fontSize, color: text.isEmpty ? Colors.grey : Colors.black, ), ), ), ), ), ), // 图片区域(编辑态) if (widget.isEditable && widget.isAddImage) RepairedPhotoSection( title: '图片', maxCount: widget.imageCount, isEdit: widget.isEditable, followInitialUpdates: true, // 关键:只传当前条目的图片数组 initialMediaPaths: imageList, // 当本地文件变化(用户选择/删除)回调,用它同步到 _itemsData onChanged: (files) { // setState(() { // if (files.isNotEmpty) { // imageList[index] = files.first.path; // } else { // imageList[index] = ''; // } // }); // _onDataChanged(); }, onMediaAdded: (localPath) async { // 也可能单独使用该回调,本处把它当成新增单张图片 final List current = _getImageListForIndex(index); current.add(localPath); setState(() { _setImageListForIndex(index, current); }); _onDataChanged(); }, onMediaRemoved: (localPath) async { final List current = _getImageListForIndex(index); current.removeWhere((p) => p == localPath); setState(() { _setImageListForIndex(index, current); }); _onDataChanged(); }, onMediaTapped: (path) async { presentOpaque(SingleImageViewer(imageUrl: path), context); }, onAiIdentify: () {}, ), // 非编辑态显示只读图片 if (!widget.isEditable && imageList.isNotEmpty&&widget.imageCount==1) _buildReadOnlyImage(imageList.first), if (!widget.isEditable && imageList.isNotEmpty&&widget.imageCount==4) _buildReadOnlyFourImage(imageList), ], ), ); } Widget _buildReadOnlyImage(String imagePath) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 7), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 8), Text( '图片:', style: TextStyle( fontSize: widget.fontSize - 1, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), GestureDetector( child: Container( width: 100, height: 100, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), image: DecorationImage( image: _getImageProvider(imagePath), fit: BoxFit.cover, ), ), ), onTap: () { presentOpaque(SingleImageViewer(imageUrl: imagePath), context); }, ), ], ), ); } Widget _buildReadOnlyFourImage( imagePath) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 7), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 8), Text( '图片:', style: TextStyle( fontSize: widget.fontSize - 1, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 10), Wrap( spacing: 8, // 水平间距 runSpacing: 8, // 垂直间距 children: List.generate(imagePath.length, (i) { final url = ApiService.baseImgPath + imagePath[i]; Widget img; if (url.startsWith('http')) { img = Image.network( url, height: 90, width: 90 , fit: BoxFit.fill, ); } else { img = Image.asset( url, height: 90, width: 90 , fit: BoxFit.fill, ); } return GestureDetector( onTap: () { presentOpaque( SingleImagesViewer( imageUrl: '', imageUrls: imagePath, initialIndex: i, ), context, ); }, child: ClipRRect( borderRadius: BorderRadius.circular(4), child: img, ), ); }), ), ], ), ); } ImageProvider _getImageProvider(String imagePath) { if (imagePath.startsWith('http')) { return NetworkImage(imagePath); } else { return FileImage(File(imagePath)); } } }