| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- import 'package:flutter/material.dart';
- import '../../core/i18n/app_localizations.dart';
- import '../../core/theme/app_colors.dart';
- import '../models/approval_status.dart';
- class ApprovalTimeline extends StatelessWidget {
- final List<ApprovalRecord> records;
- final List<String> chain;
- final String currentApproverId;
- const ApprovalTimeline({
- super.key,
- required this.records,
- required this.chain,
- this.currentApproverId = '',
- });
- @override
- Widget build(BuildContext context) {
- final l10n = AppLocalizations.of(context);
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- l10n.get('approvalProgress'),
- style: TextStyle(
- fontSize: 13,
- fontWeight: FontWeight.w600,
- color: AppColors.textPrimary,
- ),
- ),
- const SizedBox(height: 12),
- ...chain.asMap().entries.map((entry) {
- final idx = entry.key;
- final approverId = entry.value;
- final record = records
- .where((r) => r.approverId == approverId)
- .firstOrNull;
- final isCurrent = approverId == currentApproverId;
- return _buildNode(
- l10n,
- idx,
- chain.length,
- approverId,
- record,
- isCurrent,
- );
- }),
- ],
- );
- }
- Widget _buildNode(
- AppLocalizations l10n,
- int index,
- int total,
- String approverId,
- ApprovalRecord? record,
- bool isCurrent,
- ) {
- final isDone = record != null && record.action == 'approve';
- final isRejected = record != null && record.action == 'reject';
- final isLast = index == total - 1;
- return IntrinsicHeight(
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Column(
- children: [
- Container(
- width: 22,
- height: 22,
- decoration: BoxDecoration(
- color: isRejected
- ? AppColors.danger
- : isDone
- ? AppColors.success
- : isCurrent
- ? AppColors.primary
- : const Color(0xFFDDDDDD),
- shape: BoxShape.circle,
- ),
- child: Center(
- child: isRejected
- ? const Icon(Icons.close, size: 12, color: Colors.white)
- : isDone
- ? const Icon(Icons.check, size: 12, color: Colors.white)
- : isCurrent
- ? const Text(
- '●',
- style: TextStyle(color: Colors.white, fontSize: 10),
- )
- : null,
- ),
- ),
- if (!isLast)
- Container(
- width: 1.5,
- height: 36,
- color: isDone ? AppColors.success : const Color(0xFFDDDDDD),
- ),
- ],
- ),
- const SizedBox(width: 10),
- Expanded(
- child: Padding(
- padding: EdgeInsets.only(bottom: isLast ? 0 : 12),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- record?.approverName ?? '审批人$approverId',
- style: TextStyle(
- fontSize: 13,
- fontWeight: FontWeight.w500,
- color: isRejected
- ? AppColors.danger
- : isCurrent
- ? AppColors.primary
- : AppColors.textPrimary,
- ),
- ),
- if (record != null && record.action != 'pending') ...[
- const SizedBox(height: 2),
- Text(
- '${record.action == 'approve' ? l10n.get('approved') : l10n.get('rejected')} · ${record.approvalTime.toString().substring(0, 16)}',
- style: const TextStyle(
- fontSize: 11,
- color: AppColors.textPlaceholder,
- ),
- ),
- if (record.opinion.isNotEmpty)
- Padding(
- padding: const EdgeInsets.only(top: 2),
- child: Text(
- '${l10n.get('opinion')}${record.opinion}',
- style: const TextStyle(
- fontSize: 11,
- color: AppColors.textSecondary,
- ),
- ),
- ),
- ] else if (isCurrent) ...[
- const SizedBox(height: 2),
- Text(
- l10n.get('currentNode'),
- style: TextStyle(fontSize: 11, color: AppColors.primary),
- ),
- ] else ...[
- const SizedBox(height: 2),
- Text(
- l10n.get('waitHandle'),
- style: TextStyle(
- fontSize: 11,
- color: AppColors.textPlaceholder,
- ),
- ),
- ],
- ],
- ),
- ),
- ),
- ],
- ),
- );
- }
- }
|