refactor(transfer): 移除发起转让页的售价字段 — 线下柜台签合同交易
APP 端树转让仅完成系统层面的所有权变更 + 算力调整(撤回旧用户算力、 新增至新用户及其团队),价格由用户线下到柜台签署合同确定。 变更详情: - transfer_initiate_page.dart: · 移除 _priceController、_feeRate、费用相关 getter · 移除"每棵售价"输入框和费用计算卡片 UI · 移除 _feeRow 辅助方法 · 更新说明文案:强调线下柜台签合同 · 更新确认弹窗:仅显示买方账号和转让棵数 · API 调用改用 treeCount 替代 pricePerTree - transfer_service.dart: · createTransfer() 参数从 pricePerTree(double) 改为 treeCount(int) · 请求体字段同步调整 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0e34896d0b
commit
6efa74aded
|
|
@ -236,21 +236,21 @@ class TransferService {
|
||||||
/// 发起转让
|
/// 发起转让
|
||||||
///
|
///
|
||||||
/// [buyerAccountSequence] 买方账号
|
/// [buyerAccountSequence] 买方账号
|
||||||
/// [plantingOrderNos] 要转让的认种订单号列表
|
/// [plantingOrderNos] 要转让的认种订单号列表(空列表时由后端自动选择)
|
||||||
/// [pricePerTree] 每棵售价
|
/// [treeCount] 转让棵数
|
||||||
Future<TransferOrder> createTransfer({
|
Future<TransferOrder> createTransfer({
|
||||||
required String buyerAccountSequence,
|
required String buyerAccountSequence,
|
||||||
required List<String> plantingOrderNos,
|
required List<String> plantingOrderNos,
|
||||||
required double pricePerTree,
|
required int treeCount,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
debugPrint('[TransferService] 发起转让: buyer=$buyerAccountSequence, trees=${plantingOrderNos.length}');
|
debugPrint('[TransferService] 发起转让: buyer=$buyerAccountSequence, treeCount=$treeCount');
|
||||||
final response = await _apiClient.post(
|
final response = await _apiClient.post(
|
||||||
'/transfers',
|
'/transfers',
|
||||||
data: {
|
data: {
|
||||||
'buyerAccountSequence': buyerAccountSequence,
|
'buyerAccountSequence': buyerAccountSequence,
|
||||||
'plantingOrderNos': plantingOrderNos,
|
'plantingOrderNos': plantingOrderNos,
|
||||||
'pricePerTree': pricePerTree.toString(),
|
'treeCount': treeCount,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ import '../../../../routes/route_paths.dart';
|
||||||
//
|
//
|
||||||
// 用户发起树转让的表单页面:
|
// 用户发起树转让的表单页面:
|
||||||
// - 输入买方账号
|
// - 输入买方账号
|
||||||
// - 输入每棵售价
|
// - 输入转让棵数
|
||||||
// - 自动计算手续费(5%)和卖方到账金额
|
|
||||||
// - 确认发起转让
|
// - 确认发起转让
|
||||||
|
// 注:价格线下柜台签合同交易,APP 仅完成系统层面所有权 + 算力转移
|
||||||
//
|
//
|
||||||
// === 回滚方式 ===
|
// === 回滚方式 ===
|
||||||
// 删除 features/transfer/ 目录
|
// 删除 features/transfer/ 目录
|
||||||
|
|
@ -28,27 +28,18 @@ class TransferInitiatePage extends ConsumerStatefulWidget {
|
||||||
|
|
||||||
class _TransferInitiatePageState extends ConsumerState<TransferInitiatePage> {
|
class _TransferInitiatePageState extends ConsumerState<TransferInitiatePage> {
|
||||||
final _buyerController = TextEditingController();
|
final _buyerController = TextEditingController();
|
||||||
final _priceController = TextEditingController();
|
|
||||||
final _treeCountController = TextEditingController(text: '1');
|
final _treeCountController = TextEditingController(text: '1');
|
||||||
bool _isSubmitting = false;
|
bool _isSubmitting = false;
|
||||||
String? _errorMessage;
|
String? _errorMessage;
|
||||||
|
|
||||||
// 平台手续费率
|
|
||||||
static const double _feeRate = 0.05;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_buyerController.dispose();
|
_buyerController.dispose();
|
||||||
_priceController.dispose();
|
|
||||||
_treeCountController.dispose();
|
_treeCountController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
double get _pricePerTree => double.tryParse(_priceController.text) ?? 0;
|
|
||||||
int get _treeCount => int.tryParse(_treeCountController.text) ?? 1;
|
int get _treeCount => int.tryParse(_treeCountController.text) ?? 1;
|
||||||
double get _totalPrice => _pricePerTree * _treeCount;
|
|
||||||
double get _feeAmount => _totalPrice * _feeRate;
|
|
||||||
double get _sellerReceive => _totalPrice - _feeAmount;
|
|
||||||
|
|
||||||
Future<void> _handleSubmit() async {
|
Future<void> _handleSubmit() async {
|
||||||
final buyer = _buyerController.text.trim();
|
final buyer = _buyerController.text.trim();
|
||||||
|
|
@ -56,10 +47,6 @@ class _TransferInitiatePageState extends ConsumerState<TransferInitiatePage> {
|
||||||
setState(() => _errorMessage = '请输入买方账号');
|
setState(() => _errorMessage = '请输入买方账号');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_pricePerTree <= 0) {
|
|
||||||
setState(() => _errorMessage = '请输入有效的每棵售价');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_treeCount <= 0) {
|
if (_treeCount <= 0) {
|
||||||
setState(() => _errorMessage = '请输入有效的转让棵数');
|
setState(() => _errorMessage = '请输入有效的转让棵数');
|
||||||
return;
|
return;
|
||||||
|
|
@ -77,12 +64,9 @@ class _TransferInitiatePageState extends ConsumerState<TransferInitiatePage> {
|
||||||
),
|
),
|
||||||
content: Text(
|
content: Text(
|
||||||
'买方账号: $buyer\n'
|
'买方账号: $buyer\n'
|
||||||
'转让棵数: $_treeCount 棵\n'
|
'转让棵数: $_treeCount 棵\n\n'
|
||||||
'每棵售价: ${_pricePerTree.toStringAsFixed(0)} USDT\n'
|
'确认后系统将完成树所有权变更及算力调整。\n'
|
||||||
'总价: ${_totalPrice.toStringAsFixed(0)} USDT\n'
|
'交易价格请线下柜台签署合同确定。',
|
||||||
'手续费: ${_feeAmount.toStringAsFixed(2)} USDT (${(_feeRate * 100).toStringAsFixed(0)}%)\n'
|
|
||||||
'您将收到: ${_sellerReceive.toStringAsFixed(2)} USDT\n\n'
|
|
||||||
'转让发起后需要卖方确认,确认后 Saga 流程将自动执行。',
|
|
||||||
style: const TextStyle(color: Color(0xFF5D4037), fontSize: 14, height: 1.6),
|
style: const TextStyle(color: Color(0xFF5D4037), fontSize: 14, height: 1.6),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
|
@ -115,7 +99,7 @@ class _TransferInitiatePageState extends ConsumerState<TransferInitiatePage> {
|
||||||
final order = await service.createTransfer(
|
final order = await service.createTransfer(
|
||||||
buyerAccountSequence: buyer,
|
buyerAccountSequence: buyer,
|
||||||
plantingOrderNos: [], // 由后端根据棵数自动选择
|
plantingOrderNos: [], // 由后端根据棵数自动选择
|
||||||
pricePerTree: _pricePerTree,
|
treeCount: _treeCount,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|
@ -178,7 +162,7 @@ class _TransferInitiatePageState extends ConsumerState<TransferInitiatePage> {
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'转让已认种的果树所有权。发起后需卖方确认,系统将自动完成资金冻结、所有权变更、算力调整等流程。',
|
'转让已认种的果树所有权。系统将自动完成所有权变更及算力调整,交易价格请线下柜台签署合同确定。',
|
||||||
style: TextStyle(fontSize: 13, color: Color(0xFF8B5A2B)),
|
style: TextStyle(fontSize: 13, color: Color(0xFF8B5A2B)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -207,48 +191,8 @@ class _TransferInitiatePageState extends ConsumerState<TransferInitiatePage> {
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
onChanged: (_) => setState(() {}),
|
onChanged: (_) => setState(() {}),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
|
||||||
|
|
||||||
// 每棵售价
|
|
||||||
_buildLabel('每棵售价 (USDT)'),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_buildTextField(
|
|
||||||
controller: _priceController,
|
|
||||||
hintText: '输入每棵售价',
|
|
||||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
|
||||||
inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'[\d.]'))],
|
|
||||||
onChanged: (_) => setState(() {}),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
// 费用计算卡片
|
|
||||||
if (_pricePerTree > 0) ...[
|
|
||||||
Container(
|
|
||||||
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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
_feeRow('总价', '${_totalPrice.toStringAsFixed(0)} USDT'),
|
|
||||||
_feeRow('手续费 (${(_feeRate * 100).toStringAsFixed(0)}%)', '${_feeAmount.toStringAsFixed(2)} USDT'),
|
|
||||||
const Divider(color: Color(0xFFEAE0C8)),
|
|
||||||
_feeRow('您将收到', '${_sellerReceive.toStringAsFixed(2)} USDT',
|
|
||||||
valueColor: const Color(0xFFD4AF37)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
],
|
|
||||||
|
|
||||||
// 错误信息
|
// 错误信息
|
||||||
if (_errorMessage != null) ...[
|
if (_errorMessage != null) ...[
|
||||||
Container(
|
Container(
|
||||||
|
|
@ -429,24 +373,4 @@ class _TransferInitiatePageState extends ConsumerState<TransferInitiatePage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _feeRow(String label, String value, {Color? valueColor}) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(label, style: const TextStyle(fontSize: 13, color: Color(0xFF745D43))),
|
|
||||||
Text(
|
|
||||||
value,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: valueColor ?? const Color(0xFF5D4037),
|
|
||||||
fontFamily: 'Inter',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue