feat(c2c): 修改C2C交易为积分值并添加绿积分支付方式

- C2C交易的商品从积分股改为积分值
- 添加绿积分作为默认且不可取消的支付方式
- 添加1.0系统ID输入框(绿积分支付必填)
- 支持多种支付方式同时选择
- 更新交易提示说明

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-19 23:07:35 -08:00
parent 9333cd81c3
commit d31bfc4221
3 changed files with 196 additions and 103 deletions

View File

@ -102,6 +102,7 @@ abstract class TradingRemoteDataSource {
String? paymentAccount, String? paymentAccount,
String? paymentQrCode, String? paymentQrCode,
String? paymentRealName, String? paymentRealName,
String? systemId, // 1.0ID绿
String? remark, String? remark,
}); });
@ -444,6 +445,7 @@ class TradingRemoteDataSourceImpl implements TradingRemoteDataSource {
String? paymentAccount, String? paymentAccount,
String? paymentQrCode, String? paymentQrCode,
String? paymentRealName, String? paymentRealName,
String? systemId, // 1.0ID绿
String? remark, String? remark,
}) async { }) async {
try { try {
@ -459,6 +461,7 @@ class TradingRemoteDataSourceImpl implements TradingRemoteDataSource {
if (paymentAccount != null) data['paymentAccount'] = paymentAccount; if (paymentAccount != null) data['paymentAccount'] = paymentAccount;
if (paymentQrCode != null) data['paymentQrCode'] = paymentQrCode; if (paymentQrCode != null) data['paymentQrCode'] = paymentQrCode;
if (paymentRealName != null) data['paymentRealName'] = paymentRealName; if (paymentRealName != null) data['paymentRealName'] = paymentRealName;
if (systemId != null && systemId.isNotEmpty) data['systemId'] = systemId;
if (remark != null && remark.isNotEmpty) data['remark'] = remark; if (remark != null && remark.isNotEmpty) data['remark'] = remark;
final response = await client.post( final response = await client.post(

View File

@ -29,9 +29,10 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
final _remarkController = TextEditingController(); final _remarkController = TextEditingController();
// //
String _paymentMethod = 'ALIPAY'; // ALIPAY, WECHAT, BANK Set<String> _selectedPaymentMethods = {'GREEN_POINTS'}; // 绿
final _paymentAccountController = TextEditingController(); final _paymentAccountController = TextEditingController();
final _paymentRealNameController = TextEditingController(); final _paymentRealNameController = TextEditingController();
final _systemIdController = TextEditingController(); // 1.0ID
@override @override
void dispose() { void dispose() {
@ -40,6 +41,7 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
_remarkController.dispose(); _remarkController.dispose();
_paymentAccountController.dispose(); _paymentAccountController.dispose();
_paymentRealNameController.dispose(); _paymentRealNameController.dispose();
_systemIdController.dispose();
super.dispose(); super.dispose();
} }
@ -194,8 +196,8 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
} }
Widget _buildBalanceCard(String availableShares, String availableCash) { Widget _buildBalanceCard(String availableShares, String availableCash) {
final isBuy = _selectedType == 0; // C2C交易的是积分值广绿
// 广
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 16), margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
@ -206,16 +208,16 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( const Text(
isBuy ? '可用积分值' : '可用积分股', '可用积分值',
style: const TextStyle(fontSize: 14, color: _grayText), style: TextStyle(fontSize: 14, color: _grayText),
), ),
Text( Text(
isBuy ? formatAmount(availableCash) : formatAmount(availableShares), formatAmount(availableCash),
style: TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: isBuy ? _green : _orange, color: _green,
), ),
), ),
], ],
@ -271,7 +273,7 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: _orange, width: 2), borderSide: const BorderSide(color: _orange, width: 2),
), ),
suffixText: '积分值/股', suffixText: '元/积分值',
suffixStyle: const TextStyle(color: _grayText, fontSize: 14), suffixStyle: const TextStyle(color: _grayText, fontSize: 14),
), ),
onChanged: (_) => setState(() {}), onChanged: (_) => setState(() {}),
@ -286,8 +288,7 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
String availableCash, String availableCash,
String currentPrice, String currentPrice,
) { ) {
final isBuy = _selectedType == 0; // C2C交易的是积分值
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 16), margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
@ -314,7 +315,7 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,4}')), FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,4}')),
], ],
decoration: InputDecoration( decoration: InputDecoration(
hintText: '请输入数量', hintText: '请输入积分值数量',
hintStyle: const TextStyle(color: _grayText), hintStyle: const TextStyle(color: _grayText),
filled: true, filled: true,
fillColor: _bgGray, fillColor: _bgGray,
@ -328,17 +329,8 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
), ),
suffixIcon: TextButton( suffixIcon: TextButton(
onPressed: () { onPressed: () {
if (isBuy) { //
// _quantityController.text = availableCash;
final price = double.tryParse(_priceController.text) ?? 0;
final cash = double.tryParse(availableCash) ?? 0;
if (price > 0) {
_quantityController.text = (cash / price).toStringAsFixed(4);
}
} else {
//
_quantityController.text = availableShares;
}
setState(() {}); setState(() {});
}, },
child: const Text( child: const Text(
@ -349,7 +341,7 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
), ),
), ),
), ),
suffixText: '积分', suffixText: '积分',
suffixStyle: const TextStyle(color: _grayText, fontSize: 14), suffixStyle: const TextStyle(color: _grayText, fontSize: 14),
), ),
onChanged: (_) => setState(() {}), onChanged: (_) => setState(() {}),
@ -389,33 +381,40 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// //
const Text( const Text(
'收款方式', '收款方式(可多选)',
style: TextStyle(fontSize: 14, color: _grayText), style: TextStyle(fontSize: 14, color: _grayText),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Row( Wrap(
spacing: 12,
runSpacing: 8,
children: [ children: [
_buildPaymentMethodChip('ALIPAY', '支付宝', Icons.account_balance_wallet), // 绿 -
const SizedBox(width: 12), _buildPaymentMethodCheckbox('GREEN_POINTS', '绿积分', Icons.eco, isLocked: true),
_buildPaymentMethodChip('WECHAT', '微信', Icons.chat_bubble), _buildPaymentMethodCheckbox('ALIPAY', '支付宝', Icons.account_balance_wallet),
const SizedBox(width: 12), _buildPaymentMethodCheckbox('WECHAT', '微信', Icons.chat_bubble),
_buildPaymentMethodChip('BANK', '银行卡', Icons.credit_card), _buildPaymentMethodCheckbox('BANK', '银行卡', Icons.credit_card),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// // 1.0ID绿
const Text( const Text(
'收款账号', '1.0系统ID',
style: TextStyle(fontSize: 14, color: _grayText), style: TextStyle(fontSize: 14, color: _grayText),
), ),
const SizedBox(height: 4),
const Text(
'买家将通过此ID向您转账绿积分',
style: TextStyle(fontSize: 12, color: _grayText),
),
const SizedBox(height: 8), const SizedBox(height: 8),
TextField( TextField(
controller: _paymentAccountController, controller: _systemIdController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: _paymentMethod == 'BANK' ? '请输入银行卡号' : '请输入收款账号', hintText: '请输入您的1.0系统ID',
hintStyle: const TextStyle(color: _grayText), hintStyle: const TextStyle(color: _grayText),
filled: true, filled: true,
fillColor: _bgGray, fillColor: _bgGray,
@ -429,47 +428,75 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
), ),
), ),
), ),
const SizedBox(height: 16),
// // 绿
const Text( if (_selectedPaymentMethods.any((m) => m != 'GREEN_POINTS')) ...[
'收款人姓名', const SizedBox(height: 16),
style: TextStyle(fontSize: 14, color: _grayText), //
), const Text(
const SizedBox(height: 8), '收款账号(其他支付方式)',
TextField( style: TextStyle(fontSize: 14, color: _grayText),
controller: _paymentRealNameController, ),
decoration: InputDecoration( const SizedBox(height: 8),
hintText: '请输入收款人真实姓名', TextField(
hintStyle: const TextStyle(color: _grayText), controller: _paymentAccountController,
filled: true, decoration: InputDecoration(
fillColor: _bgGray, hintText: '请输入收款账号',
border: OutlineInputBorder( hintStyle: const TextStyle(color: _grayText),
borderRadius: BorderRadius.circular(12), filled: true,
borderSide: BorderSide.none, fillColor: _bgGray,
), border: OutlineInputBorder(
focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none,
borderSide: const BorderSide(color: _orange, width: 2), ),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: _orange, width: 2),
),
), ),
), ),
), const SizedBox(height: 16),
//
const Text(
'收款人姓名',
style: TextStyle(fontSize: 14, color: _grayText),
),
const SizedBox(height: 8),
TextField(
controller: _paymentRealNameController,
decoration: InputDecoration(
hintText: '请输入收款人真实姓名',
hintStyle: const TextStyle(color: _grayText),
filled: true,
fillColor: _bgGray,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: _orange, width: 2),
),
),
),
],
const SizedBox(height: 12), const SizedBox(height: 12),
Container( Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _orange.withOpacity(0.1), color: _green.withOpacity(0.1),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Row( child: Row(
children: const [ children: const [
Icon(Icons.info_outline, size: 16, color: _orange), Icon(Icons.eco, size: 16, color: _green),
SizedBox(width: 8), SizedBox(width: 8),
Expanded( Expanded(
child: Text( child: Text(
'买家会根据您填写的收款信息进行付款,请确保信息准确', '绿积分支付买家将在1.0系统中向您的ID转账绿积分',
style: TextStyle(fontSize: 12, color: _orange), style: TextStyle(fontSize: 12, color: _green),
), ),
), ),
], ],
@ -480,14 +507,22 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
); );
} }
Widget _buildPaymentMethodChip(String value, String label, IconData icon) { Widget _buildPaymentMethodCheckbox(String value, String label, IconData icon, {bool isLocked = false}) {
final isSelected = _paymentMethod == value; final isSelected = _selectedPaymentMethods.contains(value);
return GestureDetector( return GestureDetector(
onTap: () => setState(() => _paymentMethod = value), onTap: isLocked ? null : () {
setState(() {
if (isSelected) {
_selectedPaymentMethods.remove(value);
} else {
_selectedPaymentMethods.add(value);
}
});
},
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? _orange : _bgGray, color: isSelected ? (value == 'GREEN_POINTS' ? _green : _orange) : _bgGray,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: isSelected ? null : Border.all(color: Colors.grey.shade300), border: isSelected ? null : Border.all(color: Colors.grey.shade300),
), ),
@ -495,11 +530,17 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon( Icon(
icon, isSelected ? Icons.check_circle : Icons.circle_outlined,
size: 18, size: 18,
color: isSelected ? Colors.white : _grayText, color: isSelected ? Colors.white : _grayText,
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
Icon(
icon,
size: 16,
color: isSelected ? Colors.white : _grayText,
),
const SizedBox(width: 4),
Text( Text(
label, label,
style: TextStyle( style: TextStyle(
@ -508,6 +549,14 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
), ),
), ),
if (isLocked) ...[
const SizedBox(width: 4),
Icon(
Icons.lock,
size: 12,
color: isSelected ? Colors.white70 : _grayText,
),
],
], ],
), ),
), ),
@ -580,7 +629,7 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
children: [ children: [
const Text('交易总额', style: TextStyle(fontSize: 14, color: _grayText)), const Text('交易总额', style: TextStyle(fontSize: 14, color: _grayText)),
Text( Text(
'${formatAmount(total.toString())} 积分值', '${formatAmount(total.toString())}',
style: const TextStyle( style: const TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -590,12 +639,27 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
], ],
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('积分值数量', style: TextStyle(fontSize: 14, color: _grayText)),
Text(
'${formatAmount(quantity.toString())} 积分值',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: _green,
),
),
],
),
const SizedBox(height: 8),
Divider(color: _orange.withOpacity(0.2)), Divider(color: _orange.withOpacity(0.2)),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
isBuy isBuy
? '发布后,其他用户可以接单卖出积分股给您' ? '发布后,其他用户可以接单卖出积分给您'
: '发布后,其他用户可以接单用积分值购买您的积分股', : '发布后,其他用户可以接单购买您的积分值',
style: const TextStyle(fontSize: 12, color: _grayText), style: const TextStyle(fontSize: 12, color: _grayText),
), ),
], ],
@ -608,12 +672,17 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
final quantity = double.tryParse(_quantityController.text) ?? 0; final quantity = double.tryParse(_quantityController.text) ?? 0;
final isSell = _selectedType == 1; final isSell = _selectedType == 1;
// //
bool isValid = price > 0 && quantity > 0; bool isValid = price > 0 && quantity > 0;
if (isSell) { if (isSell) {
isValid = isValid && // 1.0ID绿
_paymentAccountController.text.trim().isNotEmpty && isValid = isValid && _systemIdController.text.trim().isNotEmpty;
_paymentRealNameController.text.trim().isNotEmpty; //
if (_selectedPaymentMethods.any((m) => m != 'GREEN_POINTS')) {
isValid = isValid &&
_paymentAccountController.text.trim().isNotEmpty &&
_paymentRealNameController.text.trim().isNotEmpty;
}
} }
return Padding( return Padding(
@ -680,10 +749,11 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
const Text( const Text(
'1. 发布广告后,您的资产将被冻结直到交易完成或取消\n' '1. 发布广告后,您的积分值将被冻结直到交易完成或取消\n'
'2. 其他用户接单后,需在规定时间内完成交易\n' '2. 其他用户接单后,需在规定时间内完成交易\n'
'3. 买方需先转账积分值,卖方确认收款后积分股自动划转\n' '3. 买方需在1.0系统中向卖方转账绿积分\n'
'4. 如遇问题,请联系客服处理', '4. 卖方确认收到绿积分后,积分值自动划转给买方\n'
'5. 如遇问题,请联系客服处理',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: _grayText, color: _grayText,
@ -704,25 +774,40 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
// //
if (isSell) { if (isSell) {
if (_paymentAccountController.text.trim().isEmpty) { // 1.0ID绿
if (_systemIdController.text.trim().isEmpty) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请填写收款账号'), backgroundColor: _red), const SnackBar(content: Text('请填写1.0系统ID'), backgroundColor: _red),
); );
return; return;
} }
if (_paymentRealNameController.text.trim().isEmpty) { //
ScaffoldMessenger.of(context).showSnackBar( if (_selectedPaymentMethods.any((m) => m != 'GREEN_POINTS')) {
const SnackBar(content: Text('请填写收款人姓名'), backgroundColor: _red), if (_paymentAccountController.text.trim().isEmpty) {
); ScaffoldMessenger.of(context).showSnackBar(
return; const SnackBar(content: Text('请填写收款账号'), backgroundColor: _red),
);
return;
}
if (_paymentRealNameController.text.trim().isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请填写收款人姓名'), backgroundColor: _red),
);
return;
}
} }
} }
final paymentMethodText = _paymentMethod == 'ALIPAY' //
? '支付宝' final paymentMethodTexts = _selectedPaymentMethods.map((m) {
: _paymentMethod == 'WECHAT' switch (m) {
? '微信' case 'GREEN_POINTS': return '绿积分';
: '银行卡'; case 'ALIPAY': return '支付宝';
case 'WECHAT': return '微信';
case 'BANK': return '银行卡';
default: return m;
}
}).join('');
// //
final confirmed = await showDialog<bool>( final confirmed = await showDialog<bool>(
@ -733,29 +818,31 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('单价: $price 积分值/股'), Text('单价: $price/积分值'),
const SizedBox(height: 8), const SizedBox(height: 8),
Text('数量: $quantity 积分'), Text('数量: $quantity 积分'),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'总额: ${formatAmount((double.parse(price) * double.parse(quantity)).toString())} 积分值', '总额: ${formatAmount((double.parse(price) * double.parse(quantity)).toString())}',
style: const TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(fontWeight: FontWeight.bold),
), ),
if (isSell) ...[ if (isSell) ...[
const SizedBox(height: 16), const SizedBox(height: 16),
const Divider(), const Divider(),
const SizedBox(height: 8), const SizedBox(height: 8),
Text('收款方式: $paymentMethodText'), Text('收款方式: $paymentMethodTexts'),
const SizedBox(height: 4), const SizedBox(height: 4),
Text('收款账号: ${_paymentAccountController.text.trim()}'), Text('1.0系统ID: ${_systemIdController.text.trim()}'),
const SizedBox(height: 4), if (_selectedPaymentMethods.any((m) => m != 'GREEN_POINTS')) ...[
Text('收款人: ${_paymentRealNameController.text.trim()}'), const SizedBox(height: 4),
Text('收款账号: ${_paymentAccountController.text.trim()}'),
const SizedBox(height: 4),
Text('收款人: ${_paymentRealNameController.text.trim()}'),
],
], ],
const SizedBox(height: 16), const SizedBox(height: 16),
Text( const Text(
_selectedType == 0 '发布后,您的积分值将被冻结',
? '发布后,您的积分值将被冻结'
: '发布后,您的积分股将被冻结',
style: TextStyle(fontSize: 12, color: _grayText), style: TextStyle(fontSize: 12, color: _grayText),
), ),
], ],
@ -784,9 +871,10 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
price: price, price: price,
quantity: quantity, quantity: quantity,
// //
paymentMethod: isSell ? _paymentMethod : null, paymentMethod: isSell ? _selectedPaymentMethods.join(',') : null,
paymentAccount: isSell ? _paymentAccountController.text.trim() : null, paymentAccount: isSell ? _paymentAccountController.text.trim() : null,
paymentRealName: isSell ? _paymentRealNameController.text.trim() : null, paymentRealName: isSell ? _paymentRealNameController.text.trim() : null,
systemId: isSell ? _systemIdController.text.trim() : null,
remark: remark.isEmpty ? null : remark, remark: remark.isEmpty ? null : remark,
); );

View File

@ -87,6 +87,7 @@ class C2cTradingNotifier extends StateNotifier<C2cTradingState> {
String? paymentAccount, String? paymentAccount,
String? paymentQrCode, String? paymentQrCode,
String? paymentRealName, String? paymentRealName,
String? systemId, // 1.0ID绿
String? remark, String? remark,
}) async { }) async {
state = state.copyWith(isLoading: true, clearError: true); state = state.copyWith(isLoading: true, clearError: true);
@ -101,6 +102,7 @@ class C2cTradingNotifier extends StateNotifier<C2cTradingState> {
paymentAccount: paymentAccount, paymentAccount: paymentAccount,
paymentQrCode: paymentQrCode, paymentQrCode: paymentQrCode,
paymentRealName: paymentRealName, paymentRealName: paymentRealName,
systemId: systemId,
remark: remark, remark: remark,
); );
state = state.copyWith(isLoading: false, lastOrder: order); state = state.copyWith(isLoading: false, lastOrder: order);