Browse Source

优化支付系统

newdev_shunjiawei
liwei1dao 11 months ago
parent
commit
cfdd3b8acf
  1. 57
      lib/data/models/user_Info.dart
  2. 9
      lib/data/services/network/api.dart
  3. 62
      lib/data/services/pay_service.dart
  4. 155
      lib/modules/goods/controllers/goods_base_controller.dart
  5. 37
      lib/modules/goods/views/goodsai_view.dart
  6. 162
      lib/modules/goods/views/goodsmeet_view.dart
  7. 162
      lib/modules/goods/views/goodstrade_view.dart

57
lib/data/models/user_Info.dart

@ -1,10 +1,14 @@
import 'package:get_storage/get_storage.dart';
import 'package:get/get.dart';
import '../../../core/utils/logger.dart';
class User {
// 单例实例
static User? _instance;
/// 全局刷新信号(当用户信息更新时 +1,用于驱动UI重建)
static final RxInt refreshTick = 0.obs;
/// 安全地检查用户是否已经登录(即单例是否已初始化)
static bool isLoggedIn() {
return _instance != null;
@ -41,7 +45,6 @@ class User {
String phonemac;
String password;
int viplv = 0; //添加会员等级字段,0表示普通用户
// 新增的会员与用量统计字段
int viptime = 0; // 会员到期/剩余时间(秒/毫秒,按后端定义)
int tradetotaltime = 0; // 翻译总时长
int meettotaltime = 0; // 会议总时长
@ -63,6 +66,13 @@ class User {
required this.phonemac,
required this.password,
required this.viplv,
required this.viptime,
required this.aichattotaltimer,
required this.meettotaltime,
required this.tradetotaltime,
required this.aichattimer,
required this.meettime,
required this.tradetime,
required this.devices,
});
@ -79,20 +89,28 @@ class User {
name: (userData?['name'] as String?) ?? '默认名称', // 提供默认值
uid: (userData?['uid'] as String?) ?? '',
avatar: (userData?['avatar'] as String?) ?? '',
language: (userData?['language'] as String?) ?? 'en',
mail: (userData?['mail'] as String?) ?? '',
phone: (userData?['phone'] as String?) ?? '',
phonemac: (userData?['phonemac'] as String?) ?? '',
password: (userData?['password'] as String?) ?? '',
viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值
viptime: (userData?['viptime'] as int?) ?? 0, // 提供默认值
aichattotaltimer: (userData?['aichattotaltimer'] as int?) ?? 0,
meettotaltime: (userData?['meettotaltime'] as int?) ?? 0,
tradetotaltime: (userData?['tradetotaltime'] as int?) ?? 0,
aichattimer: (userData?['aichattimer'] as int?) ?? 0,
meettime: (userData?['meettime'] as int?) ?? 0,
tradetime: (userData?['tradetime'] as int?) ?? 0,
devices: devicesData
?.map((e) => UserDevice.fromJson(e as Map<String, dynamic>))
.toList() ??
[], // 处理设备列表
);
}
factory User.fronTokenJson(Map<String, dynamic> json) {
// 修复1:安全获取嵌套的 user 字段
final userData = json['user'] as Map<String, dynamic>?; // 允许空值
@ -111,6 +129,13 @@ class User {
phonemac: (userData?['phonemac'] as String?) ?? '',
password: (userData?['password'] as String?) ?? '',
viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值
viptime: (userData?['viptime'] as int?) ?? 0, // 提供默认值
aichattotaltimer: (userData?['aichattotaltimer'] as int?) ?? 0,
meettotaltime: (userData?['meettotaltime'] as int?) ?? 0,
tradetotaltime: (userData?['tradetotaltime'] as int?) ?? 0,
aichattimer: (userData?['aichattimer'] as int?) ?? 0,
meettime: (userData?['meettime'] as int?) ?? 0,
tradetime: (userData?['tradetime'] as int?) ?? 0,
devices: devicesData != null
? (devicesData)
.map((e) => UserDevice.fromJson(e as Map<String, dynamic>))
@ -119,6 +144,29 @@ class User {
);
}
updateUserInfo(Map<String, dynamic> user) {
name = user['name'] as String? ?? name;
uid = user['uid'] as String? ?? uid;
avatar = user['avatar'] as String? ?? avatar;
language = user['language'] as String? ?? language;
token = user['token'] as String? ?? token;
mail = user['mail'] as String? ?? mail;
phone = user['phone'] as String? ?? phone;
phonemac = user['phonemac'] as String? ?? phonemac;
password = user['password'] as String? ?? password;
viplv = user['viplv'] as int? ?? viplv;
viptime = user['viptime'] as int? ?? viptime;
tradetotaltime = user['tradetotaltime'] as int? ?? tradetotaltime;
meettotaltime = user['meettotaltime'] as int? ?? meettotaltime;
aichattotaltimer = user['aichattotaltimer'] as int? ?? aichattotaltimer;
tradetime = user['tradetime'] as int? ?? tradetime;
meettime = user['meettime'] as int? ?? meettime;
aichattimer = user['aichattimer'] as int? ?? aichattimer;
// 触发全局刷新信号,驱动依赖UI重建
refreshTick.value++;
}
// 完整的JSON序列化
Map<String, dynamic> toJson() => {
'user': {
@ -135,8 +183,7 @@ class User {
'viptime': viptime,
'tradetotaltime': tradetotaltime,
'meettotaltime': meettotaltime,
// 序列化统一使用 aichattotaltime 键
'aichattotaltime': aichattotaltimer,
'aichattotaltimer': aichattotaltimer,
'tradetime': tradetime,
'meettime': meettime,
'aichattimer': aichattimer,

9
lib/data/services/network/api.dart

@ -181,6 +181,15 @@ class Api {
);
}
//询问订单
static payaskorder([params]) {
return DioManager().request(
NWMethod.post,
'/api/home/pay_askorder',
params: params,
);
}
//获取模版数据
static getEchomeetTemplates([params]) {
return DioManager().request(

62
lib/data/services/pay_service.dart

@ -11,6 +11,9 @@ typedef PaymentResultCallback = void Function(bool success, String message);
class PayService extends GetxService {
late FlWX _flwx;
late Tobias _tobias; // 添加 Tobias 实例
String? _lastOrderId; // 最近一次创建的订单号
String? get lastOrderId => _lastOrderId;
@override
void onInit() {
@ -151,9 +154,36 @@ class PayService extends GetxService {
print('创建支付订单: $response');
// 检查响应是否成功
if (response != null) {
// 尝试提取订单号,兼容多种返回字段
String? orderId;
final candidates = [
'order_id',
'orderId',
'out_trade_no',
'outTradeNo',
];
for (final key in candidates) {
final v = response[key];
if (v is String && v.isNotEmpty) {
orderId = v;
break;
}
}
if (orderId == null && response['data'] is Map<String, dynamic>) {
final data = response['data'] as Map<String, dynamic>;
for (final key in candidates) {
final v = data[key];
if (v is String && v.isNotEmpty) {
orderId = v;
break;
}
}
}
_lastOrderId = orderId;
final pay_params = response["pay_params"] as Map<String, dynamic>;
// 返回微信支付所需的参数
return {
final result = {
'appId': pay_params['appid'],
'partnerId': pay_params['partnerid'],
'prepayId': pay_params['prepayid'],
@ -162,6 +192,10 @@ class PayService extends GetxService {
'timestamp': pay_params['timestamp'],
'sign': pay_params['sign'],
};
if (orderId != null) {
result['orderId'] = orderId;
}
return result;
} else {
print('创建支付订单失败: ${response?['message'] ?? '未知错误'}');
return null;
@ -349,6 +383,32 @@ class PayService extends GetxService {
// 检查响应是否成功
if (response != null && response['alipay_pay_params'] != null) {
// 解析订单号并缓存
String? orderId;
final candidates = [
'order_id',
'orderId',
'out_trade_no',
'outTradeNo',
];
for (final key in candidates) {
final v = response[key];
if (v is String && v.isNotEmpty) {
orderId = v;
break;
}
}
if (orderId == null && response['data'] is Map<String, dynamic>) {
final data = response['data'] as Map<String, dynamic>;
for (final key in candidates) {
final v = data[key];
if (v is String && v.isNotEmpty) {
orderId = v;
break;
}
}
}
_lastOrderId = orderId;
// 返回支付宝订单信息字符串
return response['alipay_pay_params'] as String?;
} else {

155
lib/modules/goods/controllers/goods_base_controller.dart

@ -1,10 +1,12 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:voitrans/data/models/user_Info.dart';
import '../../../routes/app_routes.dart';
import '../../../data/services/pay_service.dart';
import '../../../data/models/goods.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../../../data/services/network/dio_manager.dart';
import '../../../data/services/network/api.dart';
/// goods 模块基础控制器,封装通用的商品加载与支付流程
abstract class GoodsBaseController extends GetxController {
@ -238,7 +240,21 @@ abstract class GoodsBaseController extends GetxController {
onResult?.call(false, '创建微信订单失败');
return false;
}
return _payService.wechatPay(paymentData: paymentData, onResult: onResult);
final orderId =
paymentData['orderId'] as String? ?? _payService.lastOrderId;
return _payService.wechatPay(
paymentData: paymentData,
onResult: (success, msg) async {
// 先透传给外部回调
onResult?.call(success, msg);
if (success) {
await _confirmOrderAndShow(orderId,
channel: '微信', initialMessage: msg);
} else {
_showPayResultDialog(false, msg ?? '支付失败');
}
},
);
}
/// 指定支付宝支付(直接下单并拉起支付宝)
@ -256,7 +272,142 @@ abstract class GoodsBaseController extends GetxController {
onResult?.call(false, '创建支付宝订单失败');
return false;
}
return _payService.alipay(orderInfo: orderInfo, onResult: onResult);
final orderId = _payService.lastOrderId; // 创建订单时已缓存
return _payService.alipay(
orderInfo: orderInfo,
onResult: (success, msg) async {
onResult?.call(success, msg);
if (success) {
await _confirmOrderAndShow(orderId,
channel: '支付宝', initialMessage: msg);
} else {
_showPayResultDialog(false, msg ?? '支付失败');
}
},
);
}
Future<void> _confirmOrderAndShow(String? orderId,
{required String channel, String? initialMessage}) async {
// 若无法获取订单号,直接弹成功提示但提醒稍后刷新
if (orderId == null || orderId.isEmpty) {
_showPayResultDialog(true, '${channel}支付成功,订单处理中,请稍后查看。');
return;
}
String message = initialMessage ?? '${channel}支付成功';
bool success = false;
try {
// 简单轮询回查,避免回调延迟
for (int i = 0; i < 5; i++) {
final resp = await Api.payaskorder({'order_id': orderId});
print("查询订单 resp: $resp");
final order = resp?['order'] as Map<String, dynamic>?;
final user = resp?['user'] as Map<String, dynamic>?;
if (order != null && user != null) {
final orderStatus = order["status"];
if (orderStatus == 1) {
User.instance.updateUserInfo(user);
success = true;
break;
}
}
await Future.delayed(const Duration(milliseconds: 1500));
}
// 若标记成功但未拿到用户信息,使用token登录接口回拉用户资料,确保额度刷新
if (success) {
try {
final response = await Api.tokenlogin();
if (response is Map && response['user'] is Map<String, dynamic>) {
User.instance
.updateUserInfo(response['user'] as Map<String, dynamic>);
}
} catch (_) {}
}
} catch (e) {
message = '查询支付结果异常: $e';
}
_showPayResultDialog(success, message);
}
void _showPayResultDialog(bool success, String message) {
final context = Get.context;
final isDark =
context != null && Theme.of(context).brightness == Brightness.dark;
Get.dialog(
Dialog(
insetPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
backgroundColor: isDark ? Colors.grey[900] : Colors.white,
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: success
? (isDark ? Colors.green[800] : Colors.green[100])
: (isDark ? Colors.red[800] : Colors.red[100]),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
success ? Icons.check_circle : Icons.error,
color:
success ? Colors.green[600] : Colors.redAccent[200],
size: 28,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
success ? '支付成功' : '支付失败',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: isDark ? Colors.white : Colors.black,
),
),
const SizedBox(height: 6),
Text(
message,
style: TextStyle(
fontSize: 13,
color: isDark ? Colors.white70 : Colors.grey[700],
),
),
],
),
),
],
),
const SizedBox(height: 16),
Divider(color: isDark ? Colors.white24 : Colors.black12),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Get.back(),
child: const Text('知道了'),
),
],
),
],
),
),
),
barrierDismissible: true,
);
}
/// 指定 PayPal 支付(创建订单并打开授权链接)

37
lib/modules/goods/views/goodsai_view.dart

@ -28,6 +28,8 @@ class GoodsaiView extends GetView<GoodsaiController> {
centerTitle: true,
),
body: Obx(() {
// 访问刷新信号以建立依赖关系:用户信息变化时触发重建
final _ = User.refreshTick.value;
if (controller.loading.value) {
return const Center(child: CircularProgressIndicator());
}
@ -35,25 +37,12 @@ class GoodsaiView extends GetView<GoodsaiController> {
return Center(child: Text(controller.error.value));
}
// 用户用量信息
int totalAi = 0;
int remainAi = 0;
if (User.isLoggedIn()) {
final u = User.instance;
totalAi = u.aichattotaltimer;
remainAi = u.aichattimer;
}
final usedAi =
(totalAi > 0) ? (totalAi - remainAi).clamp(0, totalAi) : 0;
final progress = (totalAi > 0) ? usedAi / totalAi : 0.0;
return ListView(
padding: EdgeInsets.all(16.w),
children: [
_buildHeader(isDarkMode),
SizedBox(height: 12.h),
_buildUsageCard(isDarkMode,
totalAi: totalAi, remainAi: remainAi, progress: progress),
_buildUsageCard(isDarkMode),
SizedBox(height: 16.h),
Text(
'可购买套餐',
@ -146,12 +135,18 @@ class GoodsaiView extends GetView<GoodsaiController> {
);
}
Widget _buildUsageCard(
bool isDarkMode, {
required int totalAi,
required int remainAi,
required double progress,
}) {
Widget _buildUsageCard(bool isDarkMode) {
// 从 User 属性计算 aitimer 数据
int totalAi = 0;
int remainAi = 0;
if (User.isLoggedIn()) {
final u = User.instance;
totalAi = u.aichattotaltimer;
remainAi = u.aichattimer;
}
// 进度显示为“剩余额度比例”,remain/total:
final progress = (totalAi > 0) ? (remainAi / totalAi).clamp(0.0, 1.0) : 0.0;
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
@ -176,7 +171,7 @@ class GoodsaiView extends GetView<GoodsaiController> {
color: isDarkMode ? Colors.blue[300]! : Colors.blue[600]!),
SizedBox(width: 8.w),
Text(
'使用进度',
'剩余额度',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,

162
lib/modules/goods/views/goodsmeet_view.dart

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:voitrans/data/models/goods.dart';
import '../../../data/models/user_Info.dart';
import '../controllers/goodsmeet_controller.dart';
class GoodsmeetView extends GetView<GoodsmeetController> {
@ -27,6 +28,8 @@ class GoodsmeetView extends GetView<GoodsmeetController> {
centerTitle: true,
),
body: Obx(() {
// 访问刷新信号以建立依赖关系:用户信息变化时触发重建
final _ = User.refreshTick.value;
if (controller.loading.value) {
return const Center(child: CircularProgressIndicator());
}
@ -35,14 +38,21 @@ class GoodsmeetView extends GetView<GoodsmeetController> {
}
return ListView.separated(
padding: EdgeInsets.all(16.w),
itemCount: controller.items.length + 2,
itemCount: controller.items.length + 4,
separatorBuilder: (_, __) => SizedBox(height: 12.h),
itemBuilder: (_, index) {
if (index < controller.items.length) {
final item = controller.items[index];
if (index == 0) {
return _buildHeader(isDarkMode);
}
if (index == 1) {
return _buildUsageCard(isDarkMode);
}
final itemIndex = index - 2;
if (itemIndex < controller.items.length) {
final item = controller.items[itemIndex];
return _buildCard(item, isDarkMode);
}
if (index == controller.items.length) {
if (index == controller.items.length + 2) {
return _buildFeaturesSection(isDarkMode);
}
return _buildFooterSection(isDarkMode);
@ -52,6 +62,150 @@ class GoodsmeetView extends GetView<GoodsmeetController> {
);
}
Widget _buildHeader(bool isDarkMode) {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 44.w,
height: 44.w,
decoration: BoxDecoration(
color: isDarkMode
? Colors.pink[900]!.withOpacity(0.3)
: Colors.pink[100]!,
borderRadius: BorderRadius.circular(22.r),
),
child: Icon(Icons.people_alt,
color: isDarkMode ? Colors.pink[300]! : Colors.pink[600]!),
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'会议助手',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w700,
color: isDarkMode ? Colors.white : Colors.black,
),
),
SizedBox(height: 6.h),
Text(
'录音转写、关键词检索与自动总结,提升会议效率。',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[700],
),
),
SizedBox(height: 4.h),
Text(
'适用于商务会议、课堂笔记、访谈纪要等场景。',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[700],
),
),
],
),
),
],
),
);
}
Widget _buildUsageCard(bool isDarkMode) {
int totalAi = 0;
int remainAi = 0;
if (User.isLoggedIn()) {
final u = User.instance;
totalAi = u.meettotaltime;
remainAi = u.meettime;
}
final progress = (totalAi > 0) ? (remainAi / totalAi).clamp(0.0, 1.0) : 0.0;
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.timeline,
color: isDarkMode ? Colors.blue[300]! : Colors.blue[600]!),
SizedBox(width: 8.w),
Text(
'剩余额度',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
],
),
SizedBox(height: 12.h),
LinearProgressIndicator(
value: progress,
minHeight: 8.h,
backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[300],
valueColor: AlwaysStoppedAnimation<Color>(
isDarkMode ? Colors.blue[300]! : Colors.blue[600]!),
),
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'总次数:$totalAi',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[700],
),
),
Text(
'剩余:$remainAi',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[700],
),
),
],
),
],
),
);
}
Widget _buildCard(DBGoods item, bool isDarkMode) {
return Container(
padding: EdgeInsets.all(16.w),

162
lib/modules/goods/views/goodstrade_view.dart

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:voitrans/data/models/goods.dart';
import '../../../data/models/user_Info.dart';
import '../controllers/goodstrade_controller.dart';
class GoodstradeView extends GetView<GoodstradeController> {
@ -27,6 +28,8 @@ class GoodstradeView extends GetView<GoodstradeController> {
centerTitle: true,
),
body: Obx(() {
// 访问刷新信号以建立依赖关系:用户信息变化时触发重建
final _ = User.refreshTick.value;
if (controller.loading.value) {
return const Center(child: CircularProgressIndicator());
}
@ -35,14 +38,21 @@ class GoodstradeView extends GetView<GoodstradeController> {
}
return ListView.separated(
padding: EdgeInsets.all(16.w),
itemCount: controller.items.length + 2,
itemCount: controller.items.length + 4,
separatorBuilder: (_, __) => SizedBox(height: 12.h),
itemBuilder: (_, index) {
if (index < controller.items.length) {
final item = controller.items[index];
if (index == 0) {
return _buildHeader(isDarkMode);
}
if (index == 1) {
return _buildUsageCard(isDarkMode);
}
final itemIndex = index - 2;
if (itemIndex < controller.items.length) {
final item = controller.items[itemIndex];
return _buildCard(item, isDarkMode);
}
if (index == controller.items.length) {
if (index == controller.items.length + 2) {
return _buildFeaturesSection(isDarkMode);
}
return _buildFooterSection(isDarkMode);
@ -52,6 +62,150 @@ class GoodstradeView extends GetView<GoodstradeController> {
);
}
Widget _buildHeader(bool isDarkMode) {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 44.w,
height: 44.w,
decoration: BoxDecoration(
color: isDarkMode
? Colors.indigo[900]!.withOpacity(0.3)
: Colors.indigo[100]!,
borderRadius: BorderRadius.circular(22.r),
),
child: Icon(Icons.translate,
color: isDarkMode ? Colors.indigo[300]! : Colors.indigo[600]!),
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'翻译功能',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w700,
color: isDarkMode ? Colors.white : Colors.black,
),
),
SizedBox(height: 6.h),
Text(
'多语种实时同传与文本翻译,低延时语音播报。',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[700],
),
),
SizedBox(height: 4.h),
Text(
'适用于出行、商务、演讲与教学等场景。',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[700],
),
),
],
),
),
],
),
);
}
Widget _buildUsageCard(bool isDarkMode) {
int totalAi = 0;
int remainAi = 0;
if (User.isLoggedIn()) {
final u = User.instance;
totalAi = u.tradetotaltime;
remainAi = u.tradetime;
}
final progress = (totalAi > 0) ? (remainAi / totalAi).clamp(0.0, 1.0) : 0.0;
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.timeline,
color: isDarkMode ? Colors.blue[300]! : Colors.blue[600]!),
SizedBox(width: 8.w),
Text(
'剩余额度',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
],
),
SizedBox(height: 12.h),
LinearProgressIndicator(
value: progress,
minHeight: 8.h,
backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[300],
valueColor: AlwaysStoppedAnimation<Color>(
isDarkMode ? Colors.blue[300]! : Colors.blue[600]!),
),
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'总次数:$totalAi',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[700],
),
),
Text(
'剩余:$remainAi',
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[700],
),
),
],
),
],
),
);
}
Widget _buildCard(DBGoods item, bool isDarkMode) {
return Container(
padding: EdgeInsets.all(16.w),

Loading…
Cancel
Save