refactor: 完成项目架构重构,统一模块导入路径

- 清理大量废弃的 barrel 导出文件,移除冗余的中间导出层
- 修复所有相对路径导入错误,统一调整为扁平化模块引用
- 更新多平台 pubspec 版本号与依赖库版本
- 补充后端功能问题管理后台与脚本工具
- 调整部分页面的快捷方式文案适配新功能
- 更新部分翻译覆盖率与API文档
This commit is contained in:
Developer
2026-06-12 08:48:33 +08:00
parent 33e347dea7
commit f91be94e9c
560 changed files with 28489 additions and 14780 deletions

View File

@@ -1,603 +1,9 @@
/// ============================================================
/// 闲言APP — 用户数据模型
/// 闲言APP — 用户数据模型Re-export
/// 创建时间: 2026-04-28
/// 更新时间: 2026-06-05
/// 作用: 用户信息数据模型,对应后端 tool_user 表
/// 上次更新: UserDevice新增isActiveRecently字段统一后端7天活跃判定
/// 更新时间: 2026-06-12
/// 作用: 重新导出 core/models/user_model.dart保持现有导入者兼容
/// 上次更新: 实际定义已移至 core/models/user_model.dart
/// ============================================================
class UserModel {
const UserModel({
required this.id,
required this.username,
this.nickname = '',
this.avatar = '',
this.avatarUrl = '',
this.email = '',
this.mobile = '',
this.score = 0,
this.level = 1,
this.exp = 0,
this.expToNext = 0,
this.expProgress = 0.0,
this.money = '0.00',
this.titleId = 1,
this.signinDays = 0,
this.lastSigninDate,
this.noteLimit = 50,
this.articleCount = 0,
this.bio = '',
this.token,
this.title,
this.verification,
this.isOnline = 0,
this.vip,
this.cloudSpace,
this.devices = const [],
this.extra,
this.profileSlug = '',
this.secQuestion = 0,
this.secQuestionText = '',
});
final int id;
final String username;
final String nickname;
final String avatar;
final String avatarUrl;
final String email;
final String mobile;
final int score;
final int level;
final int exp;
final int expToNext;
final double expProgress;
final String money;
final int titleId;
final int signinDays;
final String? lastSigninDate;
final int noteLimit;
final int articleCount;
final String bio;
final String? token;
final UserTitle? title;
final UserVerification? verification;
final int isOnline;
final UserVip? vip;
final UserCloudSpace? cloudSpace;
final List<UserDevice> devices;
final UserExtra? extra;
final String profileSlug;
final int secQuestion;
final String secQuestionText;
bool get hasSecQuestion => secQuestion > 0;
String get displayName => nickname.isNotEmpty ? nickname : username;
static const int _maxAvatarUrlLength = 2048;
String get avatarDisplayUrl {
if (isAvatarUnderReview) return '';
final raw = _resolveAvatarUrl();
if (raw.isEmpty) return '';
if (raw.length > _maxAvatarUrlLength) {
return raw.substring(0, _maxAvatarUrlLength);
}
return raw;
}
bool get isAvatarUnderReview {
if (avatarUrl.isNotEmpty && avatarUrl.startsWith('http')) return true;
if (avatar.isNotEmpty && avatar.startsWith('http')) return true;
return false;
}
String _resolveAvatarUrl() {
if (avatarUrl.isNotEmpty) {
if (avatarUrl.startsWith('data:')) return '';
if (!_isValidUrl(avatarUrl)) return '';
if (avatarUrl.startsWith('http')) return avatarUrl;
return 'https://tools.wktyl.com$avatarUrl';
}
if (avatar.isNotEmpty) {
if (avatar.startsWith('data:')) return '';
if (!_isValidUrl(avatar)) return '';
if (avatar.startsWith('http')) return avatar;
return 'https://tools.wktyl.com$avatar';
}
return '';
}
static bool _isValidUrl(String url) {
if (url.isEmpty) return false;
if (url.startsWith('http://') || url.startsWith('https://')) {
try {
final uri = Uri.parse(url);
return uri.host.isNotEmpty;
} catch (_) {
return false;
}
}
return url.startsWith('/');
}
@Deprecated('Use avatarDisplayUrl instead')
String get avatarUrlCompat => avatarDisplayUrl;
bool get isVip => vip?.isVip ?? false;
bool get getIsOnline => isOnline == 1;
/// 从JSON解析sec_question兼容顶层和extra嵌套两种路径
/// 服务端UserCenter接口返回: extra.sec_question.question_id
/// 服务端changeSecQuestion接口返回: sec_question (顶层int)
static int _parseSecQuestion(Map<String, dynamic> json) {
final topLevel = json['sec_question'];
if (topLevel is int && topLevel > 0) return topLevel;
final extra = json['extra'];
if (extra is Map<String, dynamic>) {
final nested = extra['sec_question'];
if (nested is int && nested > 0) return nested;
if (nested is Map<String, dynamic>) {
return nested['question_id'] as int? ?? 0;
}
}
return 0;
}
/// 从JSON解析sec_question_text兼容顶层和extra嵌套两种路径
static String _parseSecQuestionText(Map<String, dynamic> json) {
final topLevel = json['sec_question_text'];
if (topLevel is String && topLevel.isNotEmpty) return topLevel;
final extra = json['extra'];
if (extra is Map<String, dynamic>) {
final nested = extra['sec_question'];
if (nested is Map<String, dynamic>) {
final text = nested['question_text'];
if (text is String && text.isNotEmpty) return text;
}
}
return '';
}
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
id: (json['id'] as num?)?.toInt() ?? 0,
username: json['username'] as String? ?? '',
nickname: json['nickname'] as String? ?? '',
avatar: json['avatar'] as String? ?? '',
avatarUrl: json['avatar_url'] as String? ?? '',
email: json['email'] as String? ?? '',
mobile: json['mobile'] as String? ?? '',
score: (json['score'] as num?)?.toInt() ?? 0,
level: (json['level'] as num?)?.toInt() ?? 1,
exp: (json['exp'] as num?)?.toInt() ?? 0,
expToNext: (json['exp_to_next'] as num?)?.toInt() ?? 0,
expProgress: (json['exp_progress'] as num?)?.toDouble() ?? 0.0,
money: json['money']?.toString() ?? '0.00',
titleId: (json['title_id'] as num?)?.toInt() ?? 1,
signinDays: (json['signin_days'] as num?)?.toInt() ?? 0,
lastSigninDate: json['last_signin_date'] as String?,
noteLimit: (json['note_limit'] as num?)?.toInt() ?? 50,
articleCount: (json['article_count'] as num?)?.toInt() ?? 0,
bio: json['bio'] as String? ?? '',
token: json['token'] as String?,
title: json['title'] != null
? UserTitle.fromJson(json['title'] as Map<String, dynamic>)
: null,
verification: json['verification'] != null
? UserVerification.fromJson(
json['verification'] as Map<String, dynamic>,
)
: null,
isOnline: json['is_online'] as int? ?? 0,
vip: json['vip'] != null
? UserVip.fromJson(json['vip'] as Map<String, dynamic>)
: null,
cloudSpace: json['cloud_space'] != null
? UserCloudSpace.fromJson(json['cloud_space'] as Map<String, dynamic>)
: null,
devices: json['devices'] != null
? (json['devices'] as List<dynamic>)
.map((e) => UserDevice.fromJson(e as Map<String, dynamic>))
.toList()
: [],
extra: json['extra'] != null
? UserExtra.fromJson(json['extra'] as Map<String, dynamic>)
: null,
profileSlug: json['profile_slug'] as String? ?? '',
secQuestion: _parseSecQuestion(json),
secQuestionText: _parseSecQuestionText(json),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'username': username,
'nickname': nickname,
'avatar': avatar,
'avatar_url': avatarUrl,
'email': email,
'mobile': mobile,
'score': score,
'level': level,
'exp': exp,
'exp_to_next': expToNext,
'exp_progress': expProgress,
'money': money,
'title_id': titleId,
'signin_days': signinDays,
'last_signin_date': lastSigninDate,
'note_limit': noteLimit,
'article_count': articleCount,
'bio': bio,
'token': token,
'title': title?.toJson(),
'verification': verification?.toJson(),
'is_online': isOnline,
'vip': vip?.toJson(),
'cloud_space': cloudSpace?.toJson(),
'devices': devices.map((e) => e.toJson()).toList(),
'extra': extra?.toJson(),
'profile_slug': profileSlug,
'sec_question': secQuestion,
'sec_question_text': secQuestionText,
'is_avatar_under_review': isAvatarUnderReview,
};
}
UserModel copyWith({
int? id,
String? username,
String? nickname,
String? avatar,
String? avatarUrl,
String? email,
String? mobile,
int? score,
int? level,
int? exp,
int? expToNext,
double? expProgress,
String? money,
int? titleId,
int? signinDays,
String? lastSigninDate,
int? noteLimit,
int? articleCount,
String? bio,
String? token,
UserTitle? title,
UserVerification? verification,
int? isOnline,
UserVip? vip,
UserCloudSpace? cloudSpace,
List<UserDevice>? devices,
UserExtra? extra,
String? profileSlug,
int? secQuestion,
String? secQuestionText,
}) {
return UserModel(
id: id ?? this.id,
username: username ?? this.username,
nickname: nickname ?? this.nickname,
avatar: avatar ?? this.avatar,
avatarUrl: avatarUrl ?? this.avatarUrl,
email: email ?? this.email,
mobile: mobile ?? this.mobile,
score: score ?? this.score,
level: level ?? this.level,
exp: exp ?? this.exp,
expToNext: expToNext ?? this.expToNext,
expProgress: expProgress ?? this.expProgress,
money: money ?? this.money,
titleId: titleId ?? this.titleId,
signinDays: signinDays ?? this.signinDays,
lastSigninDate: lastSigninDate ?? this.lastSigninDate,
noteLimit: noteLimit ?? this.noteLimit,
articleCount: articleCount ?? this.articleCount,
bio: bio ?? this.bio,
token: token ?? this.token,
title: title ?? this.title,
verification: verification ?? this.verification,
isOnline: isOnline ?? this.isOnline,
vip: vip ?? this.vip,
cloudSpace: cloudSpace ?? this.cloudSpace,
devices: devices ?? this.devices,
extra: extra ?? this.extra,
profileSlug: profileSlug ?? this.profileSlug,
secQuestion: secQuestion ?? this.secQuestion,
secQuestionText: secQuestionText ?? this.secQuestionText,
);
}
}
class UserTitle {
const UserTitle({
required this.id,
required this.name,
this.icon = '',
this.color = '#999999',
});
final int id;
final String name;
final String icon;
final String color;
Map<String, dynamic> toJson() {
return {'id': id, 'name': name, 'icon': icon, 'color': color};
}
factory UserTitle.fromJson(Map<String, dynamic> json) {
return UserTitle(
id: json['id'] as int? ?? 1,
name: json['name'] as String? ?? '新手',
icon: json['icon'] as String? ?? '',
color: json['color'] as String? ?? '#999999',
);
}
}
class UserVerification {
const UserVerification({this.email = 0, this.mobile = 0});
final int email;
final int mobile;
bool get isEmailVerified => email == 1;
bool get isMobileVerified => mobile == 1;
Map<String, dynamic> toJson() => {'email': email, 'mobile': mobile};
factory UserVerification.fromJson(Map<String, dynamic> json) {
return UserVerification(
email: (json['email'] as num?)?.toInt() ?? 0,
mobile: (json['mobile'] as num?)?.toInt() ?? 0,
);
}
}
class UserVip {
const UserVip({
this.isVip = false,
this.startTime = 0,
this.endTime = 0,
this.startDate = '',
this.endDate = '',
});
final bool isVip;
final int startTime;
final int endTime;
final String startDate;
final String endDate;
bool get isActive => isVip && endTime > 0;
Map<String, dynamic> toJson() {
return {
'is_vip': isVip,
'start_time': startTime,
'end_time': endTime,
'start_date': startDate,
'end_date': endDate,
};
}
factory UserVip.fromJson(Map<String, dynamic> json) {
return UserVip(
isVip: json['is_vip'] as bool? ?? false,
startTime: json['start_time'] as int? ?? 0,
endTime: json['end_time'] as int? ?? 0,
startDate: json['start_date'] as String? ?? '',
endDate: json['end_date'] as String? ?? '',
);
}
}
class UserCloudSpace {
const UserCloudSpace({
this.total = 0,
this.used = 0,
this.free = 0,
this.totalHuman = '',
this.usedHuman = '',
this.usagePercent = 0.0,
});
final int total;
final int used;
final int free;
final String totalHuman;
final String usedHuman;
final double usagePercent;
Map<String, dynamic> toJson() {
return {
'total': total,
'used': used,
'free': free,
'total_human': totalHuman,
'used_human': usedHuman,
'usage_percent': usagePercent,
};
}
factory UserCloudSpace.fromJson(Map<String, dynamic> json) {
return UserCloudSpace(
total: (json['total'] as num?)?.toInt() ?? 0,
used: (json['used'] as num?)?.toInt() ?? 0,
free: (json['free'] as num?)?.toInt() ?? 0,
totalHuman: json['total_human'] as String? ?? '',
usedHuman: json['used_human'] as String? ?? '',
usagePercent: (json['usage_percent'] as num?)?.toDouble() ?? 0.0,
);
}
}
class UserDevice {
const UserDevice({
this.id = 0,
this.deviceName = '',
this.deviceModel = '',
this.platform = '',
this.appName = '',
this.ip = '',
this.ipCity = '',
this.ipRange = '',
this.lastActiveTime = 0,
this.isOnline = 0,
this.createtime = 0,
this.lastActiveText = '',
this.createtimeText = '',
this.isActiveRecently = 0,
});
final int id;
final String deviceName;
final String deviceModel;
final String platform;
final String appName;
final String ip;
final String ipCity;
final String ipRange;
final int lastActiveTime;
final int isOnline;
final int createtime;
final String lastActiveText;
final String createtimeText;
/// 7天内是否活跃后端计算
final int isActiveRecently;
bool get getIsOnline => isOnline == 1;
/// 7天内是否活跃在线后端计算
bool get getIsActiveRecently => isActiveRecently == 1;
/// 前端兜底:根据 lastActiveTime 判断7天内是否活跃
/// 解决后端 is_online 5分钟超时置0 导致 onlineCount 始终为0的问题
bool get getIsActiveByTime {
if (lastActiveTime <= 0) return false;
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
return (now - lastActiveTime) < 7 * 86400;
}
bool get hasIpCity => ipCity.isNotEmpty;
bool get hasIpRange => ipRange.isNotEmpty;
String get displayLocation => hasIpCity ? '📍 $ipCity' : '';
Map<String, dynamic> toJson() {
return {
'id': id,
'device_name': deviceName,
'device_model': deviceModel,
'platform': platform,
'app_name': appName,
'ip': ip,
'ip_city': ipCity,
'ip_range': ipRange,
'last_active_time': lastActiveTime,
'is_online': isOnline,
'createtime': createtime,
'last_active_text': lastActiveText,
'createtime_text': createtimeText,
'is_active_recently': isActiveRecently,
};
}
factory UserDevice.fromJson(Map<String, dynamic> json) {
return UserDevice(
id: (json['id'] as num?)?.toInt() ?? 0,
deviceName: json['device_name'] as String? ?? '',
deviceModel: json['device_model'] as String? ?? '',
platform: json['platform'] as String? ?? '',
appName: json['app_name'] as String? ?? '',
ip: json['ip'] as String? ?? '',
ipCity: json['ip_city'] as String? ?? '',
ipRange: json['ip_range'] as String? ?? '',
lastActiveTime: (json['last_active_time'] as num?)?.toInt() ?? 0,
isOnline: (json['is_online'] as num?)?.toInt() ?? 0,
createtime: (json['createtime'] as num?)?.toInt() ?? 0,
lastActiveText: json['last_active_text'] as String? ?? '',
createtimeText: json['createtime_text'] as String? ?? '',
isActiveRecently: (json['is_active_recently'] as num?)?.toInt() ?? 0,
);
}
UserDevice copyWith({
int? id,
String? deviceName,
String? deviceModel,
String? platform,
String? appName,
String? ip,
String? ipCity,
String? ipRange,
int? lastActiveTime,
int? isOnline,
int? createtime,
String? lastActiveText,
String? createtimeText,
int? isActiveRecently,
}) {
return UserDevice(
id: id ?? this.id,
deviceName: deviceName ?? this.deviceName,
deviceModel: deviceModel ?? this.deviceModel,
platform: platform ?? this.platform,
appName: appName ?? this.appName,
ip: ip ?? this.ip,
ipCity: ipCity ?? this.ipCity,
ipRange: ipRange ?? this.ipRange,
lastActiveTime: lastActiveTime ?? this.lastActiveTime,
isOnline: isOnline ?? this.isOnline,
createtime: createtime ?? this.createtime,
lastActiveText: lastActiveText ?? this.lastActiveText,
createtimeText: createtimeText ?? this.createtimeText,
isActiveRecently: isActiveRecently ?? this.isActiveRecently,
);
}
}
class UserExtra {
const UserExtra({
this.money = '0.00',
this.noteLimit = 50,
this.verification,
this.lastSigninDate = '',
});
final String money;
final int noteLimit;
final UserVerification? verification;
final String lastSigninDate;
Map<String, dynamic> toJson() {
return {
'money': money,
'note_limit': noteLimit,
'verification': verification?.toJson(),
'last_signin_date': lastSigninDate,
};
}
factory UserExtra.fromJson(Map<String, dynamic> json) {
return UserExtra(
money: json['money']?.toString() ?? '0.00',
noteLimit: (json['note_limit'] as num?)?.toInt() ?? 50,
verification: json['verification'] != null
? UserVerification.fromJson(
json['verification'] as Map<String, dynamic>,
)
: null,
lastSigninDate: json['last_signin_date'] as String? ?? '',
);
}
}
export '../../../core/models/user_model.dart';