feat(c2c): 修复6项C2C交易问题 (#11-#16)
- #11: 价格固定1:1,最小交易数量为1 - #12: BUY买单不再冻结买方积分值(绿积分通过外部1.0系统支付) - #13: 支持部分成交,taker可指定数量,剩余自动拆分为新PENDING订单 - #14: 发布页双向输入,积分股数量与积分值金额1:1联动 - #15: 接BUY单时弹出收款信息输入(收款方式、账号、姓名) - #16: BUY单创建时验证积分值余额但不冻结 后端: cancelOrder/expireOrder/executeTransfer 均按BUY/SELL分别处理 前端: 发布页价格只读、市场页接单对话框增加数量和收款输入 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c28ccb6206
commit
187b82e9ac
|
|
@ -109,6 +109,13 @@ export class C2cService {
|
|||
if (quantityDecimal.lte(0)) {
|
||||
throw new BadRequestException('数量必须大于0');
|
||||
}
|
||||
// #11: 价格必须为1(1:1),数量最小为1
|
||||
if (!priceDecimal.equals(1)) {
|
||||
throw new BadRequestException('C2C交易价格固定为1:1');
|
||||
}
|
||||
if (quantityDecimal.lt(1)) {
|
||||
throw new BadRequestException('最小交易数量为1');
|
||||
}
|
||||
|
||||
// 卖单:自动获取卖家 Kava 地址(用于绿积分转账)
|
||||
let sellerKavaAddress: string | null = null;
|
||||
|
|
@ -130,13 +137,12 @@ export class C2cService {
|
|||
|
||||
// 检查余额并冻结资产
|
||||
if (type === C2C_ORDER_TYPE.BUY) {
|
||||
// 买入订单:需要冻结积分值(现金)
|
||||
// #16: 买入订单:验证积分值余额(买方通过外部系统支付绿积分)
|
||||
const totalAmountMoney = new Money(totalAmount);
|
||||
if (account.availableCash.isLessThan(totalAmountMoney)) {
|
||||
throw new BadRequestException(`积分值余额不足,需要 ${totalAmount.toString()},可用 ${account.availableCash.toString()}`);
|
||||
}
|
||||
// 冻结积分值
|
||||
await this.tradingAccountRepository.freezeCash(accountSequence, totalAmount);
|
||||
// #12: 不冻结(买方通过外部1.0系统支付绿积分,系统无法冻结外部资产)
|
||||
} else {
|
||||
// 卖出订单:需要冻结积分股
|
||||
const quantityMoney = new Money(quantityDecimal);
|
||||
|
|
@ -234,15 +240,60 @@ export class C2cService {
|
|||
throw new NotFoundException('交易账户不存在');
|
||||
}
|
||||
|
||||
const quantityDecimal = new Decimal(order.quantity);
|
||||
const totalAmountDecimal = new Decimal(order.totalAmount);
|
||||
// #13: 支持部分成交,taker可指定数量
|
||||
const originalQuantity = new Decimal(order.quantity);
|
||||
const priceDecimal = new Decimal(order.price);
|
||||
|
||||
let quantityDecimal: Decimal;
|
||||
let totalAmountDecimal: Decimal;
|
||||
|
||||
if (options?.quantity) {
|
||||
quantityDecimal = new Decimal(options.quantity);
|
||||
if (quantityDecimal.gt(originalQuantity)) {
|
||||
throw new BadRequestException('接单数量不能超过订单数量');
|
||||
}
|
||||
if (quantityDecimal.lt(1)) {
|
||||
throw new BadRequestException('最小交易数量为1');
|
||||
}
|
||||
totalAmountDecimal = priceDecimal.mul(quantityDecimal);
|
||||
} else {
|
||||
quantityDecimal = originalQuantity;
|
||||
totalAmountDecimal = new Decimal(order.totalAmount);
|
||||
}
|
||||
|
||||
// #13: 如果部分成交,创建剩余部分的新PENDING订单
|
||||
if (quantityDecimal.lt(originalQuantity)) {
|
||||
const remainingQuantity = originalQuantity.minus(quantityDecimal);
|
||||
const remainingTotal = priceDecimal.mul(remainingQuantity);
|
||||
|
||||
const remainOrderNo = this.generateOrderNo();
|
||||
await this.c2cOrderRepository.create({
|
||||
orderNo: remainOrderNo,
|
||||
type: order.type as any,
|
||||
makerAccountSequence: order.makerAccountSequence,
|
||||
makerUserId: order.makerUserId ?? undefined,
|
||||
makerPhone: order.makerPhone ?? undefined,
|
||||
makerNickname: order.makerNickname ?? undefined,
|
||||
price: order.price,
|
||||
quantity: remainingQuantity.toString(),
|
||||
totalAmount: remainingTotal.toString(),
|
||||
paymentMethod: order.paymentMethod ?? undefined,
|
||||
paymentAccount: order.paymentAccount ?? undefined,
|
||||
paymentQrCode: order.paymentQrCode ?? undefined,
|
||||
paymentRealName: order.paymentRealName ?? undefined,
|
||||
sellerKavaAddress: order.sellerKavaAddress,
|
||||
remark: order.remark ?? undefined,
|
||||
});
|
||||
|
||||
this.logger.log(`C2C部分成交: 原订单 ${orderNo} 拆分,剩余 ${remainingQuantity} 创建新订单 ${remainOrderNo}`);
|
||||
}
|
||||
|
||||
// 接单方需要冻结对应资产
|
||||
if (order.type === C2C_ORDER_TYPE.BUY) {
|
||||
// 挂单方要买入积分股,接单方需要有积分股来卖出
|
||||
const quantityMoney = new Money(quantityDecimal);
|
||||
if (takerAccount.availableShares.isLessThan(quantityMoney)) {
|
||||
throw new BadRequestException(`积分股余额不足,需要 ${order.quantity},可用 ${takerAccount.availableShares.toString()}`);
|
||||
throw new BadRequestException(`积分股余额不足,需要 ${quantityDecimal.toString()},可用 ${takerAccount.availableShares.toString()}`);
|
||||
}
|
||||
// 冻结接单方的积分股
|
||||
await this.tradingAccountRepository.freezeShares(takerAccountSequence, quantityDecimal);
|
||||
|
|
@ -250,7 +301,7 @@ export class C2cService {
|
|||
// 挂单方要卖出积分股,接单方需要有积分值来买入
|
||||
const totalAmountMoney = new Money(totalAmountDecimal);
|
||||
if (takerAccount.availableCash.isLessThan(totalAmountMoney)) {
|
||||
throw new BadRequestException(`积分值余额不足,需要 ${order.totalAmount},可用 ${takerAccount.availableCash.toString()}`);
|
||||
throw new BadRequestException(`积分值余额不足,需要 ${totalAmountDecimal.toString()},可用 ${takerAccount.availableCash.toString()}`);
|
||||
}
|
||||
// 冻结接单方的积分值
|
||||
await this.tradingAccountRepository.freezeCash(takerAccountSequence, totalAmountDecimal);
|
||||
|
|
@ -266,6 +317,8 @@ export class C2cService {
|
|||
takerUserId: options?.userId,
|
||||
takerPhone: options?.phone,
|
||||
takerNickname: options?.nickname,
|
||||
quantity: quantityDecimal.toString(), // #13: 更新为实际成交量
|
||||
totalAmount: totalAmountDecimal.toString(), // #13: 更新为实际成交额
|
||||
matchedAt: now,
|
||||
paymentDeadline,
|
||||
};
|
||||
|
|
@ -280,7 +333,7 @@ export class C2cService {
|
|||
|
||||
const updatedOrder = await this.c2cOrderRepository.updateStatus(orderNo, C2C_ORDER_STATUS.MATCHED as any, updateData);
|
||||
|
||||
this.logger.log(`C2C订单接单成功: ${orderNo}, 接单方: ${takerAccountSequence}`);
|
||||
this.logger.log(`C2C订单接单成功: ${orderNo}, 接单方: ${takerAccountSequence}, 成交数量: ${quantityDecimal.toString()}`);
|
||||
return updatedOrder!;
|
||||
} finally {
|
||||
await this.redis.releaseLock(lockKey, lockValue);
|
||||
|
|
@ -314,7 +367,7 @@ export class C2cService {
|
|||
|
||||
// 解冻挂单方的资产
|
||||
if (order.type === C2C_ORDER_TYPE.BUY) {
|
||||
await this.tradingAccountRepository.unfreezeCash(accountSequence, totalAmountDecimal);
|
||||
// BUY 订单未冻结买方资产(买方通过外部支付绿积分),无需解冻
|
||||
} else {
|
||||
await this.tradingAccountRepository.unfreezeShares(accountSequence, quantityDecimal);
|
||||
}
|
||||
|
|
@ -443,81 +496,144 @@ export class C2cService {
|
|||
|
||||
// 使用事务执行转账
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
// 1. 解冻买方的积分值并扣除
|
||||
await tx.tradingAccount.update({
|
||||
where: { accountSequence: buyerAccountSequence },
|
||||
data: {
|
||||
frozenCash: { decrement: totalAmountDecimal.toNumber() },
|
||||
cashBalance: { decrement: totalAmountDecimal.toNumber() },
|
||||
},
|
||||
});
|
||||
if (order.type === C2C_ORDER_TYPE.BUY) {
|
||||
// BUY订单:买方通过外部支付绿积分,系统只转移卖方积分股给买方(单向)
|
||||
// 1. 解冻卖方积分股并扣除
|
||||
await tx.tradingAccount.update({
|
||||
where: { accountSequence: sellerAccountSequence },
|
||||
data: {
|
||||
frozenShares: { decrement: quantityDecimal.toNumber() },
|
||||
shareBalance: { decrement: quantityDecimal.toNumber() },
|
||||
totalSold: { increment: quantityDecimal.toNumber() },
|
||||
},
|
||||
});
|
||||
|
||||
// 2. 解冻卖方的积分股并扣除
|
||||
await tx.tradingAccount.update({
|
||||
where: { accountSequence: sellerAccountSequence },
|
||||
data: {
|
||||
frozenShares: { decrement: quantityDecimal.toNumber() },
|
||||
shareBalance: { decrement: quantityDecimal.toNumber() },
|
||||
totalSold: { increment: quantityDecimal.toNumber() },
|
||||
},
|
||||
});
|
||||
// 2. 买方获得积分股
|
||||
await tx.tradingAccount.update({
|
||||
where: { accountSequence: buyerAccountSequence },
|
||||
data: {
|
||||
shareBalance: { increment: quantityDecimal.toNumber() },
|
||||
totalBought: { increment: quantityDecimal.toNumber() },
|
||||
},
|
||||
});
|
||||
|
||||
// 3. 买方获得积分股
|
||||
await tx.tradingAccount.update({
|
||||
where: { accountSequence: buyerAccountSequence },
|
||||
data: {
|
||||
shareBalance: { increment: quantityDecimal.toNumber() },
|
||||
totalBought: { increment: quantityDecimal.toNumber() },
|
||||
},
|
||||
});
|
||||
// 3. 记录交易流水(买方 — 积分股入账)
|
||||
const buyerAccount = await tx.tradingAccount.findUnique({
|
||||
where: { accountSequence: buyerAccountSequence },
|
||||
});
|
||||
await tx.tradingTransaction.create({
|
||||
data: {
|
||||
accountSequence: buyerAccountSequence,
|
||||
type: 'C2C_BUY',
|
||||
assetType: 'SHARE',
|
||||
amount: quantityDecimal.toNumber(),
|
||||
balanceBefore: new Decimal(buyerAccount!.shareBalance).minus(quantityDecimal).toNumber(),
|
||||
balanceAfter: buyerAccount!.shareBalance,
|
||||
referenceId: order.orderNo,
|
||||
referenceType: 'C2C_ORDER',
|
||||
counterpartyType: 'USER',
|
||||
counterpartyAccountSeq: sellerAccountSequence,
|
||||
memo: `C2C买入 ${order.quantity} 积分股(绿积分支付)`,
|
||||
},
|
||||
});
|
||||
|
||||
// 4. 卖方获得积分值
|
||||
await tx.tradingAccount.update({
|
||||
where: { accountSequence: sellerAccountSequence },
|
||||
data: {
|
||||
cashBalance: { increment: totalAmountDecimal.toNumber() },
|
||||
},
|
||||
});
|
||||
// 4. 记录交易流水(卖方 — 积分股扣除)
|
||||
const sellerAccount = await tx.tradingAccount.findUnique({
|
||||
where: { accountSequence: sellerAccountSequence },
|
||||
});
|
||||
await tx.tradingTransaction.create({
|
||||
data: {
|
||||
accountSequence: sellerAccountSequence,
|
||||
type: 'C2C_SELL',
|
||||
assetType: 'SHARE',
|
||||
amount: quantityDecimal.toNumber(),
|
||||
balanceBefore: new Decimal(sellerAccount!.shareBalance).plus(quantityDecimal).toNumber(),
|
||||
balanceAfter: sellerAccount!.shareBalance,
|
||||
referenceId: order.orderNo,
|
||||
referenceType: 'C2C_ORDER',
|
||||
counterpartyType: 'USER',
|
||||
counterpartyAccountSeq: buyerAccountSequence,
|
||||
memo: `C2C卖出 ${order.quantity} 积分股(收到绿积分)`,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// SELL订单:双向交换(积分股 ↔ 积分值),保持现有逻辑
|
||||
// 1. 解冻买方的积分值并扣除
|
||||
await tx.tradingAccount.update({
|
||||
where: { accountSequence: buyerAccountSequence },
|
||||
data: {
|
||||
frozenCash: { decrement: totalAmountDecimal.toNumber() },
|
||||
cashBalance: { decrement: totalAmountDecimal.toNumber() },
|
||||
},
|
||||
});
|
||||
|
||||
// 5. 记录交易流水(买方)
|
||||
const buyerAccount = await tx.tradingAccount.findUnique({
|
||||
where: { accountSequence: buyerAccountSequence },
|
||||
});
|
||||
await tx.tradingTransaction.create({
|
||||
data: {
|
||||
accountSequence: buyerAccountSequence,
|
||||
type: 'C2C_BUY',
|
||||
assetType: 'SHARE',
|
||||
amount: quantityDecimal.toNumber(),
|
||||
balanceBefore: new Decimal(buyerAccount!.shareBalance).minus(quantityDecimal).toNumber(),
|
||||
balanceAfter: buyerAccount!.shareBalance,
|
||||
referenceId: order.orderNo,
|
||||
referenceType: 'C2C_ORDER',
|
||||
counterpartyType: 'USER',
|
||||
counterpartyAccountSeq: sellerAccountSequence,
|
||||
memo: `C2C买入 ${order.quantity} 积分股,单价 ${order.price}`,
|
||||
},
|
||||
});
|
||||
// 2. 解冻卖方的积分股并扣除
|
||||
await tx.tradingAccount.update({
|
||||
where: { accountSequence: sellerAccountSequence },
|
||||
data: {
|
||||
frozenShares: { decrement: quantityDecimal.toNumber() },
|
||||
shareBalance: { decrement: quantityDecimal.toNumber() },
|
||||
totalSold: { increment: quantityDecimal.toNumber() },
|
||||
},
|
||||
});
|
||||
|
||||
// 6. 记录交易流水(卖方)
|
||||
const sellerAccount = await tx.tradingAccount.findUnique({
|
||||
where: { accountSequence: sellerAccountSequence },
|
||||
});
|
||||
await tx.tradingTransaction.create({
|
||||
data: {
|
||||
accountSequence: sellerAccountSequence,
|
||||
type: 'C2C_SELL',
|
||||
assetType: 'CASH',
|
||||
amount: totalAmountDecimal.toNumber(),
|
||||
balanceBefore: new Decimal(sellerAccount!.cashBalance).minus(totalAmountDecimal).toNumber(),
|
||||
balanceAfter: sellerAccount!.cashBalance,
|
||||
referenceId: order.orderNo,
|
||||
referenceType: 'C2C_ORDER',
|
||||
counterpartyType: 'USER',
|
||||
counterpartyAccountSeq: buyerAccountSequence,
|
||||
memo: `C2C卖出 ${order.quantity} 积分股,单价 ${order.price}`,
|
||||
},
|
||||
});
|
||||
// 3. 买方获得积分股
|
||||
await tx.tradingAccount.update({
|
||||
where: { accountSequence: buyerAccountSequence },
|
||||
data: {
|
||||
shareBalance: { increment: quantityDecimal.toNumber() },
|
||||
totalBought: { increment: quantityDecimal.toNumber() },
|
||||
},
|
||||
});
|
||||
|
||||
// 4. 卖方获得积分值
|
||||
await tx.tradingAccount.update({
|
||||
where: { accountSequence: sellerAccountSequence },
|
||||
data: {
|
||||
cashBalance: { increment: totalAmountDecimal.toNumber() },
|
||||
},
|
||||
});
|
||||
|
||||
// 5. 记录交易流水(买方)
|
||||
const buyerAccount = await tx.tradingAccount.findUnique({
|
||||
where: { accountSequence: buyerAccountSequence },
|
||||
});
|
||||
await tx.tradingTransaction.create({
|
||||
data: {
|
||||
accountSequence: buyerAccountSequence,
|
||||
type: 'C2C_BUY',
|
||||
assetType: 'SHARE',
|
||||
amount: quantityDecimal.toNumber(),
|
||||
balanceBefore: new Decimal(buyerAccount!.shareBalance).minus(quantityDecimal).toNumber(),
|
||||
balanceAfter: buyerAccount!.shareBalance,
|
||||
referenceId: order.orderNo,
|
||||
referenceType: 'C2C_ORDER',
|
||||
counterpartyType: 'USER',
|
||||
counterpartyAccountSeq: sellerAccountSequence,
|
||||
memo: `C2C买入 ${order.quantity} 积分股,单价 ${order.price}`,
|
||||
},
|
||||
});
|
||||
|
||||
// 6. 记录交易流水(卖方)
|
||||
const sellerAccount = await tx.tradingAccount.findUnique({
|
||||
where: { accountSequence: sellerAccountSequence },
|
||||
});
|
||||
await tx.tradingTransaction.create({
|
||||
data: {
|
||||
accountSequence: sellerAccountSequence,
|
||||
type: 'C2C_SELL',
|
||||
assetType: 'CASH',
|
||||
amount: totalAmountDecimal.toNumber(),
|
||||
balanceBefore: new Decimal(sellerAccount!.cashBalance).minus(totalAmountDecimal).toNumber(),
|
||||
balanceAfter: sellerAccount!.cashBalance,
|
||||
referenceId: order.orderNo,
|
||||
referenceType: 'C2C_ORDER',
|
||||
counterpartyType: 'USER',
|
||||
counterpartyAccountSeq: buyerAccountSequence,
|
||||
memo: `C2C卖出 ${order.quantity} 积分股,单价 ${order.price}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.logger.log(`C2C交易转账完成: ${order.orderNo}, 买方: ${buyerAccountSequence}, 卖方: ${sellerAccountSequence}`);
|
||||
|
|
@ -626,8 +742,7 @@ export class C2cService {
|
|||
|
||||
// 解冻双方资产
|
||||
if (freshOrder.type === C2C_ORDER_TYPE.BUY) {
|
||||
// BUY订单:maker冻结了积分值,taker冻结了积分股
|
||||
await this.tradingAccountRepository.unfreezeCash(freshOrder.makerAccountSequence, totalAmountDecimal);
|
||||
// BUY订单:maker未冻结(买方通过外部支付绿积分),只解冻taker的积分股
|
||||
if (freshOrder.takerAccountSequence) {
|
||||
await this.tradingAccountRepository.unfreezeShares(freshOrder.takerAccountSequence, quantityDecimal);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,6 +150,9 @@ export class C2cOrderRepository {
|
|||
paymentAccount: string;
|
||||
paymentQrCode: string;
|
||||
paymentRealName: string;
|
||||
// #13: 部分成交时更新数量和金额
|
||||
quantity: string;
|
||||
totalAmount: string;
|
||||
// 超时时间
|
||||
paymentDeadline: Date;
|
||||
confirmDeadline: Date;
|
||||
|
|
|
|||
|
|
@ -585,65 +585,364 @@ class _C2cMarketPageState extends ConsumerState<C2cMarketPage>
|
|||
return '${dateTime.month}/${dateTime.day} ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
// #13 + #15: 接单对话框(支持部分成交数量输入 + BUY单收款信息输入)
|
||||
void _showTakeOrderDialog(C2cOrderModel order, bool isBuyAction) {
|
||||
showDialog(
|
||||
final quantityController = TextEditingController(text: order.quantity);
|
||||
final paymentAccountController = TextEditingController();
|
||||
final paymentRealNameController = TextEditingController();
|
||||
final isTakingBuyOrder = order.isBuy; // 接BUY单 = taker是卖方
|
||||
Set<String> selectedPaymentMethods = {'GREEN_POINTS'};
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
backgroundColor: AppColors.cardOf(dialogContext),
|
||||
title: Text(
|
||||
isBuyAction ? '确认购买' : '确认出售',
|
||||
style: TextStyle(color: AppColors.textPrimaryOf(dialogContext)),
|
||||
isScrollControlled: true,
|
||||
backgroundColor: AppColors.cardOf(context),
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
builder: (sheetContext) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setSheetState) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 20,
|
||||
right: 20,
|
||||
top: 20,
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom + 20,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 标题
|
||||
Center(
|
||||
child: Text(
|
||||
isBuyAction ? '确认购买' : '确认出售',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.textPrimaryOf(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// 订单信息
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: _bgGray,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('单价', style: TextStyle(fontSize: 13, color: AppColors.textSecondaryOf(context))),
|
||||
Text('${formatPrice(order.price)} 积分值', style: TextStyle(fontSize: 13, color: AppColors.textPrimaryOf(context))),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('可用数量', style: TextStyle(fontSize: 13, color: AppColors.textSecondaryOf(context))),
|
||||
Text('${formatAmount(order.quantity)} 积分股', style: TextStyle(fontSize: 13, color: AppColors.textPrimaryOf(context))),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// #13: 数量输入框(支持部分成交)
|
||||
Text(
|
||||
'交易数量',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.textPrimaryOf(context),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: quantityController,
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
decoration: InputDecoration(
|
||||
hintText: '输入数量 (最大 ${order.quantity})',
|
||||
hintStyle: TextStyle(color: AppColors.textSecondaryOf(context)),
|
||||
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),
|
||||
),
|
||||
suffixText: '积分股',
|
||||
suffixStyle: TextStyle(color: AppColors.textSecondaryOf(context), fontSize: 14),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'最小数量: 1,可输入小于订单总量的数量进行部分成交',
|
||||
style: TextStyle(fontSize: 11, color: AppColors.textMutedOf(context)),
|
||||
),
|
||||
|
||||
// #15: 收款信息(仅当接BUY单时显示 — taker是卖方,需要提供收款信息)
|
||||
if (isTakingBuyOrder) ...[
|
||||
const SizedBox(height: 20),
|
||||
const Divider(),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'收款信息',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.textPrimaryOf(context),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'买方将通过以下方式向您支付绿积分',
|
||||
style: TextStyle(fontSize: 12, color: AppColors.textSecondaryOf(context)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// 收款方式选择
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_buildPaymentChip('GREEN_POINTS', '绿积分', Icons.eco,
|
||||
isSelected: selectedPaymentMethods.contains('GREEN_POINTS'),
|
||||
isLocked: true,
|
||||
onTap: null,
|
||||
),
|
||||
_buildPaymentChip('ALIPAY', '支付宝', Icons.account_balance_wallet,
|
||||
isSelected: selectedPaymentMethods.contains('ALIPAY'),
|
||||
onTap: () => setSheetState(() {
|
||||
if (selectedPaymentMethods.contains('ALIPAY')) {
|
||||
selectedPaymentMethods.remove('ALIPAY');
|
||||
} else {
|
||||
selectedPaymentMethods.add('ALIPAY');
|
||||
}
|
||||
}),
|
||||
),
|
||||
_buildPaymentChip('WECHAT', '微信', Icons.chat_bubble,
|
||||
isSelected: selectedPaymentMethods.contains('WECHAT'),
|
||||
onTap: () => setSheetState(() {
|
||||
if (selectedPaymentMethods.contains('WECHAT')) {
|
||||
selectedPaymentMethods.remove('WECHAT');
|
||||
} else {
|
||||
selectedPaymentMethods.add('WECHAT');
|
||||
}
|
||||
}),
|
||||
),
|
||||
_buildPaymentChip('BANK', '银行卡', Icons.credit_card,
|
||||
isSelected: selectedPaymentMethods.contains('BANK'),
|
||||
onTap: () => setSheetState(() {
|
||||
if (selectedPaymentMethods.contains('BANK')) {
|
||||
selectedPaymentMethods.remove('BANK');
|
||||
} else {
|
||||
selectedPaymentMethods.add('BANK');
|
||||
}
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// 账户ID (自动填充)
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'您的账户ID(买方将向此ID转账绿积分)',
|
||||
style: TextStyle(fontSize: 12, color: AppColors.textSecondaryOf(context)),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: _bgGray,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
ref.read(userNotifierProvider).accountSequence ?? '',
|
||||
style: TextStyle(fontSize: 14, color: AppColors.textPrimaryOf(context)),
|
||||
),
|
||||
),
|
||||
|
||||
// 其他支付方式的收款账号
|
||||
if (selectedPaymentMethods.any((m) => m != 'GREEN_POINTS')) ...[
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: paymentAccountController,
|
||||
decoration: InputDecoration(
|
||||
labelText: '收款账号(其他支付方式)',
|
||||
hintText: '请输入收款账号',
|
||||
hintStyle: TextStyle(color: AppColors.textSecondaryOf(context)),
|
||||
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),
|
||||
TextField(
|
||||
controller: paymentRealNameController,
|
||||
decoration: InputDecoration(
|
||||
labelText: '收款人姓名',
|
||||
hintText: '请输入收款人真实姓名',
|
||||
hintStyle: TextStyle(color: AppColors.textSecondaryOf(context)),
|
||||
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: 24),
|
||||
|
||||
// 提示信息
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: _orange.withOpacity(0.05),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
isBuyAction
|
||||
? '您将使用积分值购买对方的积分股'
|
||||
: '您将出售积分股,买方将向您支付绿积分',
|
||||
style: TextStyle(fontSize: 12, color: AppColors.textSecondaryOf(context)),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// 按钮
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
_takeOrder(
|
||||
order,
|
||||
quantity: quantityController.text.trim(),
|
||||
paymentMethod: isTakingBuyOrder ? selectedPaymentMethods.join(',') : null,
|
||||
paymentAccount: isTakingBuyOrder ? paymentAccountController.text.trim() : null,
|
||||
paymentRealName: isTakingBuyOrder ? paymentRealNameController.text.trim() : null,
|
||||
);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _orange,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
child: const Text('确认接单', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPaymentChip(String value, String label, IconData icon, {
|
||||
required bool isSelected,
|
||||
bool isLocked = false,
|
||||
VoidCallback? onTap,
|
||||
}) {
|
||||
return GestureDetector(
|
||||
onTap: isLocked ? null : onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? (value == 'GREEN_POINTS' ? _green : _orange) : _bgGray,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: isSelected ? null : Border.all(color: Colors.grey.shade300),
|
||||
),
|
||||
content: Column(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'数量: ${formatAmount(order.quantity)} 积分股',
|
||||
style: TextStyle(color: AppColors.textPrimaryOf(dialogContext)),
|
||||
Icon(
|
||||
isSelected ? Icons.check_circle : Icons.circle_outlined,
|
||||
size: 16,
|
||||
color: isSelected ? Colors.white : _grayText,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(width: 4),
|
||||
Icon(icon, size: 14, color: isSelected ? Colors.white : _grayText),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'单价: ${formatPrice(order.price)} 积分值',
|
||||
style: TextStyle(color: AppColors.textPrimaryOf(dialogContext)),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'总金额: ${formatAmount(order.totalAmount)} 积分值',
|
||||
style: TextStyle(fontWeight: FontWeight.bold, color: AppColors.textPrimaryOf(dialogContext)),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
isBuyAction
|
||||
? '您将使用积分值购买对方的积分股'
|
||||
: '您将出售积分股换取对方的积分值',
|
||||
style: TextStyle(fontSize: 12, color: AppColors.textSecondaryOf(dialogContext)),
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: isSelected ? Colors.white : _darkText,
|
||||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
if (isLocked) ...[
|
||||
const SizedBox(width: 2),
|
||||
Icon(Icons.lock, size: 10, color: isSelected ? Colors.white70 : _grayText),
|
||||
],
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(dialogContext),
|
||||
child: Text('取消', style: TextStyle(color: AppColors.textSecondaryOf(dialogContext))),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(dialogContext);
|
||||
await _takeOrder(order);
|
||||
},
|
||||
child: Text(
|
||||
'确认',
|
||||
style: TextStyle(color: _orange),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _takeOrder(C2cOrderModel order) async {
|
||||
Future<void> _takeOrder(
|
||||
C2cOrderModel order, {
|
||||
String? quantity,
|
||||
String? paymentMethod,
|
||||
String? paymentAccount,
|
||||
String? paymentRealName,
|
||||
}) async {
|
||||
final notifier = ref.read(c2cTradingNotifierProvider.notifier);
|
||||
final success = await notifier.takeOrder(order.orderNo);
|
||||
final success = await notifier.takeOrder(
|
||||
order.orderNo,
|
||||
quantity: quantity,
|
||||
paymentMethod: paymentMethod,
|
||||
paymentAccount: paymentAccount,
|
||||
paymentRealName: paymentRealName,
|
||||
);
|
||||
|
||||
if (success && mounted) {
|
||||
// 刷新列表
|
||||
|
|
|
|||
|
|
@ -24,9 +24,11 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
|
|||
static const Color _bgGray = Color(0xFFF3F4F6);
|
||||
|
||||
int _selectedType = 1; // 0: 买入, 1: 卖出
|
||||
final _priceController = TextEditingController();
|
||||
final _priceController = TextEditingController(text: '1'); // #11: 价格固定1:1
|
||||
final _quantityController = TextEditingController();
|
||||
final _amountController = TextEditingController(); // #14: 双向输入
|
||||
final _remarkController = TextEditingController();
|
||||
bool _isSyncingInput = false; // 防止双向输入递归
|
||||
|
||||
// 收款信息(卖单必填)
|
||||
Set<String> _selectedPaymentMethods = {'GREEN_POINTS'}; // 绿积分默认选中且不可取消
|
||||
|
|
@ -37,6 +39,7 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
|
|||
void dispose() {
|
||||
_priceController.dispose();
|
||||
_quantityController.dispose();
|
||||
_amountController.dispose();
|
||||
_remarkController.dispose();
|
||||
_paymentAccountController.dispose();
|
||||
_paymentRealNameController.dispose();
|
||||
|
|
@ -56,11 +59,6 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
|
|||
final availableShares = asset?.availableShares ?? '0';
|
||||
final availableCash = asset?.availableCash ?? '0';
|
||||
|
||||
// 设置默认价格
|
||||
if (_priceController.text.isEmpty && currentPrice != '0') {
|
||||
_priceController.text = currentPrice;
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: _bgGray,
|
||||
appBar: AppBar(
|
||||
|
|
@ -194,8 +192,12 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
|
|||
}
|
||||
|
||||
Widget _buildBalanceCard(String availableShares, String availableCash) {
|
||||
// C2C交易的是积分值,买入广告需要买家有绿积分(积分值)来支付
|
||||
// 卖出广告需要卖家有积分值来出售
|
||||
final isBuy = _selectedType == 0;
|
||||
// BUY: 显示可用积分值(用于验证 #16)
|
||||
// SELL: 显示可用积分股(卖方需要有足够积分股)
|
||||
final label = isBuy ? '可用积分值' : '可用积分股';
|
||||
final balance = isBuy ? availableCash : availableShares;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(16),
|
||||
|
|
@ -206,12 +208,12 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
|
|||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'可用积分值',
|
||||
style: TextStyle(fontSize: 14, color: _grayText),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(fontSize: 14, color: _grayText),
|
||||
),
|
||||
Text(
|
||||
formatAmount(availableCash),
|
||||
formatAmount(balance),
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
|
@ -223,6 +225,7 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
|
|||
);
|
||||
}
|
||||
|
||||
// #11: 价格固定为1:1,不可修改
|
||||
Widget _buildPriceInput(String currentPrice) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
|
|
@ -234,59 +237,51 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'单价',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _darkText,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'当前价: ${formatPrice(currentPrice)}',
|
||||
style: const TextStyle(fontSize: 12, color: _grayText),
|
||||
),
|
||||
],
|
||||
const Text(
|
||||
'单价',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _darkText,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: _priceController,
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,8}')),
|
||||
],
|
||||
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),
|
||||
),
|
||||
suffixText: '元/积分值',
|
||||
suffixStyle: const TextStyle(color: _grayText, fontSize: 14),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: _bgGray,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: const [
|
||||
Text(
|
||||
'1',
|
||||
style: TextStyle(fontSize: 16, color: _darkText),
|
||||
),
|
||||
Text(
|
||||
'元/积分值 (固定)',
|
||||
style: TextStyle(color: _grayText, fontSize: 14),
|
||||
),
|
||||
],
|
||||
),
|
||||
onChanged: (_) => setState(() {}),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// #14: 双向输入 — 积分股数量 ↔ 积分值金额(1:1固定价格,两者相等)
|
||||
Widget _buildQuantityInput(
|
||||
String availableShares,
|
||||
String availableCash,
|
||||
String currentPrice,
|
||||
) {
|
||||
// C2C交易的是积分值
|
||||
final isBuy = _selectedType == 0;
|
||||
// BUY: 买入积分股,用积分值支付 → 上限是可用积分值
|
||||
// SELL: 卖出积分股 → 上限是可用积分股
|
||||
final maxBalance = isBuy ? availableCash : availableShares;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
|
|
@ -298,14 +293,21 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'数量',
|
||||
'交易数量',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _darkText,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'价格1:1,积分股数量 = 积分值金额',
|
||||
style: const TextStyle(fontSize: 12, color: _grayText),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// 输入框1: 积分股数量
|
||||
TextField(
|
||||
controller: _quantityController,
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
|
|
@ -313,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,
|
||||
|
|
@ -327,8 +329,10 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
|
|||
),
|
||||
suffixIcon: TextButton(
|
||||
onPressed: () {
|
||||
// 填入全部可用积分值
|
||||
_quantityController.text = availableCash;
|
||||
_isSyncingInput = true;
|
||||
_quantityController.text = maxBalance;
|
||||
_amountController.text = maxBalance;
|
||||
_isSyncingInput = false;
|
||||
setState(() {});
|
||||
},
|
||||
child: const Text(
|
||||
|
|
@ -339,10 +343,50 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
|
|||
),
|
||||
),
|
||||
),
|
||||
suffixText: '积分股',
|
||||
suffixStyle: const TextStyle(color: _grayText, fontSize: 14),
|
||||
),
|
||||
onChanged: (val) {
|
||||
if (_isSyncingInput) return;
|
||||
_isSyncingInput = true;
|
||||
_amountController.text = val; // 1:1同步
|
||||
_isSyncingInput = false;
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// 输入框2: 积分值金额
|
||||
TextField(
|
||||
controller: _amountController,
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,4}')),
|
||||
],
|
||||
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),
|
||||
),
|
||||
suffixText: '积分值',
|
||||
suffixStyle: const TextStyle(color: _grayText, fontSize: 14),
|
||||
),
|
||||
onChanged: (_) => setState(() {}),
|
||||
onChanged: (val) {
|
||||
if (_isSyncingInput) return;
|
||||
_isSyncingInput = true;
|
||||
_quantityController.text = val; // 1:1同步
|
||||
_isSyncingInput = false;
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -635,9 +679,9 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
|
|||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('积分值数量', style: TextStyle(fontSize: 14, color: _grayText)),
|
||||
const Text('积分股数量', style: TextStyle(fontSize: 14, color: _grayText)),
|
||||
Text(
|
||||
'${formatAmount(quantity.toString())} 积分值',
|
||||
'${formatAmount(quantity.toString())} 积分股',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
|
@ -661,12 +705,11 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
|
|||
}
|
||||
|
||||
Widget _buildPublishButton(C2cTradingState c2cState) {
|
||||
final price = double.tryParse(_priceController.text) ?? 0;
|
||||
final quantity = double.tryParse(_quantityController.text) ?? 0;
|
||||
final isSell = _selectedType == 1;
|
||||
|
||||
// 验证条件
|
||||
bool isValid = price > 0 && quantity > 0;
|
||||
// #11: 价格固定为1,数量最小为1
|
||||
bool isValid = quantity >= 1;
|
||||
if (isSell) {
|
||||
// 如果选择了其他支付方式,还需要填写收款账号和姓名
|
||||
if (_selectedPaymentMethods.any((m) => m != 'GREEN_POINTS')) {
|
||||
|
|
@ -740,11 +783,12 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
|
|||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'1. 发布广告后,您的积分值将被冻结直到交易完成或取消\n'
|
||||
'2. 其他用户接单后,需在规定时间内完成交易\n'
|
||||
'3. 买方需在1.0系统中向卖方转账绿积分\n'
|
||||
'4. 卖方确认收到绿积分后,积分值自动划转给买方\n'
|
||||
'5. 如遇问题,请联系客服处理',
|
||||
'1. 发布卖出广告后,您的积分股将被冻结直到交易完成或取消\n'
|
||||
'2. 发布买入广告不会冻结您的资产\n'
|
||||
'3. 其他用户接单后,需在规定时间内完成交易\n'
|
||||
'4. 买方需在1.0系统中向卖方转账绿积分\n'
|
||||
'5. 卖方确认收到绿积分后,积分股自动划转给买方\n'
|
||||
'6. 如遇问题,请联系客服处理',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: _grayText,
|
||||
|
|
@ -825,9 +869,11 @@ class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
|
|||
],
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'发布后,您的积分值将被冻结',
|
||||
style: TextStyle(fontSize: 12, color: _grayText),
|
||||
Text(
|
||||
_selectedType == 1
|
||||
? '发布后,您的积分股将被冻结'
|
||||
: '发布后不会冻结您的资产',
|
||||
style: const TextStyle(fontSize: 12, color: _grayText),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
Loading…
Reference in New Issue