1521 lines
51 KiB
Dart
1521 lines
51 KiB
Dart
import 'dart:io';
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:qhd_prevention/customWidget/custom_button.dart';
|
||
import 'package:qhd_prevention/customWidget/single_image_viewer.dart';
|
||
import 'package:qhd_prevention/http/ApiService.dart';
|
||
import 'package:qhd_prevention/tools/tools.dart';
|
||
import 'package:flutter/services.dart';
|
||
|
||
class ItemListWidget {
|
||
static const Color detailtextColor = Colors.black54;
|
||
static const double requiredInset = 0;
|
||
static const double horizontal_inset = 12;
|
||
static const double vertical_inset = 5;
|
||
|
||
/// 单行水平排列:
|
||
/// - 可编辑时:标题 + TextField
|
||
/// - 不可编辑时:标题 + 带省略号的文本
|
||
|
||
static Widget singleLineTitleText({
|
||
required String label, // 标题文本
|
||
required bool isEditable, // 是否可编辑
|
||
String? text, // 显示的初始文本(编辑/非编辑模式都会显示)
|
||
String hintText = '请输入',
|
||
double fontSize = 14, // 字体大小
|
||
bool isRequired = true,
|
||
bool strongRequired = false,
|
||
ValueChanged<String>? onChanged,
|
||
ValueChanged<String>? onFieldSubmitted,
|
||
int maxLines = 5,
|
||
bool showMaxLength = false,
|
||
|
||
// 新增:数字输入控制
|
||
bool isNumericInput = false,
|
||
int maxDecimalPlaces = 2,
|
||
TextInputType keyboardType = TextInputType.text,
|
||
}) {
|
||
// 数字输入键盘
|
||
final actualKeyboardType =
|
||
isNumericInput
|
||
? const TextInputType.numberWithOptions(decimal: true)
|
||
: keyboardType;
|
||
|
||
// 数字输入格式化器
|
||
final List<TextInputFormatter>? numericFormatters =
|
||
isNumericInput
|
||
? [
|
||
FilteringTextInputFormatter.allow(RegExp(r'[\d\.]')),
|
||
TextInputFormatter.withFunction((oldValue, newValue) {
|
||
final newText = newValue.text;
|
||
|
||
if (newText.isEmpty) return newValue;
|
||
|
||
if (newText.split('.').length > 2) return oldValue;
|
||
|
||
final regex = RegExp(
|
||
r'^\d*\.?\d{0,' + maxDecimalPlaces.toString() + r'}$',
|
||
);
|
||
|
||
if (regex.hasMatch(newText)) return newValue;
|
||
|
||
return oldValue;
|
||
}),
|
||
]
|
||
: null;
|
||
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(
|
||
vertical: vertical_inset,
|
||
horizontal: horizontal_inset,
|
||
),
|
||
child: Row(
|
||
mainAxisAlignment:
|
||
isEditable
|
||
? MainAxisAlignment.start
|
||
: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
if ((isRequired && isEditable) || strongRequired)
|
||
Text('* ', style: TextStyle(color: Colors.red)),
|
||
Text(
|
||
label,
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(width: 8),
|
||
|
||
/// 可编辑模式
|
||
isEditable
|
||
? Expanded(
|
||
child: TextFormField(
|
||
initialValue: text ?? '',
|
||
autofocus: false,
|
||
onChanged: onChanged,
|
||
onFieldSubmitted: onFieldSubmitted,
|
||
keyboardType: actualKeyboardType,
|
||
maxLength: showMaxLength ? 120 : null,
|
||
style: TextStyle(fontSize: fontSize),
|
||
maxLines: 1,
|
||
inputFormatters: numericFormatters,
|
||
decoration: InputDecoration(
|
||
isDense: true,
|
||
hintText: hintText,
|
||
contentPadding: EdgeInsets.symmetric(vertical: 8),
|
||
),
|
||
),
|
||
)
|
||
/// 只读模式
|
||
: Expanded(
|
||
child: Text(
|
||
text ?? '',
|
||
maxLines: maxLines,
|
||
style: TextStyle(fontSize: fontSize, color: detailtextColor),
|
||
textAlign: TextAlign.right,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 多行垂直排列:
|
||
/// - 可编辑时:标题 + 可扩展的多行 TextField
|
||
/// - 不可编辑时:标题 + 带滚动的多行文本
|
||
static Widget multiLineTitleTextField({
|
||
required String label, // 标题文本
|
||
required bool isEditable, // 是否可编辑
|
||
TextEditingController? controller, // 编辑时使用的控制器
|
||
String? text, // 不可编辑时显示的文本
|
||
double fontSize = 14, // 字体大小
|
||
double height = 110, // 整体高度
|
||
bool isRequired = true,
|
||
String hintText = '请输入',
|
||
ValueChanged<String>? onChanged,
|
||
bool showMaxLength = false,
|
||
}) {
|
||
return Container(
|
||
// 统一左右 padding,保证标题和内容在同一左侧基线
|
||
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 12),
|
||
height: height,
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
if (isRequired && isEditable)
|
||
Text('* ', style: TextStyle(color: Colors.red)),
|
||
Text(
|
||
label,
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 8),
|
||
Expanded(
|
||
child:
|
||
isEditable
|
||
? TextFormField(
|
||
autofocus: false,
|
||
initialValue: controller == null ? text : null,
|
||
controller: controller,
|
||
keyboardType: TextInputType.multiline,
|
||
maxLines: null,
|
||
expands: true,
|
||
onChanged: onChanged,
|
||
maxLength: showMaxLength ? 120 : null,
|
||
// 垂直顶部对齐
|
||
textAlignVertical: TextAlignVertical.top,
|
||
style: TextStyle(fontSize: fontSize),
|
||
decoration: InputDecoration(
|
||
hintText: hintText,
|
||
// 去掉 TextField 默认内边距
|
||
contentPadding: EdgeInsets.zero,
|
||
border: InputBorder.none,
|
||
),
|
||
)
|
||
: SingleChildScrollView(
|
||
// 去掉多余的 padding
|
||
padding: EdgeInsets.zero,
|
||
child: Text(
|
||
text ?? '',
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
color: detailtextColor,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
static Widget multiLineAutoTitleTextField({
|
||
required String label,
|
||
required bool isEditable,
|
||
TextEditingController? controller,
|
||
String? text,
|
||
double fontSize = 14,
|
||
double height = 110, // 编辑时保留原行为
|
||
bool isRequired = true,
|
||
String hintText = '请输入',
|
||
ValueChanged<String>? onChanged,
|
||
bool showMaxLength = false,
|
||
double? maxDisplayHeight, // 可选:在父无高度约束时作为“可用高度”参考
|
||
}) {
|
||
return LayoutBuilder(
|
||
builder: (context, constraints) {
|
||
// 内边距(与你的 UI 保持一致)
|
||
const horizontalPadding = 12.0;
|
||
const verticalPadding = 5.0;
|
||
final content = text ?? '';
|
||
final textStyle = TextStyle(fontSize: fontSize, color: detailtextColor);
|
||
|
||
// 编辑状态:保持原来固定 height 与 expands:true 的行为
|
||
if (isEditable) {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(
|
||
vertical: verticalPadding,
|
||
horizontal: horizontalPadding,
|
||
),
|
||
height: height,
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
if (isRequired)
|
||
const Text('* ', style: TextStyle(color: Colors.red)),
|
||
Text(
|
||
label,
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 8),
|
||
Expanded(
|
||
child: TextFormField(
|
||
autofocus: false,
|
||
initialValue: controller == null ? text : null,
|
||
controller: controller,
|
||
keyboardType: TextInputType.multiline,
|
||
maxLines: null,
|
||
expands: true,
|
||
onChanged: onChanged,
|
||
maxLength: showMaxLength ? 120 : null,
|
||
textAlignVertical: TextAlignVertical.top,
|
||
style: TextStyle(fontSize: fontSize),
|
||
decoration: InputDecoration(
|
||
hintText: hintText,
|
||
contentPadding: EdgeInsets.zero,
|
||
border: InputBorder.none,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// ---------- 不可编辑:测量并决定是否需要滚动 ----------
|
||
// 计算可用于内容的最大宽度(减去左右 padding)
|
||
final maxWidth =
|
||
(constraints.maxWidth.isFinite
|
||
? constraints.maxWidth
|
||
: MediaQuery.of(context).size.width) -
|
||
horizontalPadding * 2;
|
||
final safeMaxWidth =
|
||
maxWidth > 0
|
||
? maxWidth
|
||
: MediaQuery.of(context).size.width - horizontalPadding * 2;
|
||
|
||
// 使用 TextPainter 测量文本高度(不限制行数)
|
||
final tp = TextPainter(
|
||
text: TextSpan(text: content, style: textStyle),
|
||
textDirection: TextDirection.ltr,
|
||
textWidthBasis: TextWidthBasis.parent,
|
||
);
|
||
tp.layout(maxWidth: safeMaxWidth);
|
||
final textHeight = tp.size.height;
|
||
|
||
// 估算标签与间距所需高度
|
||
final labelHeight = fontSize + 8; // label + 上下间距估算
|
||
final neededHeight = labelHeight + 8 + textHeight + verticalPadding * 2;
|
||
|
||
// availableHeight:若父是 bounded 则用 constraints.maxHeight,否则使用 maxDisplayHeight 或屏幕高度比例作为阈值
|
||
double availableHeight;
|
||
if (constraints.maxHeight.isFinite) {
|
||
availableHeight = constraints.maxHeight;
|
||
} else {
|
||
availableHeight =
|
||
maxDisplayHeight ?? MediaQuery.of(context).size.height * 0.4;
|
||
}
|
||
|
||
final needsScroll = neededHeight > availableHeight;
|
||
|
||
// 返回 Widget(不固定高度)
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(
|
||
vertical: verticalPadding,
|
||
horizontal: horizontalPadding,
|
||
),
|
||
// 不设置 height,让其在可扩展父容器中自然撑开
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
if (isRequired)
|
||
const Text('* ', style: TextStyle(color: Colors.red)),
|
||
Text(
|
||
label,
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 8),
|
||
// 如果需要滚动则约束高度并内包 SingleChildScrollView
|
||
if (needsScroll)
|
||
// ConstrainedBox 限制最大高度,避免无限膨胀
|
||
ConstrainedBox(
|
||
constraints: BoxConstraints(
|
||
// 留出 label 的高度,最大不超过 availableHeight - labelHeight
|
||
maxHeight: (availableHeight - labelHeight - 16).clamp(
|
||
80.0,
|
||
availableHeight,
|
||
),
|
||
),
|
||
child: SingleChildScrollView(
|
||
padding: EdgeInsets.zero,
|
||
physics: const BouncingScrollPhysics(),
|
||
child: Text(content, style: textStyle),
|
||
),
|
||
)
|
||
else
|
||
// 父高度足够,直接展示文本(自然撑高)
|
||
Text(content, style: textStyle),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
/// 单行可点击选择:
|
||
/// - 可编辑时:标题 + “请选择”提示 + 右箭头
|
||
/// - 不可编辑时:标题 + 文本内容
|
||
static Widget selectableLineTitleTextRightButton({
|
||
required String label, // 标题文本
|
||
required bool isEditable, // 是否可点击
|
||
required String text, // 显示内容或提示
|
||
VoidCallback? onTap, // 点击回调
|
||
double fontSize = 14, // 字体大小
|
||
bool isClean = false,
|
||
bool isTip = false,
|
||
VoidCallback? onTapClean, // 清除回调
|
||
VoidCallback? onTapTip, // 提醒回调
|
||
bool isRequired = true,
|
||
String cleanText = '清除',
|
||
bool strongRequired = false,
|
||
|
||
double horizontalnum = horizontal_inset,
|
||
double verticalInset = vertical_inset,
|
||
|
||
}) {
|
||
return InkWell(
|
||
onTap: isEditable ? onTap : null,
|
||
child: Container(
|
||
padding: EdgeInsets.symmetric(
|
||
vertical: verticalInset,
|
||
horizontal: horizontalnum,
|
||
),
|
||
child: Row(
|
||
children: [
|
||
// 1. 标题
|
||
Row(
|
||
children: [
|
||
if ((isRequired && isEditable) || strongRequired)
|
||
Text('* ', style: TextStyle(color: Colors.red)),
|
||
Row(
|
||
children: [
|
||
Text(
|
||
label,
|
||
textAlign: TextAlign.right,
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
if (isTip)
|
||
Column(
|
||
children: [
|
||
IconButton(
|
||
onPressed: onTapTip,
|
||
icon: Icon(
|
||
Icons.error_outline,
|
||
color: Colors.blue,
|
||
size: 20,
|
||
),
|
||
),
|
||
const SizedBox(),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
if (isClean)
|
||
Column(
|
||
children: [
|
||
CustomButton(
|
||
text: cleanText,
|
||
height: 20,
|
||
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 0),
|
||
textStyle: TextStyle(fontSize: 11, color: Colors.white),
|
||
borderRadius: 10,
|
||
backgroundColor:
|
||
cleanText.contains('清除') ? Colors.red : Colors.green,
|
||
onPressed: onTapClean,
|
||
),
|
||
SizedBox(height: 20),
|
||
],
|
||
),
|
||
const SizedBox(width: 8),
|
||
Expanded(
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.end,
|
||
children: [
|
||
Flexible(
|
||
child: Text(
|
||
text.isNotEmpty ? text : (isEditable ? '请选择' : ''),
|
||
maxLines: 5,
|
||
overflow: TextOverflow.ellipsis,
|
||
textAlign: TextAlign.right,
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
color:
|
||
isEditable
|
||
? (text == '请选择'
|
||
? Colors.black87
|
||
: Colors.black)
|
||
: detailtextColor,
|
||
),
|
||
),
|
||
),
|
||
if (isEditable)
|
||
const Padding(
|
||
padding: EdgeInsets.only(left: 4),
|
||
child: Icon(Icons.chevron_right, size: 20),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 单行可点击选择:
|
||
/// - 可编辑时:标题 + “请选择”提示 + 文本框
|
||
/// - 不可编辑时:标题 + 文本内容
|
||
static Widget selectableLineTitleTextField({
|
||
required String label,
|
||
required bool isEditable,
|
||
required String text,
|
||
VoidCallback? onTap,
|
||
double fontSize = 14,
|
||
bool isClean = false,
|
||
VoidCallback? onTapClean,
|
||
bool isRequired = true,
|
||
String cleanText = '清除',
|
||
TextEditingController? controller,
|
||
}) {
|
||
return InkWell(
|
||
onTap: isEditable ? onTap : null,
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(
|
||
vertical: vertical_inset,
|
||
horizontal: horizontal_inset,
|
||
),
|
||
child: Row(
|
||
crossAxisAlignment: CrossAxisAlignment.center,
|
||
children: [
|
||
// 标题部分
|
||
Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
if (isRequired && isEditable)
|
||
const Text('* ', style: TextStyle(color: Colors.red)),
|
||
Text(
|
||
label,
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
// const SizedBox(width: 5),
|
||
// 右侧 TextField + 清除按钮
|
||
Expanded(
|
||
child: Row(
|
||
children: [
|
||
// 清除按钮
|
||
if (isClean && onTapClean != null)
|
||
Column(
|
||
children: [
|
||
CustomButton(
|
||
text: cleanText,
|
||
height: 20,
|
||
padding: EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 0,
|
||
),
|
||
textStyle: TextStyle(
|
||
fontSize: 11,
|
||
color: Colors.white,
|
||
),
|
||
borderRadius: 10,
|
||
backgroundColor:
|
||
cleanText == '清除' ? Colors.red : Colors.green,
|
||
onPressed: onTapClean,
|
||
),
|
||
SizedBox(height: 20),
|
||
],
|
||
),
|
||
// 输入框
|
||
Expanded(
|
||
child: TextField(
|
||
controller: controller,
|
||
autofocus: false,
|
||
style: TextStyle(fontSize: fontSize),
|
||
decoration: InputDecoration(
|
||
hintText: '请输入',
|
||
isCollapsed: true,
|
||
contentPadding: const EdgeInsets.symmetric(vertical: 8),
|
||
border: InputBorder.none,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 两行垂直布局:
|
||
/// 第一行:可点击选择(带箭头)或仅显示标题
|
||
/// 第二行:多行输入框或多行文本展示
|
||
static Widget twoRowSelectableTitleText({
|
||
required String label, // 第一行标题
|
||
required bool isEditable, // 是否可编辑
|
||
required String text, // 显示内容或提示
|
||
TextEditingController? controller, // 第二行编辑控制器
|
||
VoidCallback? onTap, // 第一行点击回调
|
||
double fontSize = 14, // 字体大小
|
||
double row2Height = 80, // 第二行高度
|
||
bool isRequired = true,
|
||
bool showSelect = true, //是否显示选择
|
||
}) {
|
||
return Container(
|
||
padding: EdgeInsets.symmetric(
|
||
vertical: vertical_inset,
|
||
horizontal: horizontal_inset,
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// 第一行:可点击区域或纯文本标题
|
||
InkWell(
|
||
onTap: isEditable ? onTap : null,
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
if (isRequired && isEditable)
|
||
Text('* ', style: TextStyle(color: Colors.red)),
|
||
Text(
|
||
label,
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
if (showSelect)
|
||
Row(
|
||
children: [
|
||
Text(
|
||
isEditable ? '请选择' : '',
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
color: isEditable ? Colors.black : detailtextColor,
|
||
),
|
||
maxLines: 1,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
if (isEditable) const Icon(Icons.chevron_right),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
Container(
|
||
height: row2Height,
|
||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||
child:
|
||
isEditable
|
||
? TextField(
|
||
autofocus: false,
|
||
controller: controller,
|
||
keyboardType: TextInputType.multiline,
|
||
maxLines: null,
|
||
expands: true,
|
||
style: TextStyle(fontSize: fontSize),
|
||
decoration: InputDecoration(
|
||
hintText: '请输入',
|
||
//contentPadding: EdgeInsets.zero,
|
||
border: InputBorder.none,
|
||
),
|
||
)
|
||
: SingleChildScrollView(
|
||
padding: EdgeInsets.zero,
|
||
child: Text(
|
||
text,
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
color: detailtextColor,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 两行垂直布局:
|
||
/// 标题 + 按钮
|
||
/// 第二行:多行输入框或多行文本展示
|
||
static Widget twoRowButtonTitleText({
|
||
required String label, // 第一行标题
|
||
required bool isEditable, // 是否可编辑
|
||
bool isInput = true, // 是否可输入
|
||
required String text, // 显示内容或提示
|
||
TextEditingController? controller, // 第二行编辑控制器
|
||
required VoidCallback? onTap, // 第一行点击回调
|
||
String buttonText = '选择其他',
|
||
required String hintText,
|
||
double fontSize = 14, // 字体大小
|
||
double row2Height = 80, // 第二行高度
|
||
bool isRequired = true,
|
||
}) {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(
|
||
vertical: vertical_inset,
|
||
horizontal: horizontal_inset,
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// 第一行:标题 + 按钮
|
||
InkWell(
|
||
child: Row(
|
||
children: [
|
||
Flexible(
|
||
fit: FlexFit.loose, // loose 模式下它可以比最大宽度更小
|
||
child: Row(
|
||
children: [
|
||
if (isRequired && isEditable)
|
||
Text('* ', style: TextStyle(color: Colors.red)),
|
||
Flexible(
|
||
child: Text(
|
||
label,
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
maxLines: 1,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
if (isEditable)
|
||
CustomButton(
|
||
text: buttonText,
|
||
height: 30,
|
||
padding: const EdgeInsets.symmetric(
|
||
vertical: 2,
|
||
horizontal: 10,
|
||
),
|
||
textStyle: TextStyle(
|
||
color: Colors.white,
|
||
fontSize: 11,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
backgroundColor: Colors.blue,
|
||
onPressed: onTap,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
|
||
Container(
|
||
height: row2Height,
|
||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||
child:
|
||
(isEditable && isInput)
|
||
? TextField(
|
||
autofocus: false,
|
||
controller: controller,
|
||
keyboardType: TextInputType.multiline,
|
||
maxLines: null,
|
||
expands: true,
|
||
style: TextStyle(fontSize: fontSize),
|
||
decoration: InputDecoration(
|
||
hintText: hintText,
|
||
//contentPadding: EdgeInsets.zero,
|
||
border: InputBorder.none,
|
||
),
|
||
)
|
||
: SingleChildScrollView(
|
||
padding: EdgeInsets.zero,
|
||
child: Text(
|
||
text,
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
color: detailtextColor,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 单行布局:
|
||
/// 标题 + 文字 + 按钮
|
||
static Widget OneRowButtonTitleText({
|
||
required String label, // 标题
|
||
required String text, // 显示内容或提示
|
||
required VoidCallback? onTap, // 第一行点击回调
|
||
double fontSize = 14, // 字体大小
|
||
bool isEdit = true,
|
||
String buttonText = '气体分析详情',
|
||
double horizontalnum = horizontal_inset,
|
||
}) {
|
||
return Container(
|
||
color: Colors.white,
|
||
padding: EdgeInsets.symmetric(
|
||
vertical: vertical_inset,
|
||
horizontal: horizontalnum,
|
||
),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Expanded(
|
||
child: Row(
|
||
children: [
|
||
Text(
|
||
label,
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
SizedBox(width: 15),
|
||
Expanded(
|
||
child: Text(
|
||
text,
|
||
maxLines: 1,
|
||
overflow: TextOverflow.ellipsis,
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
color: detailtextColor,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
if (isEdit)
|
||
CustomButton(
|
||
text: buttonText,
|
||
height: 30,
|
||
padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 10),
|
||
textStyle: TextStyle(
|
||
color: Colors.white,
|
||
fontSize: 11,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
backgroundColor: Colors.blue,
|
||
onPressed: onTap,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 单行布局:
|
||
/// 标题 + 按钮
|
||
static Widget OneRowButtonTitle({
|
||
required String label, // 标题
|
||
required String buttonText, // 按钮文字
|
||
required VoidCallback onTap, // 第一行点击回调
|
||
double fontSize = 14, // 字体大小
|
||
Color btnColor = Colors.blue,
|
||
bool isRequired = false,
|
||
}) {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(
|
||
vertical: vertical_inset,
|
||
horizontal: horizontal_inset,
|
||
),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Text("* ", style: TextStyle(color: Colors.red)),
|
||
Text(
|
||
label,
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
CustomButton(
|
||
text: buttonText,
|
||
height: 30,
|
||
padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5),
|
||
backgroundColor: btnColor,
|
||
onPressed: onTap,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 单行布局:
|
||
/// 标题 + 按钮(挨着)
|
||
static Widget OneRowStartButtonTitle({
|
||
required String label, // 标题
|
||
String buttonText = '气体分析详情', // 按钮文字
|
||
String text = '', // 标题
|
||
required VoidCallback onTap, // 点击回调
|
||
double fontSize = 14, // 字体大小
|
||
Color btnColor = Colors.blue,
|
||
}) {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(
|
||
vertical: vertical_inset,
|
||
horizontal: horizontal_inset,
|
||
),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
label,
|
||
style: TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold),
|
||
),
|
||
CustomButton(
|
||
text: buttonText,
|
||
height: 30,
|
||
padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5),
|
||
backgroundColor: btnColor,
|
||
onPressed: onTap,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 单行布局:
|
||
/// 标题 + 网络图片
|
||
static Widget OneRowImageTitle({
|
||
required String label, // 标题
|
||
required String imgPath, // 图片路径
|
||
double fontSize = 14, // 字体大小
|
||
Color btnColor = Colors.blue,
|
||
bool isRequired = false,
|
||
String text = '',
|
||
void Function(String)? onTapCallBack,
|
||
}) {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(
|
||
vertical: vertical_inset,
|
||
horizontal: horizontal_inset,
|
||
),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Text(
|
||
label,
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
Column(
|
||
children: [
|
||
GestureDetector(
|
||
onTap: () {
|
||
if (onTapCallBack != null)
|
||
onTapCallBack('${ApiService.baseImgPath}$imgPath');
|
||
},
|
||
child:
|
||
imgPath.isNotEmpty
|
||
? Image.network(
|
||
'${ApiService.baseImgPath}${imgPath}',
|
||
width: 80,
|
||
height: 80,
|
||
)
|
||
: SizedBox(),
|
||
),
|
||
if (text.isNotEmpty) Text(text),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 单行布局:
|
||
/// 图片 + 标题 +箭头
|
||
static Widget OneRowImageArrowTitle({
|
||
required String label, // 标题
|
||
required String imgPath, // 图片路径
|
||
double fontSize = 14, // 字体大小
|
||
Color btnColor = Colors.black,
|
||
}) {
|
||
return Card(
|
||
shape: RoundedRectangleBorder(
|
||
// 形状
|
||
borderRadius: BorderRadius.circular(8), // 圆角
|
||
),
|
||
color: Colors.white,
|
||
child: Padding(
|
||
padding: EdgeInsets.all(14),
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.start,
|
||
children: [
|
||
Image.asset(width: 40, height: 40, imgPath),
|
||
SizedBox(width: 20),
|
||
Text(label, style: TextStyle(fontSize: 14)),
|
||
Spacer(),
|
||
Icon(Icons.chevron_right, color: Colors.grey[400]),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 两行垂直布局:
|
||
/// 标题
|
||
/// 第二行:图片
|
||
static Widget twoRowTitleAndImages({
|
||
required String title, // 第一行标题
|
||
required List<dynamic>? imageUrls,
|
||
double row2Height = 80, // 第二行高度
|
||
double fontSize = 14, // 字体大小
|
||
void Function(String)? onTapCallBack,
|
||
bool isRequired = true,
|
||
}) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
|
||
children: [
|
||
// 标题部分
|
||
if (title.isNotEmpty)
|
||
Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
vertical: vertical_inset,
|
||
horizontal: horizontal_inset,
|
||
),
|
||
child: Text(
|
||
title,
|
||
style: TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold),
|
||
),
|
||
),
|
||
|
||
// 图片横向滚动区域
|
||
SizedBox(
|
||
height: 80, // 图片区域固定高度
|
||
child: ListView.builder(
|
||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||
scrollDirection: Axis.horizontal,
|
||
itemCount: imageUrls?.length,
|
||
itemBuilder: (context, index) {
|
||
return Container(
|
||
margin: const EdgeInsets.only(right: 8), // 图片间距
|
||
child: ClipRRect(
|
||
borderRadius: BorderRadius.circular(8),
|
||
child: GestureDetector(
|
||
onTap: () {
|
||
if (onTapCallBack != null)
|
||
onTapCallBack(
|
||
'${ApiService.baseImgPath}${imageUrls![index] ?? ''}',
|
||
);
|
||
},
|
||
child: Image.network(
|
||
'${ApiService.baseImgPath}${imageUrls![index] ?? ''}',
|
||
width: 80,
|
||
// 图片宽度
|
||
height: 80,
|
||
// 图片高度
|
||
fit: BoxFit.fill,
|
||
loadingBuilder: (context, child, loadingProgress) {
|
||
if (loadingProgress == null) return child;
|
||
return Container(
|
||
width: 80,
|
||
height: 80,
|
||
color: Colors.grey[200],
|
||
child: const Center(
|
||
child: CircularProgressIndicator(),
|
||
),
|
||
);
|
||
},
|
||
errorBuilder: (context, error, stackTrace) {
|
||
return Container(
|
||
width: 80,
|
||
height: 80,
|
||
color: Colors.transparent,
|
||
child: SizedBox(),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
/// 多行垂直布局:
|
||
/// 标题+按钮
|
||
/// 编辑框列表,多个编辑框可删除
|
||
static Widget mulRowTitleAndTextField({
|
||
required String label, // 第一行标题
|
||
required bool isEditable, // 是否可编辑
|
||
required String text, // 显示内容或提示
|
||
TextEditingController? controller, // 第二行编辑控制器
|
||
required VoidCallback? onTap, // 第一行点击回调
|
||
required String hintText,
|
||
double fontSize = 14, // 字体大小
|
||
double row2Height = 80, // 第二行高度
|
||
bool isRequired = true,
|
||
}) {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(
|
||
vertical: vertical_inset,
|
||
horizontal: horizontal_inset,
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// 第一行:标题 + 按钮
|
||
InkWell(
|
||
child: Row(
|
||
children: [
|
||
Flexible(
|
||
fit: FlexFit.loose, // loose 模式下它可以比最大宽度更小
|
||
child: Row(
|
||
children: [
|
||
if (isRequired && isEditable)
|
||
Text('* ', style: TextStyle(color: Colors.red)),
|
||
Flexible(
|
||
child: Text(
|
||
label,
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
maxLines: 1,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(width: 8),
|
||
if (isEditable)
|
||
CustomButton(
|
||
text: "选择其他",
|
||
height: 30,
|
||
padding: const EdgeInsets.symmetric(
|
||
vertical: 2,
|
||
horizontal: 5,
|
||
),
|
||
backgroundColor: Colors.blue,
|
||
onPressed: onTap,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
|
||
Container(
|
||
height: row2Height,
|
||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||
child:
|
||
isEditable
|
||
? TextField(
|
||
autofocus: false,
|
||
controller: controller,
|
||
keyboardType: TextInputType.multiline,
|
||
maxLines: null,
|
||
expands: true,
|
||
style: TextStyle(fontSize: fontSize),
|
||
decoration: InputDecoration(
|
||
hintText: hintText,
|
||
//contentPadding: EdgeInsets.zero,
|
||
border: InputBorder.none,
|
||
),
|
||
)
|
||
: SingleChildScrollView(
|
||
padding: EdgeInsets.zero,
|
||
child: Text(
|
||
text,
|
||
style: TextStyle(
|
||
fontSize: fontSize,
|
||
color: detailtextColor,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 多行垂直布局:
|
||
/// 标题、图片、说明、签字信息
|
||
static Widget mulColumnRowTitleAndImages({
|
||
required String title, // 第一行标题
|
||
required List<dynamic>? imageUrls,
|
||
required String text, // 描述
|
||
required List<dynamic>? signUrls,
|
||
|
||
/// 签字
|
||
required List<dynamic>? signTimes,
|
||
|
||
/// 签字时间
|
||
double row2Height = 80, // 第二行高度
|
||
double fontSize = 14, // 字体大小
|
||
void Function(String)? onTapCallBack,
|
||
bool isRequired = true,
|
||
}) {
|
||
return Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
|
||
children: [
|
||
// 标题部分
|
||
Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
vertical: vertical_inset,
|
||
horizontal: horizontal_inset,
|
||
),
|
||
child: Text(
|
||
title,
|
||
style: TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold),
|
||
),
|
||
),
|
||
|
||
// 图片横向滚动区域
|
||
SizedBox(
|
||
height: 80, // 图片区域固定高度
|
||
child: ListView.builder(
|
||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||
scrollDirection: Axis.horizontal,
|
||
itemCount: imageUrls?.length,
|
||
itemBuilder: (context, index) {
|
||
return Container(
|
||
margin: const EdgeInsets.only(right: 8), // 图片间距
|
||
child: ClipRRect(
|
||
borderRadius: BorderRadius.circular(8),
|
||
child: GestureDetector(
|
||
onTap: () {
|
||
if (onTapCallBack != null)
|
||
onTapCallBack(
|
||
'${ApiService.baseImgPath}${imageUrls![index] ?? ''}',
|
||
);
|
||
},
|
||
child: Image.network(
|
||
'${ApiService.baseImgPath}${imageUrls![index] ?? ''}',
|
||
width: 80,
|
||
// 图片宽度
|
||
height: 80,
|
||
// 图片高度
|
||
fit: BoxFit.fill,
|
||
loadingBuilder: (context, child, loadingProgress) {
|
||
if (loadingProgress == null) return child;
|
||
return Container(
|
||
width: 80,
|
||
height: 80,
|
||
color: Colors.grey[200],
|
||
child: const Center(
|
||
child: CircularProgressIndicator(),
|
||
),
|
||
);
|
||
},
|
||
errorBuilder: (context, error, stackTrace) {
|
||
return Container(
|
||
width: 80,
|
||
height: 80,
|
||
color: Colors.transparent,
|
||
child: SizedBox(),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
Row(),
|
||
],
|
||
);
|
||
}
|
||
|
||
/// 两行垂直布局:
|
||
/// 标题
|
||
/// 第二行:多行文本展示
|
||
static Widget twoRowTitleText({
|
||
required String label, // 第一行标题
|
||
required String text, // 显示内容或提示
|
||
double fontSize = 14, // 字体大小
|
||
bool isRequired = true,
|
||
}) {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(
|
||
vertical: vertical_inset,
|
||
horizontal: horizontal_inset,
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
// 第一行:标题
|
||
Text(
|
||
label,
|
||
style: TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold),
|
||
maxLines: 1,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
const SizedBox(height: 8),
|
||
Text(
|
||
text,
|
||
maxLines: 5,
|
||
overflow: TextOverflow.ellipsis,
|
||
style: TextStyle(fontSize: fontSize, color: detailtextColor),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
static Widget itemContainer(
|
||
Widget child, {
|
||
double horizontal = horizontal_inset,
|
||
double vertical = vertical_inset,
|
||
}) {
|
||
return Container(
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
padding: EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical),
|
||
child: child,
|
||
);
|
||
}
|
||
|
||
// static Widget aaa({}){
|
||
// return
|
||
// }
|
||
/// 安全环保检查步骤
|
||
static Widget buildFlowStepItem({
|
||
required List<Map<String, dynamic>> flowList,
|
||
}) {
|
||
final int lastDoneIndex = flowList.lastIndexWhere((e) => e['STATUS'] == 1);
|
||
|
||
return ListView.builder(
|
||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||
itemCount: flowList.length + 1, // +1 用来放标题
|
||
itemBuilder: (context, i) {
|
||
if (i == 0) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||
child: Text(
|
||
'查看流程图',
|
||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||
),
|
||
);
|
||
}
|
||
final idx = i - 1;
|
||
final item = flowList[idx];
|
||
final bool isFirst = idx == 0;
|
||
final bool isLast = idx == flowList.length - 1;
|
||
|
||
// 根据 lastDoneIndex 自动计算“进行中”
|
||
final int status;
|
||
if (idx <= lastDoneIndex) {
|
||
status = 1; // 已完成
|
||
} else if (idx == lastDoneIndex + 1) {
|
||
status = 0; // 进行中
|
||
} else {
|
||
status = -1; // 未到达
|
||
}
|
||
// 依据状态设色
|
||
final Color dotColor =
|
||
status == 1
|
||
? Colors.green
|
||
: (status == 0 ? Colors.blue : Colors.grey);
|
||
final Color textColor =
|
||
status == 1
|
||
? Colors.green
|
||
: (status == 0 ? Colors.blue : Colors.black);
|
||
|
||
return ListTile(
|
||
visualDensity: VisualDensity(vertical: -4),
|
||
|
||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||
leading: Container(
|
||
width: 24,
|
||
alignment: Alignment.center,
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.max,
|
||
children: [
|
||
// 上方线段或占位
|
||
isFirst
|
||
? SizedBox(height: 6 + 5)
|
||
: Expanded(
|
||
child: Container(width: 1, color: Colors.grey[300]),
|
||
),
|
||
// 圆点
|
||
CircleAvatar(radius: 6, backgroundColor: dotColor),
|
||
// 下方线段或占位
|
||
isLast
|
||
? SizedBox(height: 6 + 5)
|
||
: Expanded(
|
||
child: Container(width: 1, color: Colors.grey[300]),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
title: Text(
|
||
item['STEP_NAME'] ?? '',
|
||
style: TextStyle(color: textColor, fontSize: 15),
|
||
),
|
||
subtitle: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
if (item['SIGN_USER'] != null) ...[
|
||
Text(
|
||
item['SIGN_USER'],
|
||
style: TextStyle(color: textColor, fontSize: 13),
|
||
),
|
||
] else if (item['FINISHED_SIGN_USER'] != null) ...[
|
||
Text(
|
||
item['FINISHED_SIGN_USER'],
|
||
style: TextStyle(color: textColor, fontSize: 13),
|
||
),
|
||
] else if (item['ACT_USER_NAME'] != null) ...[
|
||
Text(
|
||
item['ACT_USER_NAME'],
|
||
style: TextStyle(color: textColor, fontSize: 13),
|
||
),
|
||
],
|
||
if (item['ACT_TIME'] != null)
|
||
Text(
|
||
item['ACT_TIME'],
|
||
style: TextStyle(color: textColor, fontSize: 13),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
/// 特殊作业步骤流程图
|
||
static Widget specialBuildFlowStepItem({
|
||
required List<Map<String, dynamic>> flowList,
|
||
}) {
|
||
// status: 1 已完成, 0 当前步骤, -99 未开始, 2 已打回, -1 已跳过
|
||
final int lastDoneIndex = flowList.lastIndexWhere((e) {
|
||
final s = e['status'];
|
||
if (s is int) return s == 1;
|
||
if (s is String) return int.tryParse(s) == 1;
|
||
return false;
|
||
});
|
||
|
||
return ListView.builder(
|
||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||
itemCount: flowList.length + 1, // +1 用来放标题
|
||
itemBuilder: (context, i) {
|
||
if (i == 0) {
|
||
return const Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||
child: Text(
|
||
'查看流程图',
|
||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||
),
|
||
);
|
||
}
|
||
|
||
final idx = i - 1;
|
||
final item = flowList[idx];
|
||
final bool isFirst = idx == 0;
|
||
final bool isLast = idx == flowList.length - 1;
|
||
|
||
// 尝试读取 item 中的 status(支持 int 或可解析为 int 的 String)
|
||
int? statusFromItem;
|
||
final rawStatus = item['status'];
|
||
if (rawStatus is int) {
|
||
statusFromItem = rawStatus;
|
||
} else if (rawStatus is String) {
|
||
statusFromItem = int.tryParse(rawStatus);
|
||
}
|
||
|
||
// 如果 item 中没有 status,则回退到根据 lastDoneIndex 推断的逻辑
|
||
final int status =
|
||
statusFromItem ??
|
||
(idx <= lastDoneIndex
|
||
? 1 // 已完成
|
||
: (idx == lastDoneIndex + 1 ? 0 : -99)); // 0=当前,-99=未开始
|
||
|
||
// 颜色映射:1 -> 绿,0 -> 蓝,其它 -> 灰
|
||
final Color dotColor =
|
||
status == 1
|
||
? Colors.green
|
||
: (status == 0 ? Colors.blue : Colors.grey);
|
||
final Color textColor =
|
||
status == 1
|
||
? Colors.green
|
||
: (status == 0 ? Colors.blue : Colors.black);
|
||
|
||
// 使用新的字段名:stepName, actUserName, actTime
|
||
final String title = (item['stepName'] ?? '').toString();
|
||
final String? user =
|
||
(item['actUserName'] ?? item['ACT_USER_NAME'] ?? item['SIGN_USER'])
|
||
?.toString();
|
||
final String? time = (item['actTime'] ?? item['ACT_TIME'])?.toString();
|
||
|
||
return ListTile(
|
||
visualDensity: VisualDensity(vertical: -4),
|
||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||
leading: SizedBox(
|
||
width: 24,
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.max,
|
||
children: [
|
||
// 上方线段或占位
|
||
if (isFirst)
|
||
const SizedBox(height: 11) // 保持与原来相似的间距
|
||
else
|
||
Expanded(child: Container(width: 1, color: Colors.grey[300])),
|
||
// 圆点
|
||
CircleAvatar(radius: 6, backgroundColor: dotColor),
|
||
// 下方线段或占位
|
||
if (isLast)
|
||
const SizedBox(height: 11)
|
||
else
|
||
Expanded(child: Container(width: 1, color: Colors.grey[300])),
|
||
],
|
||
),
|
||
),
|
||
title: Text(title, style: TextStyle(color: textColor, fontSize: 15)),
|
||
subtitle: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
if (user != null && user.isNotEmpty)
|
||
Text(user, style: TextStyle(color: textColor, fontSize: 13)),
|
||
if (rawStatus == -1)
|
||
Text('已跳过', style: TextStyle(color: textColor, fontSize: 13)),
|
||
if (time != null && time.isNotEmpty)
|
||
Text(time, style: TextStyle(color: textColor, fontSize: 13)),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|
||
}
|