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/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});
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('预种持仓')),
|
body: Container(
|
||||||
body: const Center(child: Text('预种持仓页 - 开发中')),
|
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