feat(pre-planting): Mobile App 预种持仓页面完整实现
[2026-02-17] 预种持仓页面 (pre_planting_position_page.dart) 完整功能: - 持仓概览卡片:累计份数、待合并份数、已合成树数(三列统计) - 合并进度条:当前 N/5 份进度可视化 + 文字提示 - 省市信息显示(已锁定的省市) - Tab 切换:预种订单 / 合并记录 - 预种订单列表:订单号、份数、金额、状态标签(待支付/已支付/已合并) - 合并记录列表:合并号、树数、来源订单数、合同状态标签 - 待签约合并记录高亮显示 + "点击签署合同"提示 - 点击合并记录跳转到合并详情页 - 顶部快捷购买按钮 - 下拉刷新、错误重试、空状态提示 UI 风格与全局一致:渐变背景、金色主色调、卡片阴影 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8a4508fe0d
commit
99f5070552
|
|
@ -1,16 +1,825 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../../../../core/di/injection_container.dart';
|
||||
import '../../../../core/services/pre_planting_service.dart';
|
||||
import '../../../../routes/route_paths.dart';
|
||||
|
||||
/// [2026-02-17] 预种持仓页面(占位 - 待完整实现)
|
||||
// ============================================
|
||||
// [2026-02-17] 预种持仓页面
|
||||
// ============================================
|
||||
//
|
||||
// 显示用户的预种计划持仓信息,包括:
|
||||
// - 持仓概览卡片:累计份数、待合并份数、已合成树数
|
||||
// - 合并进度条:当前 N/5 份的进度可视化
|
||||
// - 订单列表:所有预种订单记录(按时间倒序)
|
||||
// - 合并记录列表:已完成合并的树记录(可点击查看详情/签约)
|
||||
//
|
||||
// === 页面结构 ===
|
||||
// ┌─ Header(顶部导航)
|
||||
// ├─ Position Summary(持仓概览 + 进度条)
|
||||
// ├─ Tab: 预种订单 / 合并记录
|
||||
// └─ ListView(订单或合并记录卡片列表)
|
||||
//
|
||||
// === 与购买页面的关系 ===
|
||||
// 本页面纯展示,不含购买操作。
|
||||
// 购买入口在 Profile 页面或本页顶部按钮跳转到 PrePlantingPurchasePage。
|
||||
|
||||
/// 预种持仓页面
|
||||
///
|
||||
/// 功能:显示累计份数、合并进度条(N/5)、已合成树数、订单列表
|
||||
class PrePlantingPositionPage extends StatelessWidget {
|
||||
/// 展示用户的预种持仓概览、合并进度、订单列表和合并记录。
|
||||
class PrePlantingPositionPage extends ConsumerStatefulWidget {
|
||||
const PrePlantingPositionPage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<PrePlantingPositionPage> createState() =>
|
||||
_PrePlantingPositionPageState();
|
||||
}
|
||||
|
||||
class _PrePlantingPositionPageState
|
||||
extends ConsumerState<PrePlantingPositionPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
// === 常量 ===
|
||||
static const int _portionsPerTree = 5;
|
||||
|
||||
// === 状态 ===
|
||||
|
||||
/// 持仓信息
|
||||
PrePlantingPosition? _position;
|
||||
|
||||
/// 预种订单列表
|
||||
List<PrePlantingOrder> _orders = [];
|
||||
|
||||
/// 合并记录列表
|
||||
List<PrePlantingMerge> _merges = [];
|
||||
|
||||
/// 是否正在加载
|
||||
bool _isLoading = true;
|
||||
|
||||
/// 加载错误信息
|
||||
String? _errorMessage;
|
||||
|
||||
/// Tab 控制器(订单 / 合并记录)
|
||||
late TabController _tabController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: 2, vsync: this);
|
||||
_loadData();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 数据加载
|
||||
// ============================================
|
||||
|
||||
/// 并行加载持仓、订单、合并记录
|
||||
Future<void> _loadData() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_errorMessage = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final service = ref.read(prePlantingServiceProvider);
|
||||
|
||||
final results = await Future.wait([
|
||||
service.getMyPosition().catchError((_) => PrePlantingPosition(
|
||||
totalPortions: 0,
|
||||
availablePortions: 0,
|
||||
mergedPortions: 0,
|
||||
totalTreesMerged: 0,
|
||||
)),
|
||||
service.getMyOrders(),
|
||||
service.getMyMerges(),
|
||||
]);
|
||||
|
||||
setState(() {
|
||||
_position = results[0] as PrePlantingPosition;
|
||||
_orders = results[1] as List<PrePlantingOrder>;
|
||||
_merges = results[2] as List<PrePlantingMerge>;
|
||||
_isLoading = false;
|
||||
});
|
||||
} catch (e) {
|
||||
debugPrint('[PrePlantingPosition] 加载数据失败: $e');
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_errorMessage = '加载数据失败,请重试';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 返回上一页
|
||||
void _goBack() {
|
||||
context.pop();
|
||||
}
|
||||
|
||||
/// 跳转到购买页面
|
||||
void _goToPurchase() {
|
||||
context.push(RoutePaths.prePlantingPurchase);
|
||||
}
|
||||
|
||||
/// 跳转到合并详情页
|
||||
void _goToMergeDetail(String mergeNo) {
|
||||
context.push('${RoutePaths.prePlantingMergeDetail}/$mergeNo');
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// UI 构建
|
||||
// ============================================
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('预种持仓')),
|
||||
body: const Center(child: Text('预种持仓页 - 开发中')),
|
||||
body: Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFFFFF7E6), Color(0xFFEAE0C8)],
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
_buildHeader(),
|
||||
Expanded(
|
||||
child: _isLoading
|
||||
? _buildLoadingState()
|
||||
: _errorMessage != null
|
||||
? _buildErrorState()
|
||||
: _buildContent(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 顶部导航栏
|
||||
Widget _buildHeader() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFFF7E6).withValues(alpha: 0.8),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: _goBack,
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
alignment: Alignment.center,
|
||||
child: const Icon(
|
||||
Icons.arrow_back_ios,
|
||||
color: Color(0xFFD4AF37),
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
GestureDetector(
|
||||
onTap: _goBack,
|
||||
child: const Text(
|
||||
'返回',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Inter',
|
||||
height: 1.5,
|
||||
color: Color(0xFFD4AF37),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 42),
|
||||
const Expanded(
|
||||
child: Text(
|
||||
'预种持仓',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontFamily: 'Inter',
|
||||
fontWeight: FontWeight.w700,
|
||||
height: 1.25,
|
||||
letterSpacing: -0.27,
|
||||
color: Color(0xFF5D4037),
|
||||
),
|
||||
),
|
||||
),
|
||||
// 购买按钮
|
||||
GestureDetector(
|
||||
onTap: _goToPurchase,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFD4AF37),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: const Text(
|
||||
'购买',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 加载中状态
|
||||
Widget _buildLoadingState() {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Color(0xFFD4AF37),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 错误状态
|
||||
Widget _buildErrorState() {
|
||||
return Center(
|
||||
child: GestureDetector(
|
||||
onTap: _loadData,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.error_outline, color: Color(0xFFE65100), size: 48),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
_errorMessage!,
|
||||
style: const TextStyle(fontSize: 16, color: Color(0xFFE65100)),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'点击重试',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Color(0xFFD4AF37),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 主内容区域
|
||||
Widget _buildContent() {
|
||||
return RefreshIndicator(
|
||||
onRefresh: _loadData,
|
||||
color: const Color(0xFFD4AF37),
|
||||
child: Column(
|
||||
children: [
|
||||
// 持仓概览 + 进度条
|
||||
_buildPositionSummary(),
|
||||
const SizedBox(height: 8),
|
||||
// Tab 切换
|
||||
_buildTabBar(),
|
||||
// Tab 内容
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
_buildOrdersList(),
|
||||
_buildMergesList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 持仓概览卡片
|
||||
Widget _buildPositionSummary() {
|
||||
final pos = _position!;
|
||||
final available = pos.availablePortions;
|
||||
final progress = available / _portionsPerTree;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0x99FFFFFF),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x1A000000),
|
||||
blurRadius: 6,
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
BoxShadow(
|
||||
color: Color(0x1A000000),
|
||||
blurRadius: 4,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 三列统计数据
|
||||
Row(
|
||||
children: [
|
||||
_buildStatItem('累计份数', '${pos.totalPortions}'),
|
||||
_buildStatDivider(),
|
||||
_buildStatItem('待合并', '$available'),
|
||||
_buildStatDivider(),
|
||||
_buildStatItem('已合成树', '${pos.totalTreesMerged}'),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// 合并进度条
|
||||
const Text(
|
||||
'合并进度',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontFamily: 'Inter',
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF745D43),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: LinearProgressIndicator(
|
||||
value: progress,
|
||||
minHeight: 10,
|
||||
backgroundColor: const Color(0xFFEAE0C8),
|
||||
valueColor:
|
||||
const AlwaysStoppedAnimation<Color>(Color(0xFFD4AF37)),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'$available / $_portionsPerTree 份',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF5D4037),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
available == 0
|
||||
? '开始购买即可积累'
|
||||
: '还需 ${_portionsPerTree - available} 份合成 1 棵树',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0xFF745D43),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// 省市信息(如有)
|
||||
if (pos.hasProvinceCity) ...[
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.location_on_outlined,
|
||||
color: Color(0xFF745D43),
|
||||
size: 16,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'${pos.provinceName ?? pos.provinceCode} · ${pos.cityName ?? pos.cityCode}',
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
color: Color(0xFF745D43),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 统计项
|
||||
Widget _buildStatItem(String label, String value) {
|
||||
return Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontFamily: 'Inter',
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Color(0xFF5D4037),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0xFF745D43),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 统计项分隔线
|
||||
Widget _buildStatDivider() {
|
||||
return Container(
|
||||
width: 1,
|
||||
height: 40,
|
||||
color: const Color(0x338B5A2B),
|
||||
);
|
||||
}
|
||||
|
||||
/// Tab 栏
|
||||
Widget _buildTabBar() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(color: Color(0x338B5A2B), width: 1),
|
||||
),
|
||||
),
|
||||
child: TabBar(
|
||||
controller: _tabController,
|
||||
labelColor: const Color(0xFFD4AF37),
|
||||
unselectedLabelColor: const Color(0xFF745D43),
|
||||
indicatorColor: const Color(0xFFD4AF37),
|
||||
indicatorWeight: 2,
|
||||
labelStyle: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: 'Inter',
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
unselectedLabelStyle: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: 'Inter',
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
tabs: [
|
||||
Tab(text: '预种订单 (${_orders.length})'),
|
||||
Tab(text: '合并记录 (${_merges.length})'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 订单列表
|
||||
Widget _buildOrdersList() {
|
||||
if (_orders.isEmpty) {
|
||||
return _buildEmptyState('暂无预种订单', '购买预种份额后,订单将在此显示');
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: _orders.length,
|
||||
itemBuilder: (context, index) => _buildOrderCard(_orders[index]),
|
||||
);
|
||||
}
|
||||
|
||||
/// 合并记录列表
|
||||
Widget _buildMergesList() {
|
||||
if (_merges.isEmpty) {
|
||||
return _buildEmptyState('暂无合并记录', '累计 5 份后将自动合成 1 棵树');
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: _merges.length,
|
||||
itemBuilder: (context, index) => _buildMergeCard(_merges[index]),
|
||||
);
|
||||
}
|
||||
|
||||
/// 空状态
|
||||
Widget _buildEmptyState(String title, String subtitle) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.inbox_outlined,
|
||||
color: const Color(0xFF745D43).withValues(alpha: 0.4),
|
||||
size: 48,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF5D4037),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: const Color(0xFF745D43).withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 预种订单卡片
|
||||
Widget _buildOrderCard(PrePlantingOrder order) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0x99FFFFFF),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x0D000000),
|
||||
blurRadius: 4,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 顶部:订单号 + 状态标签
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
order.orderNo,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontFamily: 'Inter',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF5D4037),
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_buildStatusLabel(order.status),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// 中部:份数 + 金额
|
||||
Row(
|
||||
children: [
|
||||
_buildInfoChip(
|
||||
Icons.layers_outlined,
|
||||
'${order.portionCount} 份',
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
_buildInfoChip(
|
||||
Icons.monetization_on_outlined,
|
||||
'${order.totalAmount.toInt()} USDT',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// 底部:时间
|
||||
Text(
|
||||
_formatDateTime(order.paidAt ?? order.createdAt),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: const Color(0xFF745D43).withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 合并记录卡片
|
||||
Widget _buildMergeCard(PrePlantingMerge merge) {
|
||||
return GestureDetector(
|
||||
onTap: () => _goToMergeDetail(merge.mergeNo),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0x99FFFFFF),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: merge.contractStatus == PrePlantingContractStatus.pending
|
||||
? Border.all(
|
||||
color: const Color(0xFFD4AF37).withValues(alpha: 0.5),
|
||||
width: 1,
|
||||
)
|
||||
: null,
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x0D000000),
|
||||
blurRadius: 4,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 顶部:合并号 + 合同状态
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.park, color: Color(0xFFD4AF37), size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
merge.mergeNo,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontFamily: 'Inter',
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF5D4037),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
_buildContractStatusLabel(merge.contractStatus),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// 中部:信息
|
||||
Row(
|
||||
children: [
|
||||
_buildInfoChip(Icons.park_outlined, '${merge.treeCount} 棵树'),
|
||||
const SizedBox(width: 16),
|
||||
_buildInfoChip(
|
||||
Icons.layers_outlined,
|
||||
'${merge.sourceOrderNos.length} 份合并',
|
||||
),
|
||||
],
|
||||
),
|
||||
// 待签约提示
|
||||
if (merge.contractStatus == PrePlantingContractStatus.pending) ...[
|
||||
const SizedBox(height: 10),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFD4AF37).withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.edit_document, color: Color(0xFFD4AF37), size: 16),
|
||||
SizedBox(width: 6),
|
||||
Text(
|
||||
'点击签署合同',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFFD4AF37),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 10),
|
||||
// 底部:时间
|
||||
Text(
|
||||
'合并时间:${_formatDateTime(merge.mergedAt)}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: const Color(0xFF745D43).withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 订单状态标签
|
||||
Widget _buildStatusLabel(PrePlantingOrderStatus status) {
|
||||
late String text;
|
||||
late Color bgColor;
|
||||
late Color textColor;
|
||||
|
||||
switch (status) {
|
||||
case PrePlantingOrderStatus.created:
|
||||
text = '待支付';
|
||||
bgColor = const Color(0xFFFFF3E0);
|
||||
textColor = const Color(0xFFE65100);
|
||||
break;
|
||||
case PrePlantingOrderStatus.paid:
|
||||
text = '已支付';
|
||||
bgColor = const Color(0xFFE8F5E9);
|
||||
textColor = const Color(0xFF2E7D32);
|
||||
break;
|
||||
case PrePlantingOrderStatus.merged:
|
||||
text = '已合并';
|
||||
bgColor = const Color(0xFFD4AF37).withValues(alpha: 0.15);
|
||||
textColor = const Color(0xFFD4AF37);
|
||||
break;
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 合同状态标签
|
||||
Widget _buildContractStatusLabel(PrePlantingContractStatus status) {
|
||||
late String text;
|
||||
late Color bgColor;
|
||||
late Color textColor;
|
||||
|
||||
switch (status) {
|
||||
case PrePlantingContractStatus.pending:
|
||||
text = '待签约';
|
||||
bgColor = const Color(0xFFFFF3E0);
|
||||
textColor = const Color(0xFFE65100);
|
||||
break;
|
||||
case PrePlantingContractStatus.signed:
|
||||
text = '已签约';
|
||||
bgColor = const Color(0xFFE8F5E9);
|
||||
textColor = const Color(0xFF2E7D32);
|
||||
break;
|
||||
case PrePlantingContractStatus.expired:
|
||||
text = '已过期';
|
||||
bgColor = const Color(0xFFEEEEEE);
|
||||
textColor = const Color(0xFF757575);
|
||||
break;
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 信息小标签(图标 + 文字)
|
||||
Widget _buildInfoChip(IconData icon, String text) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, color: const Color(0xFF745D43), size: 16),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
color: Color(0xFF745D43),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 格式化日期时间
|
||||
String _formatDateTime(DateTime dt) {
|
||||
return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-'
|
||||
'${dt.day.toString().padLeft(2, '0')} '
|
||||
'${dt.hour.toString().padLeft(2, '0')}:'
|
||||
'${dt.minute.toString().padLeft(2, '0')}';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue