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

View File

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

View File

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