From 27573463341d2d0c400bae3ec96062bea2916134 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Sat, 8 Nov 2025 17:58:55 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=94=AF=E4=BB=98=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/models/user_Info.dart | 57 +++++- lib/data/services/network/api.dart | 9 + lib/data/services/pay_service.dart | 62 ++++++- .../controllers/goods_base_controller.dart | 155 ++++++++++++++++- lib/modules/goods/views/goodsai_view.dart | 37 ++-- lib/modules/goods/views/goodsmeet_view.dart | 162 +++++++++++++++++- lib/modules/goods/views/goodstrade_view.dart | 162 +++++++++++++++++- 7 files changed, 607 insertions(+), 37 deletions(-) diff --git a/lib/data/models/user_Info.dart b/lib/data/models/user_Info.dart index 662e48d3c..719bbfa07 100644 --- a/lib/data/models/user_Info.dart +++ b/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)) .toList() ?? [], // 处理设备列表 ); } + factory User.fronTokenJson(Map json) { // 修复1:安全获取嵌套的 user 字段 final userData = json['user'] as Map?; // 允许空值 @@ -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)) @@ -119,6 +144,29 @@ class User { ); } + updateUserInfo(Map 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 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, diff --git a/lib/data/services/network/api.dart b/lib/data/services/network/api.dart index 19b706bed..4f8b9a258 100644 --- a/lib/data/services/network/api.dart +++ b/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( diff --git a/lib/data/services/pay_service.dart b/lib/data/services/pay_service.dart index 4aa34a614..ac34fbd39 100644 --- a/lib/data/services/pay_service.dart +++ b/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) { + final data = response['data'] as Map; + 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; // 返回微信支付所需的参数 - 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) { + final data = response['data'] as Map; + 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 { diff --git a/lib/modules/goods/controllers/goods_base_controller.dart b/lib/modules/goods/controllers/goods_base_controller.dart index 243d49723..133b09ffd 100644 --- a/lib/modules/goods/controllers/goods_base_controller.dart +++ b/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 _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?; + final user = resp?['user'] as Map?; + 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) { + User.instance + .updateUserInfo(response['user'] as Map); + } + } 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 支付(创建订单并打开授权链接) diff --git a/lib/modules/goods/views/goodsai_view.dart b/lib/modules/goods/views/goodsai_view.dart index fb9dc428d..913efb2ae 100644 --- a/lib/modules/goods/views/goodsai_view.dart +++ b/lib/modules/goods/views/goodsai_view.dart @@ -28,6 +28,8 @@ class GoodsaiView extends GetView { 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 { 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 { ); } - 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 { color: isDarkMode ? Colors.blue[300]! : Colors.blue[600]!), SizedBox(width: 8.w), Text( - '使用进度', + '剩余额度', style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, diff --git a/lib/modules/goods/views/goodsmeet_view.dart b/lib/modules/goods/views/goodsmeet_view.dart index 25cb5e2d5..ff00908e0 100644 --- a/lib/modules/goods/views/goodsmeet_view.dart +++ b/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 { @@ -27,6 +28,8 @@ class GoodsmeetView extends GetView { 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 { } 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 { ); } + 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( + 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), diff --git a/lib/modules/goods/views/goodstrade_view.dart b/lib/modules/goods/views/goodstrade_view.dart index 8b2c619c4..a2025f7be 100644 --- a/lib/modules/goods/views/goodstrade_view.dart +++ b/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 { @@ -27,6 +28,8 @@ class GoodstradeView extends GetView { 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 { } 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 { ); } + 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( + 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),