添加 jwt_decoder 依赖;更新 AuthApi 枚举以支持获取录像列表;新增录像响应模型和相关状态管理;重构 AuthViewModel 以解析和存储 JWT 数据;更新 ScanQrCodePage 以处理用户鉴权和获取录像列表。

This commit is contained in:
2026-07-14 10:16:31 +08:00
parent 45000b4188
commit 16ad7b253c
9 changed files with 249 additions and 14 deletions
+65
View File
@@ -14,3 +14,68 @@ class GetTokenResModel {
return {'deviceAccessToken': deviceAccessToken};
}
}
/// 查看录像响应模型
class GetRecordListResModel {
String? path;
List<RecordListItem>? items;
GetRecordListResModel({this.path, this.items});
factory GetRecordListResModel.fromJson(Map<String, dynamic> json) =>
GetRecordListResModel(
path: json['path'],
items: json['items'] == null
? []
: List<RecordListItem>.from(
json['items']!.map((x) => RecordListItem.fromJson(x)),
),
);
Map<String, dynamic> toJson() => {
'path': path,
'items': items == null
? []
: List<dynamic>.from(items!.map((x) => x.toJson())),
};
}
class RecordListItem {
String? name;
String? path;
String? type;
int? size;
DateTime? modTime;
String? url;
String? extension;
RecordListItem({
this.name,
this.path,
this.type,
this.size,
this.modTime,
this.url,
this.extension,
});
factory RecordListItem.fromJson(Map<String, dynamic> json) => RecordListItem(
name: json['name'] ?? '',
path: json['path'] ?? '',
type: json['type'] ?? '',
size: json['size'] ?? 0,
modTime: json['modTime'] == null ? null : DateTime.parse(json['modTime']),
url: json['url'] ?? '',
extension: json['extension'] ?? '',
);
Map<String, dynamic> toJson() => {
'name': name,
'path': path,
'type': type,
'size': size,
'modTime': modTime?.toIso8601String(),
'url': url,
'extension': extension,
};
}
+56
View File
@@ -0,0 +1,56 @@
// To parse this JSON data, do
//
// final jwtDecodedData = jwtDecodedDataFromJson(jsonString);
import 'dart:convert';
JwtDecodedData jwtDecodedDataFromJson(String str) =>
JwtDecodedData.fromJson(json.decode(str));
String jwtDecodedDataToJson(JwtDecodedData data) => json.encode(data.toJson());
class JwtDecodedData {
String? authType;
String? deviceCode;
int? deviceId;
String? deviceRole;
String? eventName;
double? oId;
List<double>? oIds;
double? organizerId;
JwtDecodedData({
this.authType,
this.deviceCode,
this.deviceId,
this.deviceRole,
this.eventName,
this.oId,
this.oIds,
this.organizerId,
});
factory JwtDecodedData.fromJson(Map<String, dynamic> json) => JwtDecodedData(
authType: json["authType"],
deviceCode: json["deviceCode"],
deviceId: json["deviceId"],
deviceRole: json["deviceRole"],
eventName: json["eventName"],
oId: json["oId"]?.toDouble(),
oIds: json["oIds"] == null
? []
: List<double>.from(json["oIds"]!.map((x) => x?.toDouble())),
organizerId: json["organizerId"]?.toDouble(),
);
Map<String, dynamic> toJson() => {
"authType": authType,
"deviceCode": deviceCode,
"deviceId": deviceId,
"deviceRole": deviceRole,
"eventName": eventName,
"oId": oId,
"oIds": oIds == null ? [] : List<dynamic>.from(oIds!.map((x) => x)),
"organizerId": organizerId,
};
}
+1 -1
View File
@@ -24,7 +24,7 @@ class _AuthPageWidgetState extends ConsumerState<AuthPageWidget> {
_controller = TextEditingController();
_controller?.text = '555';
WidgetsBinding.instance.addPostFrameCallback((_) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
final token = AppStorage.getString(StorageKeys.authToken);
if (token?.isNotEmpty ?? false) {
AppNavigator.pushReplacement(const ScanQrCodePage());
+16 -3
View File
@@ -1,7 +1,5 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:recording_tool/app/config/api_common.dart';
import 'package:recording_tool/core/cache/app_storage.dart';
import 'package:recording_tool/core/cache/storage_keys.dart';
import 'package:recording_tool/core/network/providers/dio_providers.dart';
import 'package:recording_tool/core/utils/device_utils.dart';
import 'package:recording_tool/features/auth/model/model_auth.dart';
@@ -25,7 +23,22 @@ class AuthServer {
throw const FormatException('登录响应缺少 TOKEN');
}
await AppStorage.setString(StorageKeys.authToken, data.deviceAccessToken);
return data;
}
/// 获取赛事列表
/// [path] 赛事目录
static Future<GetRecordListResModel> getRecordList(
Ref ref,
String path,
) async {
final apiClient = ref.read(apiClientProvider);
final data = await apiClient.get<GetRecordListResModel>(
'http://sheling.local:9001/${AuthApi.getRecordList.path}',
queryParameters: {'path': path},
parser: (json) =>
GetRecordListResModel.fromJson(json as Map<String, dynamic>),
);
return data;
}
}
+23 -3
View File
@@ -1,13 +1,33 @@
import 'package:recording_tool/features/auth/model/model_auth.dart';
import 'package:recording_tool/features/auth/model/model_jwt.dart';
class AuthState {
const AuthState({this.isLoading = false, this.errorMessage});
const AuthState({
this.isLoading = false,
this.errorMessage,
this.jwtDecodedData,
this.recordList,
});
final bool isLoading;
final String? errorMessage;
AuthState copyWith({bool? isLoading, String? errorMessage}) {
/// 解析后的 TOKEN 数据
final JwtDecodedData? jwtDecodedData;
/// 赛事列表
final List<RecordListItem>? recordList;
AuthState copyWith({
bool? isLoading,
String? errorMessage,
JwtDecodedData? jwtDecodedData,
List<RecordListItem>? recordList,
}) {
return AuthState(
isLoading: isLoading ?? this.isLoading,
errorMessage: errorMessage,
errorMessage: errorMessage ?? this.errorMessage,
jwtDecodedData: jwtDecodedData ?? this.jwtDecodedData,
);
}
}
@@ -1,12 +1,17 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod/legacy.dart';
import 'package:jwt_decoder/jwt_decoder.dart';
import 'package:recording_tool/core/cache/app_storage.dart';
import 'package:recording_tool/core/cache/storage_keys.dart';
import 'package:recording_tool/core/network/api_exception.dart';
import 'package:recording_tool/features/auth/model/model_jwt.dart';
import 'package:recording_tool/features/auth/server/server_auth.dart';
import 'package:recording_tool/features/auth/state/state_auth.dart';
final authProvider = StateNotifierProvider<AuthViewModel, AuthState>((ref) {
return AuthViewModel(ref);
});
final authProvider =
StateNotifierProvider.autoDispose<AuthViewModel, AuthState>((ref) {
return AuthViewModel(ref);
});
class AuthViewModel extends StateNotifier<AuthState> {
AuthViewModel(this._ref) : super(const AuthState());
@@ -22,8 +27,12 @@ class AuthViewModel extends StateNotifier<AuthState> {
state = const AuthState(isLoading: true);
try {
await AuthServer.login(passCode, _ref);
final data = await AuthServer.login(passCode, _ref);
await AppStorage.setString(StorageKeys.authToken, data.deviceAccessToken);
state = const AuthState();
if (data.deviceAccessToken.isNotEmpty) {
parseTokenSetState(data.deviceAccessToken);
}
return true;
} on FormatException catch (error) {
state = AuthState(errorMessage: error.message);
@@ -36,4 +45,24 @@ class AuthViewModel extends StateNotifier<AuthState> {
return false;
}
}
/// 解析 TOKEN,并更新状态
Future<bool> parseTokenSetState(String token) async {
final decoded = JwtDecoder.decode(token);
if (decoded['data'] != null && decoded['data'] is Map<String, dynamic>) {
final data = JwtDecodedData.fromJson(decoded['data']);
state = state.copyWith(jwtDecodedData: data);
return true;
}
return false;
}
/// 获取赛事列表
Future<bool> getRecordList(String eventName) async {
if (eventName.isEmpty) return false;
final data = await AuthServer.getRecordList(_ref, eventName);
if (data.items == null || data.items!.isEmpty) return false;
state = state.copyWith(recordList: data.items);
return true;
}
}