387 lines
11 KiB
Dart
387 lines
11 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:common_utils/common_utils.dart';
|
|
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
|
import 'package:forui/forui.dart';
|
|
import 'package:get/get.dart';
|
|
|
|
import 'package:pj_supermarket_cloud/common/index.dart';
|
|
|
|
import 'setting_components.dart';
|
|
|
|
class FaceManageController extends GetxController {
|
|
final appC = Get.find<AppControl>();
|
|
|
|
late SysSetting sysSetting;
|
|
|
|
Rx<SysType> sysType = SysType.CANTEEN.obs;
|
|
|
|
RxList<FaceEntity> faceList = RxList();
|
|
|
|
RxInt currentPage = RxInt(1);
|
|
|
|
RxInt totalPage = RxInt(0);
|
|
|
|
RxInt pageSize = RxInt(15);
|
|
|
|
RxBool isLoading = false.obs;
|
|
|
|
RxString errorMessage = ''.obs;
|
|
|
|
final usernameC = TextEditingController();
|
|
|
|
StreamSubscription? mqUpdateListen;
|
|
|
|
@override
|
|
void onInit() async {
|
|
super.onInit();
|
|
sysSetting = await AppSettingTool.getSysSetting();
|
|
sysType.value = sysSetting.sysType;
|
|
getFaceList();
|
|
_listenMqCallback();
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
mqUpdateListen?.cancel();
|
|
usernameC.dispose();
|
|
}
|
|
|
|
Future<void> getFaceList({bool isLoading = false}) async {
|
|
this.isLoading.value = true;
|
|
errorMessage.value = '';
|
|
if (isLoading) {
|
|
EasyLoading.show(
|
|
status: '查询中...',
|
|
maskType: EasyLoadingMaskType.black,
|
|
);
|
|
}
|
|
try {
|
|
final faceDao = appC.dataBase.getFaceDao;
|
|
String userName = usernameC.text;
|
|
if (ObjectUtil.isEmpty(userName)) userName = "-1";
|
|
final findCount = await faceDao.findCount(userName);
|
|
totalPage.value = findCount ?? 0;
|
|
final list = await faceDao.findAll(
|
|
userName,
|
|
pageSize.value,
|
|
(currentPage.value - 1) * pageSize.value,
|
|
);
|
|
faceList.value = list;
|
|
} catch (e, stack) {
|
|
errorMessage.value = '人脸数据加载失败,请稍后重试';
|
|
logger.e('|人脸管理|加载失败error:|$e|stack:|$stack|');
|
|
} finally {
|
|
this.isLoading.value = false;
|
|
if (isLoading) {
|
|
Future.delayed(const Duration(seconds: 1), () {
|
|
EasyLoading.dismiss();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// 同步人脸
|
|
Future<void> asyncFace({bool isAll = false}) async {
|
|
final isNetwork = AppNetworkTool.isAutoNetworkMsg();
|
|
if (!isNetwork) return;
|
|
final setting = await AppSettingTool.getSysSetting();
|
|
final serviceMerchantId = setting.serviceMerchantId;
|
|
if (ObjectUtil.isEmpty(serviceMerchantId)) {
|
|
Toast.error(msg: "请先绑定设备");
|
|
return;
|
|
}
|
|
if (AppSysTool.isProd()) {
|
|
final isActive = await AppFaceTool.isActiveSdk();
|
|
if (!isActive) {
|
|
Toast.error(msg: "人脸sdk激活失败");
|
|
return;
|
|
}
|
|
}
|
|
final isSure = await appMessageDialog(
|
|
Get.context!,
|
|
"是否同步所有人脸,这个过程可能比较长",
|
|
);
|
|
if (!isSure) return;
|
|
EasyLoading.show(
|
|
status: '同步中...',
|
|
maskType: EasyLoadingMaskType.black,
|
|
);
|
|
try {
|
|
await SyncFaceTool.delEmptyFace();
|
|
await SyncFaceTool.syncFace(
|
|
isAll: isAll,
|
|
onSyncGroup: () {
|
|
currentPage.value = 1;
|
|
getFaceList();
|
|
},
|
|
);
|
|
await getFaceList();
|
|
Toast.success(msg: '同步成功');
|
|
} catch (e, stack) {
|
|
Toast.error(msg: '同步人脸失败,请稍后重试');
|
|
logger.e('|人脸管理|同步失败error:|$e|stack:|$stack|');
|
|
} finally {
|
|
currentPage.value = 1;
|
|
EasyLoading.dismiss();
|
|
}
|
|
}
|
|
|
|
Future<void> delAllFace() async {
|
|
final isSure = await appMessageDialog(
|
|
Get.context!,
|
|
"是否删除全部人脸",
|
|
);
|
|
if (!isSure) return;
|
|
EasyLoading.show(
|
|
status: '删除中...',
|
|
maskType: EasyLoadingMaskType.black,
|
|
);
|
|
try {
|
|
final faceDao = appC.dataBase.getFaceDao;
|
|
await faceDao.delAll();
|
|
await AppFaceTool.removeFace(sampleId: '-1');
|
|
await getFaceList();
|
|
Toast.success(msg: '删除成功');
|
|
} catch (e, stack) {
|
|
Toast.error(msg: '删除全部人脸失败');
|
|
logger.e('|人脸管理|删除全部失败error:|$e|stack:|$stack|');
|
|
} finally {
|
|
EasyLoading.dismiss();
|
|
}
|
|
}
|
|
|
|
_listenMqCallback() {
|
|
mqUpdateListen =
|
|
appC.mqUpdateEventBus.on<MqtActionEnum>().listen((event) async {
|
|
switch (event) {
|
|
case MqtActionEnum.FACE:
|
|
getFaceList(isLoading: true);
|
|
break;
|
|
default:
|
|
}
|
|
});
|
|
}
|
|
|
|
/// 搜索
|
|
Future<void> doPage({int? page}) async {
|
|
if (page != null) currentPage.value = page;
|
|
// 收起键盘
|
|
FocusManager.instance.primaryFocus?.unfocus();
|
|
await getFaceList(isLoading: true);
|
|
}
|
|
|
|
/// 重置
|
|
Future<void> doReset() async {
|
|
currentPage.value = 1;
|
|
usernameC.clear();
|
|
// 收起键盘
|
|
FocusManager.instance.primaryFocus?.unfocus();
|
|
await getFaceList(isLoading: true);
|
|
}
|
|
|
|
// 删除人脸
|
|
Future<void> deleteFace(FaceEntity v) async {
|
|
final isSure = await appMessageDialog(Get.context!, "是否删除人脸");
|
|
if (!isSure) return;
|
|
EasyLoading.show(
|
|
status: '删除中...',
|
|
maskType: EasyLoadingMaskType.black,
|
|
);
|
|
try {
|
|
final faceDao = appC.dataBase.getFaceDao;
|
|
if (ObjectUtil.isNotEmpty(v.path)) {
|
|
await SyncFaceTool.delFaceByStorage(filePath: v.path!);
|
|
}
|
|
await faceDao.delById(v.id!);
|
|
await AppFaceTool.removeFace(sampleId: v.sampleId);
|
|
await getFaceList();
|
|
Toast.success(msg: '删除成功');
|
|
} catch (e, stack) {
|
|
Toast.error(msg: '删除人脸失败');
|
|
logger.e('|人脸管理|删除失败error:|$e|stack:|$stack|');
|
|
} finally {
|
|
EasyLoading.dismiss();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 搜索
|
|
Widget buildFaceSearch(BuildContext context) {
|
|
final faceC = Get.find<FaceManageController>();
|
|
final theme = FTheme.of(context);
|
|
return <Widget>[
|
|
Obx(
|
|
() => FTextField(
|
|
textInputAction: faceC.sysType.value == SysType.SUPERMARKET
|
|
? TextInputAction.none
|
|
: TextInputAction.newline,
|
|
keyboardType: faceC.sysType.value == SysType.SUPERMARKET
|
|
? TextInputType.none
|
|
: TextInputType.multiline,
|
|
controller: faceC.usernameC,
|
|
hint: '请输入姓名',
|
|
maxLines: 1,
|
|
onSubmit: (_) => faceC.doPage(),
|
|
),
|
|
).width(300.w),
|
|
FButton(
|
|
prefix: FIcon(
|
|
FAssets.icons.ban,
|
|
size: theme.typography.base.fontSize,
|
|
),
|
|
label: Text(
|
|
'重置',
|
|
style: theme.typography.base.copyWith(
|
|
color: theme.colorScheme.secondary,
|
|
),
|
|
),
|
|
onPress: () => faceC.doReset(),
|
|
),
|
|
FButton(
|
|
prefix: FIcon(
|
|
FAssets.icons.search,
|
|
size: theme.typography.base.fontSize,
|
|
),
|
|
label: Text(
|
|
'搜索',
|
|
style: theme.typography.base.copyWith(
|
|
color: theme.colorScheme.secondary,
|
|
),
|
|
),
|
|
onPress: () => faceC.doPage(),
|
|
),
|
|
FButton(
|
|
label: Text(
|
|
'同步本部门人脸',
|
|
style: theme.typography.base.copyWith(
|
|
color: theme.colorScheme.primary,
|
|
),
|
|
),
|
|
style: FButtonStyle.outline,
|
|
onPress: () => faceC.asyncFace(isAll: false),
|
|
),
|
|
FButton(
|
|
label: Text(
|
|
'同步全部人脸',
|
|
style: theme.typography.base.copyWith(
|
|
color: theme.colorScheme.primary,
|
|
),
|
|
),
|
|
style: FButtonStyle.outline,
|
|
onPress: () => faceC.asyncFace(isAll: true),
|
|
),
|
|
const Spacer(),
|
|
FButton(
|
|
label: Text(
|
|
'删除全部人脸',
|
|
style: theme.typography.base.copyWith(color: Colors.white),
|
|
),
|
|
style: FButtonStyle.destructive,
|
|
onPress: () => faceC.delAllFace(),
|
|
),
|
|
].toRowSpace(space: 20.w);
|
|
}
|
|
|
|
/// 列表
|
|
Widget buildFaceList(context) {
|
|
final theme = FTheme.of(context);
|
|
final productC = Get.find<FaceManageController>();
|
|
return Obx(
|
|
() {
|
|
if (productC.isLoading.value) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
if (productC.errorMessage.isNotEmpty) {
|
|
return SettingEmpty(message: productC.errorMessage.value);
|
|
}
|
|
if (productC.faceList.isEmpty) {
|
|
return const SettingEmpty(message: '暂无本地人脸样本');
|
|
}
|
|
return GridView.builder(
|
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: 5,
|
|
mainAxisSpacing: XfjDesignTokens.cardGap.h,
|
|
crossAxisSpacing: XfjDesignTokens.cardGap.w,
|
|
childAspectRatio: 1.2,
|
|
),
|
|
itemCount: productC.faceList.length,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
final v = productC.faceList[index];
|
|
return InkWell(
|
|
onTap: () => productC.deleteFace(v),
|
|
borderRadius: BorderRadius.circular(XfjDesignTokens.cardRadius),
|
|
child: Container(
|
|
padding: EdgeInsets.all(14.w),
|
|
decoration: BoxDecoration(
|
|
color: XfjDesignTokens.surface,
|
|
border: Border.all(color: XfjDesignTokens.line),
|
|
borderRadius: BorderRadius.circular(XfjDesignTokens.cardRadius),
|
|
boxShadow: XfjDesignTokens.surfaceShadow,
|
|
),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
ClipOval(
|
|
child: appImg(v.path, width: 72.w, height: 72.w),
|
|
),
|
|
SizedBox(height: 10.h),
|
|
Text(
|
|
v.userName,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: theme.typography.base.copyWith(
|
|
color: XfjDesignTokens.ink,
|
|
fontWeight: FontWeight.w800,
|
|
),
|
|
),
|
|
SizedBox(height: 5.h),
|
|
Text(
|
|
'用户编号:${_maskFaceId(v.userId.toString())}',
|
|
style: theme.typography.xs.copyWith(
|
|
color: XfjDesignTokens.muted,
|
|
),
|
|
),
|
|
SizedBox(height: 3.h),
|
|
Text(
|
|
'样本编号:${_maskFaceId(v.sampleId)}',
|
|
style: theme.typography.xs.copyWith(
|
|
color: XfjDesignTokens.muted,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget settingFace(BuildContext context) {
|
|
final faceC = Get.isRegistered<FaceManageController>()
|
|
? Get.find<FaceManageController>()
|
|
: Get.put(FaceManageController());
|
|
return <Widget>[
|
|
buildFaceSearch(context),
|
|
buildFaceList(context).expanded(),
|
|
Obx(
|
|
() => appPage(
|
|
context,
|
|
pageNo: faceC.currentPage.value,
|
|
pageSize: faceC.pageSize.value,
|
|
total: faceC.totalPage.value,
|
|
onPageChanged: (pageNo) => faceC.doPage(page: pageNo),
|
|
),
|
|
),
|
|
].toColumnSpace(space: XfjDesignTokens.cardGap.h);
|
|
}
|
|
|
|
String _maskFaceId(String value) {
|
|
if (value.length <= 4) return '***$value';
|
|
return '${value.substring(0, 1)}***${value.substring(value.length - 3)}';
|
|
}
|