Compare commits
2 Commits
c738be9e23
...
d0307e7f09
Author | SHA1 | Date |
---|---|---|
|
d0307e7f09 | |
|
a68c1448c6 |
|
@ -1,6 +1,6 @@
|
|||
# qhd_prevention
|
||||
# flutter_integrated_whb
|
||||
|
||||
A new Flutter project.
|
||||
危化项目.
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 9.5 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 72 KiB |
After Width: | Height: | Size: 343 KiB |
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 428 KiB |
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 110 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 170 KiB |
Before Width: | Height: | Size: 381 KiB After Width: | Height: | Size: 345 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 23 KiB |
|
@ -2,6 +2,8 @@ PODS:
|
|||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- Flutter (1.0.0)
|
||||
- fluttertoast (0.0.2):
|
||||
- Flutter
|
||||
- image_picker_ios (0.0.1):
|
||||
- Flutter
|
||||
- mobile_scanner (7.0.0):
|
||||
|
@ -25,6 +27,7 @@ PODS:
|
|||
DEPENDENCIES:
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
|
@ -38,6 +41,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
fluttertoast:
|
||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||
image_picker_ios:
|
||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||
mobile_scanner:
|
||||
|
@ -56,6 +61,7 @@ EXTERNAL SOURCES:
|
|||
SPEC CHECKSUMS:
|
||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
|
||||
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
||||
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
|
||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
import 'package:dio/dio.dart';
|
||||
import 'package:qhd_prevention/tools/tools.dart';
|
||||
|
||||
import 'HttpManager.dart';
|
||||
|
||||
class ApiService {
|
||||
// static const String basePath = "http://192.168.0.25:28199/";
|
||||
// static const String basePath = "http://192.168.20.240:8500/integrated_whb";
|
||||
// static const String baseFacePath = "http://192.168.0.25:38199/";
|
||||
// 人脸识别服务
|
||||
// static const String baseFacePath = "https://qaaqwh.qhdsafety.com/whb_stu_face/";
|
||||
|
||||
// static const String basePath = "https://qaaqwh.qhdsafety.com/integrated_whb/";
|
||||
// static const String baseImgPath = "https://file.zcloudchina.com/YTHFile";
|
||||
// static const String adminPath = "https://qaaqwh.qhdsafety.com/integrated_whb/";
|
||||
// static const String projectManagerUrl = 'https://pm.qhdsafety.com/zy-projectManage/';
|
||||
// static const String publicKey = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDUoHAavCikaZxjlDM6Km8cX+ye78F4oF39AcEfnE1p2Yn9pJ9WFxYZ4Vkh6F8SKMi7k4nYsKceqB1RwG996SvHQ5C3pM3nbXCP4K15ad6QhN4a7lzlbLhiJcyIKszvvK8ncUDw8mVQ0j/2mwxv05yH6LN9OKU6Hzm1ninpWeE+awIDAQAB'
|
||||
/// 人脸识别服务
|
||||
static const String baseFacePath =
|
||||
"https://qaaqwh.qhdsafety.com/whb_stu_face/";
|
||||
|
||||
/// 登录及其他管理后台接口
|
||||
static const String basePath = "https://qaaqwh.qhdsafety.com/integrated_whb";
|
||||
|
||||
/// 图片文件服务
|
||||
static const String baseImgPath = "https://file.zcloudchina.com/YTHFile";
|
||||
|
||||
/// 管理后台统一路径
|
||||
static const String adminPath =
|
||||
"https://qaaqwh.qhdsafety.com/integrated_whb/";
|
||||
|
||||
/// 项目管理系统
|
||||
static const String projectManagerUrl =
|
||||
'https://pm.qhdsafety.com/zy-projectManage';
|
||||
|
||||
/// RSA 公钥
|
||||
static const publicKey = '''
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDUoHAavCikaZxjlDM6Km8cX+ye
|
||||
78F4oF39AcEfnE1p2Yn9pJ9WFxYZ4Vkh6F8SKMi7k4nYsKceqB1RwG996SvHQ5C3p
|
||||
M3nbXCP4K15ad6QhN4a7lzlbLhiJcyIKszvvK8ncUDw8mVQ0j/2mwxv05yH6LN9OK
|
||||
U6Hzm1ninpWeE+awIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
''';
|
||||
|
||||
/// 登录验证接口
|
||||
static Future<Map<String, dynamic>> loginCheck(String keydataVal) {
|
||||
return HttpManager().request(
|
||||
basePath,
|
||||
'/admin/check',
|
||||
method: Method.post,
|
||||
data: {
|
||||
'KEYDATA': keydataVal,
|
||||
'SOURCE': '2',
|
||||
'tm': DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
///TODO -------------–-------------------- 首页 -------------–--------------------
|
||||
/// 我的工作
|
||||
static Future<Map<String, dynamic>> getWork() {
|
||||
return HttpManager().request(
|
||||
basePath,
|
||||
'/app/hidden/getCountByUserId',
|
||||
method: Method.post,
|
||||
data: {
|
||||
'userId': SessionService.instance.loginUserId,
|
||||
'USER_NAME': SessionService.instance.username,
|
||||
'CHECK_DEPARTMENT_ID': SessionService.instance.deptId,
|
||||
'IS_MAIN': SessionService.instance.isRest,
|
||||
'CORPINFO_ID': SessionService.instance.corpinfoId,
|
||||
'USER_ID': SessionService.instance.loginUserId,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
static Future<Map<String, dynamic>> getRedPoint() {
|
||||
return HttpManager().request(
|
||||
basePath,
|
||||
'/app/eightwork/checkWork',
|
||||
method: Method.post,
|
||||
data: {'USER_ID': SessionService.instance.loginUserId},
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
static Future<Map<String, dynamic>> getUserData() {
|
||||
return HttpManager().request(
|
||||
basePath,
|
||||
'/app/hidden/getUserIndexData',
|
||||
method: Method.post,
|
||||
data: {
|
||||
'userId': SessionService.instance.loginUserId,
|
||||
'USER_NAME': SessionService.instance.username,
|
||||
'CHECK_DEPARTMENT_ID': SessionService.instance.deptId,
|
||||
'IS_MAIN': SessionService.instance.isRest,
|
||||
'CORPINFO_ID': SessionService.instance.corpinfoId,
|
||||
'USER_ID': SessionService.instance.loginUserId,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> getSafetyEnvironmentalInspectionCount() {
|
||||
return HttpManager().request(
|
||||
basePath,
|
||||
'/app/safetyenvironmental/countCheck',
|
||||
method: Method.post,
|
||||
data: {
|
||||
'CORPINFO_ID': SessionService.instance.corpinfoId,
|
||||
'INSPECTION_USER_ID': SessionService.instance.loginUserId,
|
||||
'INSPECTED_SITEUSER_ID': SessionService.instance.loginUserId,
|
||||
'INSPECTION_ORIGINATOR_ID': SessionService.instance.loginUserId,
|
||||
'tm': DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> getUpdateInfo() {
|
||||
return HttpManager().request(
|
||||
projectManagerUrl,
|
||||
'/projectDetails/findUpdate?code=cloud&type=APP',
|
||||
method: Method.post,
|
||||
data: {},
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> getDeptData() {
|
||||
return HttpManager().request(
|
||||
basePath,
|
||||
'/app/hidden/getDeptIndexData',
|
||||
method: Method.post,
|
||||
data: {
|
||||
'userId': SessionService.instance.loginUserId,
|
||||
'USER_NAME': SessionService.instance.username,
|
||||
'DEPARTMENT_ID': SessionService.instance.deptId,
|
||||
'IS_MAIN': SessionService.instance.isRest,
|
||||
'CORPINFO_ID': SessionService.instance.corpinfoId,
|
||||
'USER_ID': SessionService.instance.loginUserId,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 月隐患 1 月隐患,2年隐患
|
||||
static Future<Map<String, dynamic>> getDanger(int type) {
|
||||
return HttpManager().request(
|
||||
basePath,
|
||||
'/app/hidden/getIndexCount',
|
||||
method: Method.post,
|
||||
data: {
|
||||
'userId': SessionService.instance.loginUserId,
|
||||
'CORPINFO_ID': SessionService.instance.corpinfoId,
|
||||
(type == 1 ? 'IS_MONTH' : 'IS_YEAR'): '1',
|
||||
'USER_ID': SessionService.instance.loginUserId,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> getSurveyData() {
|
||||
return HttpManager().request(
|
||||
basePath,
|
||||
'/app/survey/goEdit',
|
||||
method: Method.post,
|
||||
data: {
|
||||
'CORPINFO_ID': SessionService.instance.corpinfoId,
|
||||
'USER_ID': SessionService.instance.loginUserId,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> getUserId() {
|
||||
return HttpManager().request(
|
||||
basePath,
|
||||
'/app/surveyanswer/getUserId',
|
||||
method: Method.post,
|
||||
data: {
|
||||
'CORPINFO_ID': SessionService.instance.corpinfoId,
|
||||
'USER_ID': SessionService.instance.loginUserId,
|
||||
},
|
||||
);
|
||||
}
|
||||
/// 获取清单数量
|
||||
static Future<Map<String, dynamic>> getListData() {
|
||||
return HttpManager().request(
|
||||
basePath,
|
||||
'/app/listmanager/checkList',
|
||||
method: Method.post,
|
||||
data: {
|
||||
'userId': SessionService.instance.loginUserId,
|
||||
'USER_NAME': SessionService.instance.username,
|
||||
'CHECK_DEPARTMENT_ID': SessionService.instance.deptId,
|
||||
'IS_MAIN': SessionService.instance.isRest,
|
||||
'CORPINFO_ID': SessionService.instance.corpinfoId,
|
||||
'USER_ID': SessionService.instance.loginUserId,
|
||||
},
|
||||
);
|
||||
}
|
||||
/// 获取滚动隐患
|
||||
static Future<Map<String, dynamic>> getHiddenRoll() {
|
||||
return HttpManager().request(
|
||||
basePath,
|
||||
'/app/hidden/getHiddenByCorp',
|
||||
method: Method.post,
|
||||
data: {
|
||||
'CORPINFO_ID': SessionService.instance.corpinfoId,
|
||||
'HIDDENLEVEL': 'hiddenLevel0001',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> getIsRest() {
|
||||
return HttpManager().request(
|
||||
basePath,
|
||||
'/app/hidden/getHiddenByCorp',
|
||||
method: Method.post,
|
||||
data: {
|
||||
'CORPINFO_ID': SessionService.instance.corpinfoId,
|
||||
'USER_ID': SessionService.instance.loginUserId,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
import 'package:dio/dio.dart';
|
||||
|
||||
/// 统一接口异常
|
||||
class ApiException implements Exception {
|
||||
final String result;
|
||||
final String message;
|
||||
ApiException(this.result, this.message);
|
||||
@override
|
||||
String toString() => 'ApiException($result): $message';
|
||||
}
|
||||
|
||||
/// HTTP 方法枚举
|
||||
enum Method { get, post, put, delete }
|
||||
|
||||
class HttpManager {
|
||||
HttpManager._internal() {
|
||||
_dio = Dio(BaseOptions(
|
||||
connectTimeout: const Duration(milliseconds: 10000),
|
||||
receiveTimeout: const Duration(milliseconds: 10000),
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
));
|
||||
_initInterceptors();
|
||||
}
|
||||
|
||||
static final HttpManager _instance = HttpManager._internal();
|
||||
factory HttpManager() => _instance;
|
||||
late final Dio _dio;
|
||||
|
||||
void _initInterceptors() {
|
||||
_dio.interceptors
|
||||
..add(LogInterceptor(request: true, responseBody: true, error: true))
|
||||
..add(InterceptorsWrapper(onError: (err, handler) {
|
||||
// 全局错误处理,可根据 err.response?.statusCode 或 err.type 操作
|
||||
handler.next(err);
|
||||
}));
|
||||
}
|
||||
|
||||
/// 通用 request 方法,返回完整后台 JSON
|
||||
/// baseUrl: 基础路径,如 basePath
|
||||
/// path: 接口路径,如 '/admin/check'
|
||||
/// method: HTTP 方法,默认 POST
|
||||
/// data: Form 表单参数
|
||||
/// params: URL 查询参数
|
||||
Future<Map<String, dynamic>> request(
|
||||
String baseUrl,
|
||||
String path, {
|
||||
Method method = Method.post,
|
||||
Map<String, dynamic>? data,
|
||||
Map<String, dynamic>? params,
|
||||
CancelToken? cancelToken,
|
||||
}) async {
|
||||
Response resp;
|
||||
final url = baseUrl + path;
|
||||
final options = Options(
|
||||
method: method.name.toUpperCase(),
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
);
|
||||
try {
|
||||
switch (method) {
|
||||
case Method.get:
|
||||
resp = await _dio.get(url,
|
||||
queryParameters: params, cancelToken: cancelToken, options: options);
|
||||
break;
|
||||
case Method.put:
|
||||
resp = await _dio.put(url,
|
||||
data: data, queryParameters: params, cancelToken: cancelToken, options: options);
|
||||
break;
|
||||
case Method.delete:
|
||||
resp = await _dio.delete(url,
|
||||
queryParameters: params, cancelToken: cancelToken, options: options);
|
||||
break;
|
||||
case Method.post:
|
||||
default:
|
||||
resp = await _dio.post(url,
|
||||
data: data, queryParameters: params, cancelToken: cancelToken, options: options);
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
// 网络或服务器错误
|
||||
throw ApiException('network_error', e.message ?? "");
|
||||
}
|
||||
|
||||
// 解析返回 JSON
|
||||
final json = resp.data is Map<String, dynamic>
|
||||
? resp.data as Map<String, dynamic>
|
||||
: <String, dynamic>{};
|
||||
final result = json['result'] as String?;
|
||||
final msg = json['msg'] as String? ?? json['message'] as String? ?? '';
|
||||
if (result != 'success') {
|
||||
// 非 success 都抛异常
|
||||
throw ApiException(result ?? 'unknown', msg);
|
||||
}
|
||||
// 返回完整数据,包括 msg、USER_ID 等
|
||||
return json;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart';
|
||||
|
@ -9,6 +10,7 @@ import 'package:qhd_prevention/pages/home/work/danger_page.dart';
|
|||
import 'package:qhd_prevention/pages/home/work/danger_wait_list_page.dart';
|
||||
import 'package:qhd_prevention/pages/home/workSet_page.dart';
|
||||
|
||||
import '../../http/ApiService.dart';
|
||||
import '../../tools/tools.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
|
@ -19,44 +21,12 @@ class HomePage extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
final List<Map<String, String>> buttonInfos = [
|
||||
{"icon": "assets/icon-apps/home-base.png", "title": "人员信息"},
|
||||
{"icon": "assets/icon-apps/home-rili.png", "title": "工作安排"},
|
||||
{"icon": "assets/icon-apps/home-risk.png", "title": "风险布控"},
|
||||
{"icon": "assets/icon-apps/home-fl.png", "title": "法律法规"},
|
||||
];
|
||||
final List<Map<String, dynamic>> workInfos = [
|
||||
{
|
||||
"icon": "assets/icon-apps/jobico1.png",
|
||||
"num": "1",
|
||||
"detail": "待排查",
|
||||
"index": 1,
|
||||
},
|
||||
{
|
||||
"icon": "assets/icon-apps/jobico2.png",
|
||||
"num": "2",
|
||||
"detail": "待整改",
|
||||
"index": 2,
|
||||
},
|
||||
{
|
||||
"icon": "assets/icon-apps/jobico3.png",
|
||||
"num": "3",
|
||||
"detail": "已超期",
|
||||
"index": 3,
|
||||
},
|
||||
{
|
||||
"icon": "assets/icon-apps/jobico4.png",
|
||||
"num": "4",
|
||||
"detail": "待验收",
|
||||
"index": 4,
|
||||
},
|
||||
{
|
||||
"icon": "assets/icon-apps/jobico5.png",
|
||||
"num": "5",
|
||||
"detail": "已验收",
|
||||
"index": 5,
|
||||
},
|
||||
];
|
||||
final int _eight_work_count = 0;
|
||||
final int _safetyEnvironmentalInspection = 0;
|
||||
late final List<Map<String, dynamic>> buttonInfos;
|
||||
|
||||
/// 我的工作
|
||||
List<Map<String, dynamic>> workInfos = [];
|
||||
final List<Map<String, dynamic>> pcData = [
|
||||
{
|
||||
"index": 1,
|
||||
|
@ -114,42 +84,6 @@ class _HomePageState extends State<HomePage> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildIconSection(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children:
|
||||
buttonInfos.asMap().entries.map((entry) {
|
||||
final index = entry.key; // 获取当前索引
|
||||
final btnInfo = entry.value; // 获取按钮信息
|
||||
|
||||
return _buildIconButton(
|
||||
icon: Image.asset(btnInfo["icon"]!, width: 40, height: 40),
|
||||
label: btnInfo["title"]!,
|
||||
onPressed: () {
|
||||
// 使用索引判断点击的是哪个按钮
|
||||
print("点击了第 $index 个按钮");
|
||||
if (index == 0) {
|
||||
pushPage(UserinfoPage(), context);
|
||||
} else if (index == 1) {
|
||||
pushPage(WorkSetPage(), context);
|
||||
} else if (index == 2) {
|
||||
pushPage(RiskControlPage(), context);
|
||||
} else if (index == 3) {
|
||||
pushPage(LowPage(), context);
|
||||
}
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWorkSection(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
|
@ -194,8 +128,6 @@ class _HomePageState extends State<HomePage> {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Widget _buildPCDataSection() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
|
@ -205,7 +137,6 @@ class _HomePageState extends State<HomePage> {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// _widgetTopTip(title: "排查数据"),
|
||||
ListItemFactory.createBuildSimpleSection("排查数据"),
|
||||
...pcData.map(_widgetPCDataItem),
|
||||
],
|
||||
|
@ -213,26 +144,109 @@ class _HomePageState extends State<HomePage> {
|
|||
);
|
||||
}
|
||||
|
||||
// 构建图标按钮:图上文字下
|
||||
Widget _buildIconButton({
|
||||
required Widget icon,
|
||||
required String label,
|
||||
required VoidCallback onPressed, // 添加点击回调参数
|
||||
}) {
|
||||
return InkWell(
|
||||
onTap: onPressed, // 处理点击事件
|
||||
/// 定义 item 宽度
|
||||
double _itemWidth(BuildContext context) {
|
||||
final screenW = MediaQuery.of(context).size.width;
|
||||
return (screenW - 20) / 4;
|
||||
}
|
||||
|
||||
Widget _buildIconSection(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||
),
|
||||
child: Wrap(
|
||||
runSpacing: 16,
|
||||
children:
|
||||
buttonInfos.asMap().entries.map((entry) {
|
||||
final idx = entry.key;
|
||||
final info = entry.value;
|
||||
return _buildIconButton(
|
||||
context: context,
|
||||
iconPath: info["icon"] as String,
|
||||
label: info["title"] as String,
|
||||
unreadCount: info["unreadCount"] as int,
|
||||
onPressed: () {
|
||||
final index = entry.key;
|
||||
// 你的导航逻辑
|
||||
if (index == 0) {
|
||||
pushPage(UserinfoPage(), context);
|
||||
} else if (index == 1) {
|
||||
pushPage(WorkSetPage(), context);
|
||||
} else if (index == 2) {
|
||||
pushPage(RiskControlPage(), context);
|
||||
} else if (index == 3) {
|
||||
pushPage(LowPage(), context);
|
||||
}
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIconButton({
|
||||
required BuildContext context,
|
||||
required String iconPath,
|
||||
required String label,
|
||||
required VoidCallback onPressed,
|
||||
int unreadCount = 0,
|
||||
}) {
|
||||
final w = _itemWidth(context);
|
||||
return InkWell(
|
||||
onTap: onPressed,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: SizedBox(
|
||||
width: w,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
/// 中心对齐
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
icon,
|
||||
Image.asset(iconPath, width: 40, height: 40),
|
||||
const SizedBox(height: 5),
|
||||
Text(label, style: const TextStyle(fontSize: 14)),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (unreadCount > 0)
|
||||
Positioned(
|
||||
right: -4,
|
||||
top: -4,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 16,
|
||||
minHeight: 16,
|
||||
),
|
||||
child: Text(
|
||||
unreadCount > 99 ? '99+' : '$unreadCount',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 10,
|
||||
height: 1,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -357,4 +371,135 @@ class _HomePageState extends State<HomePage> {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
void initState() {
|
||||
super.initState();
|
||||
buttonInfos = [
|
||||
{
|
||||
"icon": "assets/icon-apps/home-base.png",
|
||||
"title": "人员信息",
|
||||
"unreadCount": 0,
|
||||
},
|
||||
{
|
||||
"icon": "assets/icon-apps/home-rili.png",
|
||||
"title": "工作安排",
|
||||
"unreadCount": 0,
|
||||
},
|
||||
{
|
||||
"icon": "assets/icon-apps/home-risk.png",
|
||||
"title": "风险布控",
|
||||
"unreadCount": 0,
|
||||
},
|
||||
{
|
||||
"icon": "assets/icon-apps/home-fl.png",
|
||||
"title": "法律法规",
|
||||
"unreadCount": 0,
|
||||
},
|
||||
{
|
||||
"icon": "assets/icon-apps/home-gw.png",
|
||||
"title": "特殊作业",
|
||||
"unreadCount": _eight_work_count,
|
||||
},
|
||||
{
|
||||
"icon": "assets/icon-apps/home-zdgcgl.jpg",
|
||||
"title": "重点工程管理",
|
||||
"unreadCount": 0,
|
||||
},
|
||||
{
|
||||
"icon": "assets/icon-apps/home-cns.png",
|
||||
"title": "安全承诺",
|
||||
"unreadCount": 0,
|
||||
},
|
||||
{
|
||||
"icon": "assets/icon-apps/home-study.png",
|
||||
"title": "学习园地",
|
||||
"unreadCount": 0,
|
||||
},
|
||||
{
|
||||
"icon": "assets/icon-apps/home-base.png",
|
||||
"title": "安全检查",
|
||||
"unreadCount": _safetyEnvironmentalInspection,
|
||||
},
|
||||
{
|
||||
"icon": "assets/icon-apps/home-speEquip.jpg",
|
||||
"title": "设备巡检",
|
||||
"unreadCount": 0,
|
||||
},
|
||||
{
|
||||
"icon": "assets/icon-apps/safetymeeting.png",
|
||||
"title": "安全例会",
|
||||
"unreadCount": 0,
|
||||
},
|
||||
];
|
||||
|
||||
_fetchData(); // 初始化时请求
|
||||
|
||||
}
|
||||
Future<void> _fetchData() async {
|
||||
try {
|
||||
// “我的工作” 数量
|
||||
final raw = await ApiService.getWork();
|
||||
// 如果拿到的是 String,就 decode;如果本来就是 Map,就直接用
|
||||
final Map<String, dynamic> data = raw is String
|
||||
? json.decode(raw as String) as Map<String, dynamic>
|
||||
: raw;
|
||||
|
||||
final hidCount = data['hidCount'] as Map<String, dynamic>;
|
||||
print(hidCount);
|
||||
setState(() {
|
||||
workInfos = [
|
||||
{
|
||||
"icon": "assets/icon-apps/jobico1.png",
|
||||
"index": 1,
|
||||
"detail": "待排查",
|
||||
"num": (hidCount['dpc'] ?? 0).toString(),
|
||||
},
|
||||
{
|
||||
"icon": "assets/icon-apps/jobico2.png",
|
||||
"index": 2,
|
||||
"detail": "待整改",
|
||||
"num": (hidCount['dzg'] ?? 0).toString(),
|
||||
},
|
||||
{
|
||||
"icon": "assets/icon-apps/jobico3.png",
|
||||
"index": 3,
|
||||
"detail": "已超期",
|
||||
"num": (hidCount['ycq'] ?? 0).toString(),
|
||||
},
|
||||
{
|
||||
"icon": "assets/icon-apps/jobico4.png",
|
||||
"index": 4,
|
||||
"detail": "待验收",
|
||||
"num": (hidCount['dys'] ?? 0).toString(),
|
||||
},
|
||||
{
|
||||
"icon": "assets/icon-apps/jobico5.png",
|
||||
"index": 5,
|
||||
"detail": "已验收",
|
||||
"num": (hidCount['yys'] ?? 0).toString(),
|
||||
},
|
||||
];
|
||||
|
||||
});
|
||||
|
||||
|
||||
// // 特殊作业红点
|
||||
// final redPointJson = await ApiService.getRedPoint();
|
||||
// setState(() {
|
||||
// eightWorkCount =
|
||||
// int.parse(redPointJson['unreadCount'].toString());
|
||||
// });
|
||||
//
|
||||
// // 安全检查数
|
||||
// final checkJson =
|
||||
// await ApiService.getSafetyEnvironmentalInspectionCount();
|
||||
// setState(() {
|
||||
// safetyCheckCount =
|
||||
// int.parse(checkJson['count'].toString());
|
||||
// });
|
||||
} catch (e) {
|
||||
// 出错时可以 Toast 或者在页面上显示错误状态
|
||||
print('加载首页数据失败:$e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:qhd_prevention/tools/StorageService.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../http/ApiService.dart';
|
||||
import 'package:pointycastle/asymmetric/api.dart' show RSAPublicKey;
|
||||
import '../http/HttpManager.dart';
|
||||
import '../tools/tools.dart';
|
||||
import 'main_tab.dart';
|
||||
|
||||
void main() => runApp(const MyApp());
|
||||
|
@ -29,62 +41,82 @@ class LoginPage extends StatefulWidget {
|
|||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
// ignore: library_private_types_in_public_api
|
||||
_LoginPageState createState() => _LoginPageState();
|
||||
}
|
||||
|
||||
class _LoginPageState extends State<LoginPage> {
|
||||
final TextEditingController _phoneController = TextEditingController();
|
||||
final TextEditingController _passwordController = TextEditingController();
|
||||
final TextEditingController _phoneController = TextEditingController(text: '13293211008');
|
||||
final TextEditingController _passwordController = TextEditingController(text: 'Zsaq@123456');
|
||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
String _errorMessage = '';
|
||||
bool _isLoading = false;
|
||||
bool _obscurePassword = true;
|
||||
bool _agreed = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
_buildHeader(),
|
||||
|
||||
Transform.translate(
|
||||
offset: const Offset(0, -20),
|
||||
child: Container(
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(30),
|
||||
topRight: Radius.circular(30),
|
||||
image: DecorationImage(
|
||||
image: AssetImage('assets/images/bg-login.png'),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 30),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Image.asset(
|
||||
'assets/images/bg-login.png',
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 30),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
// 手机号
|
||||
const SizedBox(height: 150),
|
||||
Row(
|
||||
children: [
|
||||
Image.asset('assets/image/logo.png', height: 50),
|
||||
const SizedBox(width: 15),
|
||||
const Expanded(
|
||||
child: Text(
|
||||
'欢迎使用,\n智守安全云平台!',
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 50),
|
||||
_buildInputSection(
|
||||
label: "手机号码",
|
||||
controller: _phoneController,
|
||||
hintText: "请输入手机号...",
|
||||
keyboardType: TextInputType.phone,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
if (value == null || value.isEmpty)
|
||||
return '请输入手机号';
|
||||
}
|
||||
if (!RegExp(r'^1[3-9]\d{9}$').hasMatch(value)) {
|
||||
return '请输入有效的手机号';
|
||||
}
|
||||
// if (!RegExp(r'^1[3-9]\d{9}\$').hasMatch(value))
|
||||
// return '请输入有效的手机号';
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
Padding(padding: const EdgeInsets.symmetric(horizontal: 25),
|
||||
child: const Divider(height: 1, thickness: 1),
|
||||
const Divider(
|
||||
height: 1,
|
||||
thickness: 1,
|
||||
color: Colors.white70,
|
||||
),
|
||||
// 密码
|
||||
_buildInputSection(
|
||||
label: "密码",
|
||||
controller: _passwordController,
|
||||
|
@ -93,49 +125,52 @@ class _LoginPageState extends State<LoginPage> {
|
|||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
_obscurePassword
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
color: Colors.grey,
|
||||
? Icons.visibility_off
|
||||
: Icons.visibility,
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed:
|
||||
() =>
|
||||
setState(
|
||||
() => _obscurePassword = !_obscurePassword,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_obscurePassword = !_obscurePassword;
|
||||
});
|
||||
},
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
if (value == null || value.isEmpty)
|
||||
return '请输入密码';
|
||||
}
|
||||
if (value.length < 6) {
|
||||
return '密码长度至少6位';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
Padding(padding: const EdgeInsets.symmetric(horizontal: 25),
|
||||
child: const Divider(height: 1, thickness: 1),
|
||||
const Divider(
|
||||
height: 1,
|
||||
thickness: 1,
|
||||
color: Colors.white70,
|
||||
),
|
||||
// 登录按钮
|
||||
const SizedBox(height: 30),
|
||||
if (_errorMessage.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 25, vertical: 24),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 25),
|
||||
child: Text(
|
||||
_errorMessage,
|
||||
style: const TextStyle(color: Colors.blue),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: 48,
|
||||
child: ElevatedButton(
|
||||
onPressed: _isLoading ? null : _handleLogin,
|
||||
onPressed:
|
||||
(!_isLoading && _agreed) ? _handleLogin : null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: _isLoading
|
||||
? const CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
)
|
||||
: const Text(
|
||||
child:
|
||||
const Text(
|
||||
'登录',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
|
@ -146,63 +181,75 @@ class _LoginPageState extends State<LoginPage> {
|
|||
),
|
||||
),
|
||||
),
|
||||
Padding(padding: const EdgeInsets.only(left: 30, right: 30),
|
||||
child:Text(" 本平台为互联网非涉密平台,严禁处理、传输国家秘密和工作秘密",
|
||||
style: TextStyle(color: Colors.red))
|
||||
)
|
||||
],
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: _agreed,
|
||||
activeColor: Colors.white,
|
||||
checkColor: Colors.blueAccent,
|
||||
side: const BorderSide(color: Colors.white),
|
||||
onChanged:
|
||||
(v) => setState(() => _agreed = v ?? false),
|
||||
),
|
||||
Expanded(
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '我已阅读并同意',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: '《用户协议》',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF0D1D8C),
|
||||
),
|
||||
recognizer:
|
||||
TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
// 打开用户协议
|
||||
},
|
||||
),
|
||||
TextSpan(
|
||||
text: '和',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: '《隐私政策》',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFF0D1D8C),
|
||||
),
|
||||
recognizer:
|
||||
TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
// 打开隐私政策
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 顶部图片和文字
|
||||
Widget _buildHeader() {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/login-bg.png',
|
||||
width: double.infinity,
|
||||
fit: BoxFit.fitWidth,
|
||||
),
|
||||
Positioned(
|
||||
bottom: 40,
|
||||
left: 30,
|
||||
child: Column(
|
||||
children: [
|
||||
const Text(
|
||||
'欢迎登录',
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
shadows: [
|
||||
Shadow(
|
||||
blurRadius: 10,
|
||||
color: Colors.black45,
|
||||
offset: Offset(2, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'秦皇岛市应急管局\n数智应急管理平台',
|
||||
style: TextStyle(fontSize: 18, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
// 输入区域组件
|
||||
Widget _buildInputSection({
|
||||
required String label,
|
||||
required TextEditingController controller,
|
||||
|
@ -213,7 +260,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||
String? Function(String?)? validator,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 12),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
@ -222,7 +269,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black87,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
|
@ -231,56 +278,112 @@ class _LoginPageState extends State<LoginPage> {
|
|||
obscureText: obscureText,
|
||||
keyboardType: keyboardType,
|
||||
validator: validator,
|
||||
// 关键属性:让文字在行内垂直居中
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
decoration: InputDecoration(
|
||||
hintText: hintText,
|
||||
hintStyle: const TextStyle(color: Colors.white70),
|
||||
suffixIcon: suffixIcon,
|
||||
// 去掉默认的上下/左右 padding,让文字贴紧外层的 25 左边距
|
||||
isDense: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleLogin() async {
|
||||
// 表单校验
|
||||
if (!(_formKey.currentState?.validate() ?? false)) return;
|
||||
|
||||
// 登录处理
|
||||
void _handleLogin() {
|
||||
// 清除之前的错误信息
|
||||
setState(() => _errorMessage = '');
|
||||
final userName = _phoneController.text.trim();
|
||||
final userPwd = _passwordController.text;
|
||||
|
||||
// RSA 加密:encrypt 包的用法
|
||||
final parser = encrypt.RSAKeyParser();
|
||||
final pub = parser.parse(ApiService.publicKey) as RSAPublicKey;
|
||||
final encrypter = encrypt.Encrypter(encrypt.RSA(publicKey: pub));
|
||||
final plain = 'zcloudchina$userName,zy,$userPwd';
|
||||
|
||||
String keydataVal;
|
||||
try {
|
||||
keydataVal = encrypter.encrypt(plain).base64;
|
||||
} catch (e) {
|
||||
Fluttertoast.showToast(msg: '加密失败:$e', toastLength: Toast.LENGTH_LONG);
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _isLoading = true);
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => const Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
|
||||
try {
|
||||
final data = await ApiService.loginCheck(keydataVal);
|
||||
final result = data['result'] as String? ?? '';
|
||||
|
||||
if (result == 'success') {
|
||||
// 存储用户信息
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('USER', json.encode(data));
|
||||
await prefs.setStringList('remember', [userName, userPwd]);
|
||||
|
||||
SessionService.instance
|
||||
..setLoginUserId(data['USER_ID'] as String)
|
||||
..setCorpinfoId(data['CORPINFO_ID'] as String)
|
||||
..setDeptId(data['DEPARTMENT_ID'] as String)
|
||||
..setDeptLevel(data['DEPARTMENT_LEVEL'] as String)
|
||||
..setIsRest(data['ISREST'] as String)
|
||||
..setUsername(data['NAME'] as String)
|
||||
..setLoginUser(data); // 这里 data 保存整个用户 JSON
|
||||
|
||||
final weak = data['WEAK_PASSWORD'] == '1';
|
||||
final longTerm = data['LONG_TERM_PASSWORD_NOT_CHANGED'] == '1';
|
||||
|
||||
Navigator.of(context).pop(); // 关 loading
|
||||
setState(() => _isLoading = false);
|
||||
|
||||
if (weak) {
|
||||
// uni.redirectTo({
|
||||
// url: '/pages/login/forget/forget-reset?canBack=1'
|
||||
// });
|
||||
} else if (longTerm) {
|
||||
// uni.redirectTo({
|
||||
// url: '/pages/login/forget/forget-reset?canBack=2'
|
||||
// });
|
||||
|
||||
} else {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const MainPage()),
|
||||
MaterialPageRoute(builder: (_) => const MainPage()),
|
||||
);
|
||||
}
|
||||
|
||||
} else {
|
||||
// 理论上不会走这里,非 'success' 会抛 ApiException
|
||||
}
|
||||
|
||||
} on ApiException catch (e) {
|
||||
// 业务错误:
|
||||
Navigator.of(context).pop();
|
||||
setState(() => _isLoading = false);
|
||||
|
||||
String tip = e.message;
|
||||
|
||||
Fluttertoast.showToast(msg: tip, toastLength: Toast.LENGTH_LONG);
|
||||
|
||||
} catch (e) {
|
||||
// 网络或其它未预期错误
|
||||
Navigator.of(context).pop();
|
||||
setState(() => _isLoading = false);
|
||||
Fluttertoast.showToast(
|
||||
msg: '服务器正在升级,请稍后再试。\n${e.toString()}',
|
||||
toastLength: Toast.LENGTH_LONG,
|
||||
);
|
||||
// if (_formKey.currentState?.validate() ?? false) {
|
||||
// setState(() => _isLoading = true);
|
||||
//
|
||||
// // 模拟登录请求
|
||||
// Future.delayed(const Duration(seconds: 2), () {
|
||||
// setState(() => _isLoading = false);
|
||||
// // 登录成功,跳转到主页
|
||||
// // Navigator.pushReplacement(
|
||||
// // context,
|
||||
// // MaterialPageRoute(builder: (context) => const MainPage()),
|
||||
// // );
|
||||
// // 模拟登录逻辑
|
||||
// if (_phoneController.text == "13800138000" &&
|
||||
// _passwordController.text == "123456") {
|
||||
// // 登录成功,跳转到主页
|
||||
// Navigator.pushReplacement(
|
||||
// context,
|
||||
// MaterialPageRoute(builder: (context) => const MainPage()),
|
||||
// );
|
||||
// } else {
|
||||
// // 登录失败,显示错误信息
|
||||
// setState(() {
|
||||
// _errorMessage = '手机号或密码错误,请重试';
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class StorageService {
|
||||
StorageService._internal();
|
||||
static final StorageService instance = StorageService._internal();
|
||||
|
||||
late final SharedPreferences _prefs;
|
||||
|
||||
/// 启动时调用一次,确保 prefs 已就绪
|
||||
Future<void> init() async {
|
||||
_prefs = await SharedPreferences.getInstance();
|
||||
}
|
||||
|
||||
/// 存储 String
|
||||
Future<bool> setString(String key, String value) =>
|
||||
_prefs.setString(key, value);
|
||||
|
||||
/// 读取 String
|
||||
String? getString(String key) => _prefs.getString(key);
|
||||
|
||||
/// 存储 String 列表
|
||||
Future<bool> setStringList(String key, List<String> value) =>
|
||||
_prefs.setStringList(key, value);
|
||||
|
||||
/// 读取 String 列表
|
||||
List<String>? getStringList(String key) => _prefs.getStringList(key);
|
||||
|
||||
/// 存储 int
|
||||
Future<bool> setInt(String key, int value) => _prefs.setInt(key, value);
|
||||
|
||||
/// 读取 int
|
||||
int? getInt(String key) => _prefs.getInt(key);
|
||||
|
||||
/// 存储 bool
|
||||
Future<bool> setBool(String key, bool value) => _prefs.setBool(key, value);
|
||||
|
||||
/// 读取 bool
|
||||
bool? getBool(String key) => _prefs.getBool(key);
|
||||
|
||||
/// 存储 double
|
||||
Future<bool> setDouble(String key, double value) =>
|
||||
_prefs.setDouble(key, value);
|
||||
|
||||
/// 读取 double
|
||||
double? getDouble(String key) => _prefs.getDouble(key);
|
||||
|
||||
/// 删除单个 key
|
||||
Future<bool> remove(String key) => _prefs.remove(key);
|
||||
|
||||
/// 清空所有
|
||||
Future<bool> clear() => _prefs.clear();
|
||||
}
|
|
@ -17,15 +17,14 @@ double screenWidth(BuildContext context) {
|
|||
void pushPage(Widget page, BuildContext context) {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => page));
|
||||
}
|
||||
|
||||
void present(Widget page, BuildContext context) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) => page,
|
||||
),
|
||||
MaterialPageRoute(fullscreenDialog: true, builder: (context) => page),
|
||||
);
|
||||
}
|
||||
|
||||
/// 文本样式工具类
|
||||
/// 文本样式工具类
|
||||
/// 文本样式工具类,返回 Text Widget
|
||||
|
@ -50,7 +49,11 @@ class HhTextStyleUtils {
|
|||
),
|
||||
);
|
||||
}
|
||||
static TextStyle secondaryTitleStyle = TextStyle(color:Colors.black54, fontSize: 15.0);
|
||||
|
||||
static TextStyle secondaryTitleStyle = TextStyle(
|
||||
color: Colors.black54,
|
||||
fontSize: 15.0,
|
||||
);
|
||||
|
||||
/// 次要标题,返回 Text
|
||||
/// [text]: 文本内容
|
||||
|
@ -66,7 +69,6 @@ class HhTextStyleUtils {
|
|||
return Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
|
||||
color: color,
|
||||
fontSize: fontSize,
|
||||
fontWeight: bold ? FontWeight.bold : FontWeight.normal,
|
||||
|
@ -132,3 +134,119 @@ Future<AppVersionInfo> getAppVersion() async {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// ------------------------------------------------------
|
||||
/// 全局会话管理
|
||||
/// ------------------------------------------------------
|
||||
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;
|
||||
|
||||
/// 如果以下任何一项为空,则跳转到登录页
|
||||
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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// ------------------------------------------------------
|
||||
/// 日期格式化
|
||||
/// ------------------------------------------------------
|
||||
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 {
|
||||
// 可替换成 Toast
|
||||
debugPrint('请稍后点击');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void presentPage(BuildContext context, Widget page) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(fullscreenDialog: true, builder: (_) => page),
|
||||
);
|
||||
}
|
||||
|
|
56
pubspec.lock
|
@ -9,6 +9,14 @@ packages:
|
|||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
asn1lib:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: asn1lib
|
||||
sha256: "9a8f69025044eb466b9b60ef3bc3ac99b4dc6c158ae9c56d25eeccf5bc56d024"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.6.5"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -65,6 +73,14 @@ packages:
|
|||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -105,6 +121,30 @@ packages:
|
|||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "5.8.0+1"
|
||||
dio_web_adapter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dio_web_adapter
|
||||
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
encrypt:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: encrypt
|
||||
sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "5.0.3"
|
||||
extended_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -208,6 +248,14 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
fluttertoast:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: fluttertoast
|
||||
sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "8.2.12"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -528,6 +576,14 @@ packages:
|
|||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
pointycastle:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.9.1"
|
||||
provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
13
pubspec.yaml
|
@ -50,12 +50,13 @@ dependencies:
|
|||
video_player: ^2.10.0
|
||||
#网络监听
|
||||
connectivity_plus: ^6.1.4
|
||||
#网页页面加载
|
||||
webview_flutter: ^4.4.0
|
||||
#手写签字
|
||||
# signature: ^6.0.0
|
||||
|
||||
path_provider: ^2.0.1
|
||||
#接口请求
|
||||
dio: ^5.8.0+1
|
||||
#toast
|
||||
fluttertoast: ^8.2.12
|
||||
#RSA 加密库
|
||||
encrypt: ^5.0.3
|
||||
pointycastle: ^3.6.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|