Browse Source

feat(usage_stats): 添加积分统计功能并实现会议ASR记录模型

添加用户积分统计显示界面,实现根据服务器响应更新积分逻辑
新增会议音频转文字相关统计模型类
移除视图中的导出数据功能
weicu
lxm 11 months ago
parent
commit
2ef347913b
  1. 113
      lib/data/models/meeting_usage_stats.dart
  2. 46
      lib/data/services/usage_stats_service.dart
  3. 179
      lib/modules/usage/views/translation_stats_view.dart

113
lib/data/models/meeting_usage_stats.dart

@ -0,0 +1,113 @@
const int asrStatusProcessing = 0; // 处理中
const int asrStatusFailed = 1; // 处理失败
const int asrStatusCompleted = 2; // 已完成
// 会议音频文件转文字单次数据
class MeetingAsrRecord {
String id; // 本次ASR唯一标识
int meetingId; // 会议记录ID
String taskId; // 云端任务ID
int audioSeconds; // 音频总时长(秒)
int fileSizeBytes; // 音频文件大小(字节)
String language; // ASR语言
int submitTimestamp; // 提交时间戳(毫秒)
int status; // 状态: 0-处理中, 1-处理失败, 2-已完成
int transcriptBytes; // 转写后的字节数(utf8)
int completeTimestamp; // 完成时间戳(毫秒)
MeetingAsrRecord({
required this.id,
required this.meetingId,
required this.taskId,
required this.audioSeconds,
required this.fileSizeBytes,
required this.language,
required this.submitTimestamp,
required this.status,
required this.transcriptBytes,
required this.completeTimestamp,
});
Map<String, dynamic> toJson() => {
'id': id,
'meetingId': meetingId,
'taskId': taskId,
'audioSeconds': audioSeconds,
'fileSizeBytes': fileSizeBytes,
'language': language,
'submitTimestamp': submitTimestamp,
'status': status,
'transcriptBytes': transcriptBytes,
'completeTimestamp': completeTimestamp,
};
factory MeetingAsrRecord.fromJson(Map<String, dynamic> json) {
return MeetingAsrRecord(
id: json['id'] ?? '',
meetingId: json['meetingId'] ?? 0,
taskId: json['taskId'] ?? '',
audioSeconds: json['audioSeconds'] ?? 0,
fileSizeBytes: json['fileSizeBytes'] ?? 0,
language: json['language'] ?? '',
submitTimestamp: json['submitTimestamp'] ?? 0,
status: json['status'] ?? 0,
transcriptBytes: json['transcriptBytes'] ?? 0,
completeTimestamp: json['completeTimestamp'] ?? 0,
);
}
}
// 会议音频文件转文字单日统计
class MeetingAsrDailyStats {
String date; // 日期字符串: YYYY-MM-DD
String userId; // 用户ID
List<MeetingAsrRecord> items; // 当天所有单次ASR
MeetingAsrDailyStats({
required this.date,
required this.userId,
required this.items,
});
// 计算型聚合,不冗余存储
int get asrCalls => items.length;
int get audioSecondsTotal => items.fold(0, (s, r) => s + r.audioSeconds);
int get audioBytesTotal => items.fold(0, (s, r) => s + r.fileSizeBytes);
int get transcriptBytesTotal =>
items.fold(0, (s, r) => s + r.transcriptBytes);
Map<String, dynamic> toJson() => {
'date': date,
'userId': userId,
'items': items.map((e) => e.toJson()).toList(),
};
factory MeetingAsrDailyStats.fromJson(Map<String, dynamic> json) {
final list = (json['items'] as List? ?? [])
.map((e) => MeetingAsrRecord.fromJson(Map<String, dynamic>.from(e)))
.toList();
return MeetingAsrDailyStats(
date: json['date'] ?? '',
userId: json['userId'] ?? 'anonymous',
items: list,
);
}
}
// 会议音频文件转文字历史统计
class MeetingAsrHistoryStats {
String userId; // 用户ID
int allAsrCalls; // 所有ASR调用次数
int allAudioSeconds; // 所有音频总时长(秒)
int allAudioFileBytes; // 所有音频文件大小(字节)
int allTranscriptBytes; // 所有转写后的字节数(utf8)
MeetingAsrHistoryStats({
required this.userId,
required this.allAsrCalls,
required this.allAudioSeconds,
required this.allAudioFileBytes,
required this.allTranscriptBytes,
});
}

46
lib/data/services/usage_stats_service.dart

@ -5,8 +5,10 @@ import '../models/translation_usage_stats.dart';
import '../../core/utils/logger.dart';
import 'network/api.dart';
import '../models/goods.dart';
import '../models/user_Info.dart';
/// 使用量统计服务
///
class UsageStatsService extends GetxService {
static const String _storageKey = 'usage_stats';
static const String _dailyStatsKey = 'daily_translation_stats';
@ -224,11 +226,55 @@ class UsageStatsService extends GetxService {
final response = await Api.usagesResPoints(params);
Logger.info('lxm---ASR用量上报响应: $response');
_applyUsageResponse(response);
} catch (e) {
Logger.error('记录ASR API调用失败: $e');
}
}
/// 根据服务器用量上报回应,更新用户积分(非总积分)
/// 用量类型:0 AI聊天、1 翻译、2 会议
void _applyUsageResponse(dynamic response) {
try {
if (response is! Map) return;
final usagesObj = response['usages'];
if (usagesObj is! Map) return;
final usages = Map<String, dynamic>.from(usagesObj as Map);
int _asInt(dynamic v) {
if (v == null) return 0;
if (v is int) return v;
if (v is num) return v.toInt();
if (v is String) return int.tryParse(v) ?? 0;
return 0;
}
final Map<String, dynamic> update = {};
usages.forEach((k, v) {
final key = int.tryParse(k.toString());
final val = _asInt(v);
if (key == null) return;
switch (key) {
case 0:
update['aichatintegral'] = val;
break;
case 1:
update['tradeintegral'] = val;
break;
case 2:
update['meetintegral'] = val;
break;
}
});
if (update.isNotEmpty) {
User.instance.updateUserInfo(update);
Logger.info('积分更新: $update');
}
} catch (e) {
Logger.error('解析用量回应并更新积分失败: $e');
}
}
/// 记录TTS API调用
void recordTtsApiCall({
required String text,

179
lib/modules/usage/views/translation_stats_view.dart

@ -3,6 +3,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../controllers/translation_stats_controller.dart';
import '../../../core/theme/app_colors.dart';
import '../../../data/models/user_Info.dart';
class TranslationStatsView extends GetView<TranslationStatsController> {
const TranslationStatsView({Key? key}) : super(key: key);
@ -49,32 +50,32 @@ class TranslationStatsView extends GetView<TranslationStatsController> {
),
onPressed: controller.refreshData,
),
PopupMenuButton<String>(
icon: Icon(
Icons.more_vert,
color: isDarkMode ? Colors.white : Colors.black,
),
onSelected: (value) {
switch (value) {
case 'cleanup':
controller.cleanupOldData();
break;
case 'export':
controller.exportStats();
break;
}
},
itemBuilder: (context) => const [
PopupMenuItem(
value: 'cleanup',
child: Text('清理过期数据'),
),
PopupMenuItem(
value: 'export',
child: Text('导出数据'),
),
],
),
// PopupMenuButton<String>(
// icon: Icon(
// Icons.more_vert,
// color: isDarkMode ? Colors.white : Colors.black,
// ),
// onSelected: (value) {
// switch (value) {
// case 'cleanup':
// controller.cleanupOldData();
// break;
// // case 'export':
// // controller.exportStats();
// // break;
// }
// },
// itemBuilder: (context) => const [
// PopupMenuItem(
// value: 'cleanup',
// child: Text('清理过期数据'),
// ),
// // PopupMenuItem(
// // value: 'export',
// // child: Text('导出数据'),
// // ),
// ],
// ),
],
);
}
@ -99,6 +100,8 @@ class TranslationStatsView extends GetView<TranslationStatsController> {
SizedBox(height: 16.h),
_buildOverviewCard(isDarkMode),
SizedBox(height: 16.h),
_buildIntegralCard(isDarkMode),
SizedBox(height: 16.h),
_buildApiCallsCard(isDarkMode),
SizedBox(height: 16.h),
_buildDataVolumeCard(isDarkMode),
@ -300,7 +303,6 @@ class TranslationStatsView extends GetView<TranslationStatsController> {
isDarkMode,
),
),
],
)),
SizedBox(height: 12.h),
@ -422,6 +424,127 @@ class TranslationStatsView extends GetView<TranslationStatsController> {
);
}
Widget _buildIntegralCard(bool isDarkMode) {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: isDarkMode ? AppColors.cardBackground : Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.stacked_line_chart,
color: AppColors.primary,
size: 20.sp,
),
SizedBox(width: 8.w),
Text(
'积分统计',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
],
),
SizedBox(height: 16.h),
Obx(() {
final _ = User.refreshTick.value;
final u = User.instance;
return Column(
children: [
_buildIntegralRow(
'AI聊天积分',
controller.formatNumber(u.aichatintegral),
Icons.chat_bubble_outline,
isDarkMode,
),
SizedBox(height: 12.h),
_buildIntegralRow(
'AI聊天总积分',
controller.formatNumber(u.aichattotalintegral),
Icons.chat_outlined,
isDarkMode,
),
SizedBox(height: 12.h),
_buildIntegralRow(
'翻译积分',
controller.formatNumber(u.tradeintegral),
Icons.translate,
isDarkMode,
),
SizedBox(height: 12.h),
_buildIntegralRow(
'翻译总积分',
controller.formatNumber(u.tradetotalintegral),
Icons.g_translate,
isDarkMode,
),
SizedBox(height: 12.h),
_buildIntegralRow(
'会议积分',
controller.formatNumber(u.meetintegral),
Icons.groups,
isDarkMode,
),
SizedBox(height: 12.h),
_buildIntegralRow(
'会议总积分',
controller.formatNumber(u.meettotalintegral),
Icons.meeting_room,
isDarkMode,
),
],
);
}),
],
),
);
}
Widget _buildIntegralRow(
String label, String value, IconData icon, bool isDarkMode) {
return Row(
children: [
Icon(
icon,
color: AppColors.primary,
size: 18.sp,
),
SizedBox(width: 12.w),
Expanded(
child: Text(
label,
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white : Colors.black,
),
),
),
Text(
value,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
],
);
}
Widget _buildApiCallRow(
String label, int count, IconData icon, bool isDarkMode) {
return Row(
@ -747,4 +870,4 @@ class TranslationStatsView extends GetView<TranslationStatsController> {
],
);
}
}
}

Loading…
Cancel
Save