2025-07-11 11:03:21 +08:00
|
|
|
|
import 'dart:math';
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
import 'package:package_info_plus/package_info_plus.dart';
|
2025-08-08 11:05:06 +08:00
|
|
|
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
2025-07-11 11:03:21 +08:00
|
|
|
|
|
|
|
|
|
int getRandomWithNum(int min, int max) {
|
|
|
|
|
final random = Random();
|
|
|
|
|
return random.nextInt(max) + min; // 生成随机数
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double screenWidth(BuildContext context) {
|
|
|
|
|
double screenWidth = MediaQuery.of(context).size.width;
|
|
|
|
|
return screenWidth;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-17 16:10:46 +08:00
|
|
|
|
Future<T?> pushPage<T>(Widget page, BuildContext context) {
|
|
|
|
|
return Navigator.push<T>(
|
|
|
|
|
context,
|
|
|
|
|
MaterialPageRoute(builder: (_) => page),
|
|
|
|
|
);
|
2025-07-11 11:03:21 +08:00
|
|
|
|
}
|
|
|
|
|
void present(Widget page, BuildContext context) {
|
2025-07-28 16:50:40 +08:00
|
|
|
|
Navigator.of(context).push(
|
|
|
|
|
PageRouteBuilder(
|
|
|
|
|
pageBuilder: (context, animation, secondaryAnimation) => page,
|
|
|
|
|
transitionDuration: Duration.zero,
|
|
|
|
|
reverseTransitionDuration: Duration.zero,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
void presentOpaque(Widget page, BuildContext context) {
|
|
|
|
|
Navigator.of(context).push(
|
|
|
|
|
PageRouteBuilder(
|
|
|
|
|
opaque: false, // 允许下层透出
|
|
|
|
|
barrierColor: Colors.black.withOpacity(0.5), //路由遮罩色
|
|
|
|
|
pageBuilder: (context, animation, secondaryAnimation) => page,
|
|
|
|
|
transitionDuration: Duration.zero,
|
|
|
|
|
reverseTransitionDuration: Duration.zero,
|
|
|
|
|
),
|
2025-07-11 11:03:21 +08:00
|
|
|
|
);
|
|
|
|
|
}
|
2025-07-28 16:50:40 +08:00
|
|
|
|
|
2025-07-28 14:22:07 +08:00
|
|
|
|
class FocusHelper {
|
|
|
|
|
static final FocusNode _emptyNode = FocusNode();
|
|
|
|
|
/// 延迟一帧后再移交焦点,避免不生效的问题
|
|
|
|
|
static void clearFocus(BuildContext context) {
|
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
|
|
|
FocusScope.of(context).requestFocus(_emptyNode);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-15 08:32:50 +08:00
|
|
|
|
|
2025-07-11 11:03:21 +08:00
|
|
|
|
/// 文本样式工具类,返回 Text Widget
|
|
|
|
|
class HhTextStyleUtils {
|
|
|
|
|
/// 主要标题,返回 Text
|
|
|
|
|
/// [text]: 文本内容
|
|
|
|
|
/// [color]: 文本颜色,默认黑色
|
|
|
|
|
/// [fontSize]: 字体大小,默认16.0
|
|
|
|
|
/// [bold]: 是否加粗,默认true
|
|
|
|
|
static Text mainTitle(
|
2025-07-15 08:32:50 +08:00
|
|
|
|
String text, {
|
|
|
|
|
Color color = Colors.black,
|
|
|
|
|
double fontSize = 16.0,
|
|
|
|
|
bool bold = true,
|
|
|
|
|
}) {
|
2025-07-11 11:03:21 +08:00
|
|
|
|
return Text(
|
|
|
|
|
text,
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
color: color,
|
|
|
|
|
fontSize: fontSize,
|
|
|
|
|
fontWeight: bold ? FontWeight.bold : FontWeight.normal,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-07-15 08:32:50 +08:00
|
|
|
|
|
|
|
|
|
static TextStyle secondaryTitleStyle = TextStyle(
|
|
|
|
|
color: Colors.black54,
|
|
|
|
|
fontSize: 15.0,
|
|
|
|
|
);
|
2025-07-11 11:03:21 +08:00
|
|
|
|
|
|
|
|
|
/// 次要标题,返回 Text
|
|
|
|
|
/// [text]: 文本内容
|
|
|
|
|
/// [color]: 文本颜色,默认深灰
|
|
|
|
|
/// [fontSize]: 字体大小,默认14.0
|
|
|
|
|
/// [bold]: 是否加粗,默认false
|
|
|
|
|
static Text secondaryTitle(
|
2025-07-15 08:32:50 +08:00
|
|
|
|
String text, {
|
|
|
|
|
Color color = Colors.black54,
|
|
|
|
|
double fontSize = 14.0,
|
|
|
|
|
bool bold = false,
|
|
|
|
|
}) {
|
2025-07-11 11:03:21 +08:00
|
|
|
|
return Text(
|
|
|
|
|
text,
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
color: color,
|
|
|
|
|
fontSize: fontSize,
|
|
|
|
|
fontWeight: bold ? FontWeight.bold : FontWeight.normal,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 小文字,返回 Text
|
|
|
|
|
/// [text]: 文本内容
|
|
|
|
|
/// [color]: 文本颜色,默认灰色
|
|
|
|
|
/// [fontSize]: 字体大小,默认12.0
|
|
|
|
|
/// [bold]: 是否加粗,默认false
|
|
|
|
|
static Text smallText(
|
2025-07-15 08:32:50 +08:00
|
|
|
|
String text, {
|
|
|
|
|
Color color = Colors.black54,
|
|
|
|
|
double fontSize = 12.0,
|
|
|
|
|
bool bold = false,
|
|
|
|
|
}) {
|
2025-07-11 11:03:21 +08:00
|
|
|
|
return Text(
|
|
|
|
|
text,
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
color: color,
|
|
|
|
|
fontSize: fontSize,
|
|
|
|
|
fontWeight: bold ? FontWeight.bold : FontWeight.normal,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 版本信息模型类
|
|
|
|
|
class AppVersionInfo {
|
|
|
|
|
final String versionName; // 版本名称(如 1.0.0)
|
|
|
|
|
final String buildNumber; // 构建号(如 1)
|
|
|
|
|
final String fullVersion; // 完整版本(如 1.0.0+1)
|
|
|
|
|
|
|
|
|
|
AppVersionInfo({
|
|
|
|
|
required this.versionName,
|
|
|
|
|
required this.buildNumber,
|
|
|
|
|
required this.fullVersion,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
String toString() {
|
|
|
|
|
return fullVersion;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取应用版本信息的方法
|
|
|
|
|
Future<AppVersionInfo> getAppVersion() async {
|
|
|
|
|
try {
|
|
|
|
|
final packageInfo = await PackageInfo.fromPlatform();
|
|
|
|
|
return AppVersionInfo(
|
|
|
|
|
versionName: packageInfo.version,
|
|
|
|
|
buildNumber: packageInfo.buildNumber,
|
|
|
|
|
fullVersion: '${packageInfo.version}+${packageInfo.buildNumber}',
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// 获取失败时返回默认值
|
|
|
|
|
return AppVersionInfo(
|
|
|
|
|
versionName: '1.0.0',
|
|
|
|
|
buildNumber: '1',
|
|
|
|
|
fullVersion: '1.0.0+0',
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-07-15 08:32:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ------------------------------------------------------
|
|
|
|
|
/// 全局会话管理
|
|
|
|
|
/// ------------------------------------------------------
|
|
|
|
|
class SessionService {
|
|
|
|
|
SessionService._();
|
|
|
|
|
|
|
|
|
|
static final SessionService instance = SessionService._();
|
|
|
|
|
|
|
|
|
|
String? corpinfoId;
|
|
|
|
|
String? loginUserId;
|
|
|
|
|
Map<String, dynamic>? loginUser;
|
|
|
|
|
String? deptId;
|
|
|
|
|
String? deptLevel;
|
|
|
|
|
String? postId;
|
|
|
|
|
String? username;
|
|
|
|
|
String? version;
|
|
|
|
|
String? basePath;
|
|
|
|
|
String? isRest;
|
|
|
|
|
List<dynamic>? permission;
|
|
|
|
|
bool updateInfo = false;
|
2025-07-23 20:14:38 +08:00
|
|
|
|
String? dangerJson;
|
|
|
|
|
String? riskJson;
|
2025-07-24 11:18:47 +08:00
|
|
|
|
String? departmentJsonStr;
|
2025-07-25 18:06:37 +08:00
|
|
|
|
String? departmentHiddenTypeJsonStr;
|
2025-07-30 10:47:52 +08:00
|
|
|
|
String? customRecordDangerJson;
|
2025-08-01 16:40:59 +08:00
|
|
|
|
String? unqualifiedInspectionItemID;
|
2025-08-06 09:19:51 +08:00
|
|
|
|
String? listItemNameJson;
|
|
|
|
|
|
2025-07-15 08:32:50 +08:00
|
|
|
|
|
|
|
|
|
/// 如果以下任何一项为空,则跳转到登录页
|
|
|
|
|
void loginSession(BuildContext context) {
|
|
|
|
|
if (corpinfoId == null || loginUserId == null || loginUser == null) {
|
|
|
|
|
Navigator.pushReplacementNamed(context, '/login');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// setters
|
|
|
|
|
void setLoginUser(Map<String, dynamic> user) => loginUser = user;
|
|
|
|
|
|
|
|
|
|
void setLoginUserId(String id) => loginUserId = id;
|
|
|
|
|
|
|
|
|
|
void setCorpinfoId(String id) => corpinfoId = id;
|
|
|
|
|
|
|
|
|
|
void setDeptId(String id) => deptId = id;
|
|
|
|
|
|
|
|
|
|
void setDeptLevel(String level) => deptLevel = level;
|
|
|
|
|
|
|
|
|
|
void setPostId(String id) => postId = id;
|
|
|
|
|
|
|
|
|
|
void setUsername(String name) => username = name;
|
|
|
|
|
|
|
|
|
|
void setVersion(String ver) => version = ver;
|
|
|
|
|
|
|
|
|
|
void setBasePath(String url) => basePath = url;
|
|
|
|
|
|
|
|
|
|
void setIsRest(String rest) => isRest = rest;
|
|
|
|
|
|
|
|
|
|
void setPermission(List<dynamic> list) => permission = list;
|
|
|
|
|
|
|
|
|
|
void setUpdateInfo(bool flag) => updateInfo = flag;
|
|
|
|
|
|
2025-07-23 20:14:38 +08:00
|
|
|
|
void setDangerWaitInfo(String json) => dangerJson = json;
|
|
|
|
|
|
|
|
|
|
void setRiskWaitInfo(String json) => riskJson = json;
|
|
|
|
|
|
2025-07-24 11:18:47 +08:00
|
|
|
|
void setDepartmentJsonStr(String json) => departmentJsonStr = json;
|
|
|
|
|
|
2025-07-30 10:47:52 +08:00
|
|
|
|
void setCustomRecordDangerJson(String json) => customRecordDangerJson = json;
|
2025-07-15 08:32:50 +08:00
|
|
|
|
|
2025-08-01 16:40:59 +08:00
|
|
|
|
void setUnqualifiedInspectionItemIDJson(String json) => unqualifiedInspectionItemID = json;
|
|
|
|
|
|
2025-08-06 09:19:51 +08:00
|
|
|
|
void setListItemNameJson(String json) => listItemNameJson = json;
|
2025-08-01 16:40:59 +08:00
|
|
|
|
|
2025-07-15 08:32:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ------------------------------------------------------
|
|
|
|
|
/// 日期格式化
|
|
|
|
|
/// ------------------------------------------------------
|
|
|
|
|
String formatDate(DateTime? date, String fmt) {
|
|
|
|
|
if (date == null) return '';
|
|
|
|
|
String twoDigits(int n) => n.toString().padLeft(2, '0');
|
|
|
|
|
|
|
|
|
|
final replacements = <String, String>{
|
|
|
|
|
'yyyy': date.year.toString(),
|
|
|
|
|
'yy': date.year.toString().substring(2),
|
|
|
|
|
'MM': twoDigits(date.month),
|
|
|
|
|
'M': date.month.toString(),
|
|
|
|
|
'dd': twoDigits(date.day),
|
|
|
|
|
'd': date.day.toString(),
|
|
|
|
|
'hh': twoDigits(date.hour),
|
|
|
|
|
'h': date.hour.toString(),
|
|
|
|
|
'mm': twoDigits(date.minute),
|
|
|
|
|
'm': date.minute.toString(),
|
|
|
|
|
'ss': twoDigits(date.second),
|
|
|
|
|
's': date.second.toString(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
String result = fmt;
|
|
|
|
|
replacements.forEach((key, value) {
|
|
|
|
|
result = result.replaceAllMapped(RegExp(key), (_) => value);
|
|
|
|
|
});
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ------------------------------------------------------
|
|
|
|
|
/// 防多次点击
|
|
|
|
|
/// ------------------------------------------------------
|
|
|
|
|
class ClickUtil {
|
|
|
|
|
ClickUtil._();
|
|
|
|
|
|
|
|
|
|
static bool _canClick = true;
|
|
|
|
|
|
|
|
|
|
/// 调用示例:
|
|
|
|
|
/// ClickUtil.noMultipleClicks(() { /* your code */ });
|
|
|
|
|
static void noMultipleClicks(VoidCallback fn, {int delayMs = 2000}) {
|
|
|
|
|
if (_canClick) {
|
|
|
|
|
_canClick = false;
|
|
|
|
|
fn();
|
|
|
|
|
Future.delayed(Duration(milliseconds: delayMs), () {
|
|
|
|
|
_canClick = true;
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
debugPrint('请稍后点击');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void presentPage(BuildContext context, Widget page) {
|
|
|
|
|
Navigator.push(
|
|
|
|
|
context,
|
|
|
|
|
MaterialPageRoute(fullscreenDialog: true, builder: (_) => page),
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-07-16 08:37:08 +08:00
|
|
|
|
|
|
|
|
|
class LoadingDialogHelper {
|
2025-08-08 11:05:06 +08:00
|
|
|
|
// 显示加载框
|
|
|
|
|
static void show({String? message}) {
|
|
|
|
|
if (message != null) {
|
|
|
|
|
EasyLoading.show(status: message);
|
|
|
|
|
} else {
|
|
|
|
|
EasyLoading.show();
|
|
|
|
|
}
|
2025-07-16 08:37:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 11:05:06 +08:00
|
|
|
|
// 隐藏加载框
|
|
|
|
|
static void hide() {
|
|
|
|
|
if (EasyLoading.isShow) {
|
|
|
|
|
EasyLoading.dismiss();
|
2025-07-16 08:37:08 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-29 08:50:41 +08:00
|
|
|
|
|
2025-08-01 16:41:44 +08:00
|
|
|
|
|
|
|
|
|
|
2025-07-17 16:10:46 +08:00
|
|
|
|
/// 将秒数转换为 “HH:MM:SS” 格式
|
|
|
|
|
String secondsCount(dynamic seconds) {
|
|
|
|
|
// 先尝试解析出一个 double 值
|
|
|
|
|
double totalSeconds;
|
|
|
|
|
if (seconds == null) {
|
|
|
|
|
totalSeconds = 0;
|
|
|
|
|
} else if (seconds is num) {
|
|
|
|
|
totalSeconds = seconds.toDouble();
|
|
|
|
|
} else {
|
|
|
|
|
// seconds 是字符串或其他,尝试 parse
|
|
|
|
|
totalSeconds = double.tryParse(seconds.toString()) ?? 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 取整秒,向下取整
|
|
|
|
|
final int secs = totalSeconds.floor();
|
|
|
|
|
|
|
|
|
|
final int h = (secs ~/ 3600) % 24;
|
|
|
|
|
final int m = (secs ~/ 60) % 60;
|
|
|
|
|
final int s = secs % 60;
|
|
|
|
|
|
|
|
|
|
// padLeft 保证两位数
|
|
|
|
|
final String hh = h.toString().padLeft(2, '0');
|
|
|
|
|
final String mm = m.toString().padLeft(2, '0');
|
|
|
|
|
final String ss = s.toString().padLeft(2, '0');
|
|
|
|
|
|
|
|
|
|
return '$hh:$mm:$ss';
|
|
|
|
|
}
|
2025-08-06 13:43:22 +08:00
|
|
|
|
void printLongString(String text, {int chunkSize = 800}) {
|
|
|
|
|
final pattern = RegExp('.{1,$chunkSize}'); // 每 chunkSize 个字符一组
|
|
|
|
|
for (final match in pattern.allMatches(text)) {
|
|
|
|
|
print(match.group(0));
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-28 14:22:07 +08:00
|
|
|
|
|
|
|
|
|
/// 表单处理
|
|
|
|
|
class FormUtils {
|
|
|
|
|
/// 判断 [data] 中的 [key] 是否存在“有效值”:
|
|
|
|
|
/// - key 不存在或值为 null -> false
|
|
|
|
|
/// - String:去掉首尾空白后非空 -> true
|
|
|
|
|
/// - Iterable / Map:非空 -> true
|
|
|
|
|
/// - 其它类型(int、double、bool 等)只要不为 null 就算有值 -> true
|
|
|
|
|
static bool hasValue(Map<String, dynamic> data, String key) {
|
|
|
|
|
if (!data.containsKey(key)) return false;
|
|
|
|
|
final val = data[key];
|
|
|
|
|
if (val == null) return false;
|
|
|
|
|
|
|
|
|
|
if (val is String) {
|
|
|
|
|
return val.trim().isNotEmpty;
|
|
|
|
|
}
|
|
|
|
|
if (val is Iterable || val is Map) {
|
|
|
|
|
return val.isNotEmpty;
|
|
|
|
|
}
|
|
|
|
|
// 数字、布尔等其它非空即可
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-08-14 15:05:48 +08:00
|
|
|
|
/// 在list中根据一个 key,value,找到对应的map
|
|
|
|
|
static Map findMapForKeyValue(List list,String key, String value) {
|
|
|
|
|
Map target = list.firstWhere(
|
|
|
|
|
(item) => item[key] == value,
|
|
|
|
|
orElse: () => {},
|
|
|
|
|
);
|
|
|
|
|
return target ?? {};
|
|
|
|
|
}
|
2025-07-28 14:22:07 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class NoDataWidget {
|
|
|
|
|
static Widget show() {
|
|
|
|
|
return Center(
|
|
|
|
|
child: Column(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
Image.asset('assets/images/null.png', width: 200,),
|
|
|
|
|
Text('暂无数据', style: TextStyle(color: Colors.grey)),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|