rwadurian/frontend/mining-app/lib/presentation/pages/c2c/c2c_publish_page.dart

907 lines
30 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../core/utils/format_utils.dart';
import '../../providers/c2c_providers.dart';
import '../../providers/user_providers.dart';
import '../../providers/asset_providers.dart';
import '../../providers/trading_providers.dart';
class C2cPublishPage extends ConsumerStatefulWidget {
const C2cPublishPage({super.key});
@override
ConsumerState<C2cPublishPage> createState() => _C2cPublishPageState();
}
class _C2cPublishPageState extends ConsumerState<C2cPublishPage> {
static const Color _orange = Color(0xFFFF6B00);
static const Color _green = Color(0xFF10B981);
static const Color _red = Color(0xFFEF4444);
static const Color _darkText = Color(0xFF1F2937);
static const Color _grayText = Color(0xFF6B7280);
static const Color _bgGray = Color(0xFFF3F4F6);
int _selectedType = 1; // 0: 买入, 1: 卖出
final _priceController = TextEditingController();
final _quantityController = TextEditingController();
final _remarkController = TextEditingController();
// 收款信息(卖单必填)
Set<String> _selectedPaymentMethods = {'GREEN_POINTS'}; // 绿积分默认选中且不可取消
final _paymentAccountController = TextEditingController();
final _paymentRealNameController = TextEditingController();
final _systemIdController = TextEditingController(); // 1.0系统ID
@override
void dispose() {
_priceController.dispose();
_quantityController.dispose();
_remarkController.dispose();
_paymentAccountController.dispose();
_paymentRealNameController.dispose();
_systemIdController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final user = ref.watch(userNotifierProvider);
final accountSequence = user.accountSequence ?? '';
final assetAsync = ref.watch(accountAssetProvider(accountSequence));
final priceAsync = ref.watch(currentPriceProvider);
final c2cState = ref.watch(c2cTradingNotifierProvider);
final asset = assetAsync.valueOrNull;
final currentPrice = priceAsync.valueOrNull?.price ?? '0';
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(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: _darkText),
onPressed: () => context.pop(),
),
title: const Text(
'发布广告',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: _darkText,
),
),
centerTitle: true,
),
body: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 16),
// 交易类型选择
_buildTypeSelector(),
const SizedBox(height: 16),
// 可用余额
_buildBalanceCard(availableShares, availableCash),
const SizedBox(height: 16),
// 价格输入
_buildPriceInput(currentPrice),
const SizedBox(height: 16),
// 数量输入
_buildQuantityInput(availableShares, availableCash, currentPrice),
const SizedBox(height: 16),
// 收款信息(卖单必填)
if (_selectedType == 1) ...[
_buildPaymentInfoInput(),
const SizedBox(height: 16),
],
// 备注
_buildRemarkInput(),
const SizedBox(height: 16),
// 预估信息
_buildEstimateCard(),
const SizedBox(height: 24),
// 发布按钮
_buildPublishButton(c2cState),
const SizedBox(height: 16),
// 提示信息
_buildTips(),
const SizedBox(height: 24),
],
),
),
);
}
Widget _buildTypeSelector() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: () => setState(() => _selectedType = 0),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: _selectedType == 0 ? _green : Colors.transparent,
borderRadius: BorderRadius.circular(8),
),
child: Text(
'我要买入',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: _selectedType == 0 ? Colors.white : _grayText,
),
),
),
),
),
Expanded(
child: GestureDetector(
onTap: () => setState(() => _selectedType = 1),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: _selectedType == 1 ? _red : Colors.transparent,
borderRadius: BorderRadius.circular(8),
),
child: Text(
'我要卖出',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: _selectedType == 1 ? Colors.white : _grayText,
),
),
),
),
),
],
),
);
}
Widget _buildBalanceCard(String availableShares, String availableCash) {
// C2C交易的是积分值买入广告需要买家有绿积分积分值来支付
// 卖出广告需要卖家有积分值来出售
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'可用积分值',
style: TextStyle(fontSize: 14, color: _grayText),
),
Text(
formatAmount(availableCash),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: _green,
),
),
],
),
);
}
Widget _buildPriceInput(String currentPrice) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
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 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),
),
onChanged: (_) => setState(() {}),
),
],
),
);
}
Widget _buildQuantityInput(
String availableShares,
String availableCash,
String currentPrice,
) {
// C2C交易的是积分值
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'数量',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: _darkText,
),
),
const SizedBox(height: 12),
TextField(
controller: _quantityController,
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),
),
suffixIcon: TextButton(
onPressed: () {
// 填入全部可用积分值
_quantityController.text = availableCash;
setState(() {});
},
child: const Text(
'全部',
style: TextStyle(
color: _orange,
fontWeight: FontWeight.w500,
),
),
),
suffixText: '积分值',
suffixStyle: const TextStyle(color: _grayText, fontSize: 14),
),
onChanged: (_) => setState(() {}),
),
],
),
);
}
Widget _buildPaymentInfoInput() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: const [
Text(
'收款信息',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: _darkText,
),
),
SizedBox(width: 8),
Text(
'(必填)',
style: TextStyle(fontSize: 12, color: _red),
),
],
),
const SizedBox(height: 16),
// 收款方式选择(多选)
const Text(
'收款方式(可多选)',
style: TextStyle(fontSize: 14, color: _grayText),
),
const SizedBox(height: 8),
Wrap(
spacing: 12,
runSpacing: 8,
children: [
// 绿积分 - 默认选中且不可取消
_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.0系统ID绿积分必填
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: _systemIdController,
decoration: InputDecoration(
hintText: '请输入您的1.0系统ID',
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: _green.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: const [
Icon(Icons.eco, size: 16, color: _green),
SizedBox(width: 8),
Expanded(
child: Text(
'绿积分支付买家将在1.0系统中向您的ID转账绿积分',
style: TextStyle(fontSize: 12, color: _green),
),
),
],
),
),
],
),
);
}
Widget _buildPaymentMethodCheckbox(String value, String label, IconData icon, {bool isLocked = false}) {
final isSelected = _selectedPaymentMethods.contains(value);
return GestureDetector(
onTap: isLocked ? null : () {
setState(() {
if (isSelected) {
_selectedPaymentMethods.remove(value);
} else {
_selectedPaymentMethods.add(value);
}
});
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: isSelected ? (value == 'GREEN_POINTS' ? _green : _orange) : _bgGray,
borderRadius: BorderRadius.circular(8),
border: isSelected ? null : Border.all(color: Colors.grey.shade300),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
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(
fontSize: 13,
color: isSelected ? Colors.white : _darkText,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
),
if (isLocked) ...[
const SizedBox(width: 4),
Icon(
Icons.lock,
size: 12,
color: isSelected ? Colors.white70 : _grayText,
),
],
],
),
),
);
}
Widget _buildRemarkInput() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'备注 (可选)',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: _darkText,
),
),
const SizedBox(height: 12),
TextField(
controller: _remarkController,
maxLength: 100,
maxLines: 2,
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),
),
counterStyle: const TextStyle(color: _grayText),
),
),
],
),
);
}
Widget _buildEstimateCard() {
final price = double.tryParse(_priceController.text) ?? 0;
final quantity = double.tryParse(_quantityController.text) ?? 0;
final total = price * quantity;
final isBuy = _selectedType == 0;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: _orange.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: _orange.withOpacity(0.2)),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('交易总额', style: TextStyle(fontSize: 14, color: _grayText)),
Text(
'${formatAmount(total.toString())} 积分值',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: _orange,
),
),
],
),
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),
),
],
),
);
}
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;
if (isSell) {
// 卖单必须填写1.0系统ID因为绿积分是必选的
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(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: isValid && !c2cState.isLoading ? _handlePublish : null,
style: ElevatedButton.styleFrom(
backgroundColor: _selectedType == 0 ? _green : _red,
foregroundColor: Colors.white,
disabledBackgroundColor: (_selectedType == 0 ? _green : _red).withOpacity(0.4),
disabledForegroundColor: Colors.white70,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: c2cState.isLoading
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: Text(
_selectedType == 0 ? '发布买入广告' : '发布卖出广告',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
);
}
Widget _buildTips() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: const [
Icon(Icons.info_outline, size: 16, color: _orange),
SizedBox(width: 8),
Text(
'交易说明',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: _orange,
),
),
],
),
const SizedBox(height: 8),
const Text(
'1. 发布广告后,您的积分值将被冻结直到交易完成或取消\n'
'2. 其他用户接单后,需在规定时间内完成交易\n'
'3. 买方需在1.0系统中向卖方转账绿积分\n'
'4. 卖方确认收到绿积分后,积分值自动划转给买方\n'
'5. 如遇问题,请联系客服处理',
style: TextStyle(
fontSize: 12,
color: _grayText,
height: 1.5,
),
),
],
),
);
}
Future<void> _handlePublish() async {
final price = _priceController.text.trim();
final quantity = _quantityController.text.trim();
final remark = _remarkController.text.trim();
final type = _selectedType == 0 ? 'BUY' : 'SELL';
final isSell = _selectedType == 1;
// 卖单验证收款信息
if (isSell) {
// 必须填写1.0系统ID绿积分是必选的
if (_systemIdController.text.trim().isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请填写1.0系统ID'), 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 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>(
context: context,
builder: (context) => AlertDialog(
title: Text(_selectedType == 0 ? '确认发布买入广告' : '确认发布卖出广告'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('单价: $price 积分值'),
const SizedBox(height: 8),
Text('数量: $quantity 积分值'),
const SizedBox(height: 8),
Text(
'总额: ${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('收款方式: $paymentMethodTexts'),
const SizedBox(height: 4),
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),
const Text(
'发布后,您的积分值将被冻结',
style: TextStyle(fontSize: 12, color: _grayText),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: Text(
'确认发布',
style: TextStyle(color: _selectedType == 0 ? _green : _red),
),
),
],
),
);
if (confirmed != true) return;
final notifier = ref.read(c2cTradingNotifierProvider.notifier);
final success = await notifier.createOrder(
type: type,
price: price,
quantity: quantity,
// 收款信息(卖单时传递)
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,
);
if (success && mounted) {
// 刷新列表
ref.invalidate(c2cOrdersProvider(type));
ref.invalidate(myC2cOrdersProvider);
// 刷新资产
final user = ref.read(userNotifierProvider);
ref.invalidate(accountAssetProvider(user.accountSequence ?? ''));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('广告发布成功'),
backgroundColor: _green,
),
);
context.pop();
} else if (mounted) {
final error = ref.read(c2cTradingNotifierProvider).error;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('发布失败: ${error ?? '未知错误'}'),
backgroundColor: _red,
),
);
}
}
}