Files
biz_xfj_cloud/lib/common/utils/app/app_setting_tool.dart
T
2026-07-28 13:13:50 +08:00

489 lines
18 KiB
Dart

import 'dart:convert';
import 'dart:typed_data';
import 'package:common_utils/common_utils.dart';
import 'package:get/get.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:pj_supermarket_cloud/common/index.dart';
abstract class AppSettingTool {
static const String _tag = "app配置工具";
static const String _settingKey = "setting";
static const String _terminalMenuOrderKey = "terminalMenuOrder";
static const String _hiddenTerminalMenusKey = "hiddenTerminalMenus";
static const String _payTypeOrderKey = "payTypeOrder";
static const String _reservationQueryMethodOrderKey =
"reservationQueryMethodOrder";
static const String _reservationQueryDefaultMethodKey =
"reservationQueryDefaultMethod";
static const String _reservationQueryNameMaskEnabledKey =
"reservationQueryNameMaskEnabled";
static const String _reservationQueryVerificationEnabledKey =
"reservationQueryVerificationEnabled";
static const String _reservationQueryVerificationDateScopeKey =
"reservationQueryVerificationDateScope";
/// 人工硬件输入历史在现有 sysJson 扩展配置中的存储键。
static const String _hardwareInputHistoryKey = "hardwareInputHistory";
// 自定义缓存管理器
static final CacheManager _settingCache = CacheManager(
Config(
'setting_cache',
stalePeriod: const Duration(days: 7),
maxNrOfCacheObjects: 10,
),
);
static Future<void> removeSettingCache() async {
await _settingCache.emptyCache();
}
static Future<SettingEntity> _getSetting() async {
// 尝试从缓存获取
final fileInfo = await _settingCache.getFileFromCache(_settingKey);
if (fileInfo != null) {
final content = await fileInfo.file.readAsString();
if (ObjectUtil.isNotEmpty(content)) {
return SettingEntity.fromJson(json.decode(content));
}
}
final app = Get.find<AppControl>();
final settingDao = app.dataBase.getSettingDao;
final setting = await settingDao.get();
if (setting == null) {
final initSysInfo = SysSetting(
sysType: SysType.CANTEEN,
androidId: await AppSysTool.getAndroidId(),
sqlAddress: "",
);
final initUserInfo = UserSetting(
username: "",
password: "",
);
SettingEntity addObj = SettingEntity(
userJson: json.encode(initUserInfo.toJson()),
sysJson: json.encode(initSysInfo.toJson()),
);
final addId = await settingDao.save(addObj);
if (ObjectUtil.isEmpty(addId)) {
throw Exception("|$_tag|_getSetting新增错误");
}
addObj.id = addId;
return addObj;
}
return setting;
}
static Future<bool> _setSetting({
SysSetting? sysJson,
UserSetting? userJson,
CanteenExtraPriceSetting? canteenExtraPriceConfig,
}) async {
if (ObjectUtil.isEmpty(sysJson) &&
ObjectUtil.isEmpty(userJson) &&
ObjectUtil.isEmpty(canteenExtraPriceConfig)) {
return false;
}
final app = Get.find<AppControl>();
final settingDao = app.dataBase.getSettingDao;
SettingEntity? setting = await settingDao.get();
if (setting == null) {
logger.e("|$_tag|_setSetting新增错误settingDao.get()方法返回为null");
return false;
}
if (sysJson != null) {
final current = Map<String, dynamic>.from(json.decode(setting.sysJson));
// sysJson 允许保存不涉及数据库结构的终端 UI 扩展配置,更新业务设置时必须保留这些键。
setting.sysJson = json.encode({...current, ...sysJson.toJson()});
}
if (userJson != null) {
setting.userJson = json.encode(userJson.toJson());
}
if (canteenExtraPriceConfig != null) {
setting.canteenExtraPriceConfigJson = json.encode(
canteenExtraPriceConfig.toJson(),
);
}
await settingDao.edit(setting);
await _settingCache.putFile(
_settingKey,
Uint8List.fromList(utf8.encode(json.encode(setting.toJson()))),
fileExtension: 'json',
);
return true;
}
static Future<SysSetting> getSysSetting() async {
final setting = await _getSetting();
return SysSetting.fromJson(json.decode(setting.sysJson));
}
static Future<UserSetting> getUserSetting() async {
final setting = await _getSetting();
return UserSetting.fromJson(json.decode(setting.userJson));
}
static Future<CanteenExtraPriceSetting?> getCanteenExtraPriceConfig() async {
final setting = await _getSetting();
if (ObjectUtil.isEmpty(setting.canteenExtraPriceConfigJson)) {
return null;
}
return CanteenExtraPriceSetting.fromJson(
json.decode(setting.canteenExtraPriceConfigJson!),
);
}
static Future<void> setSysSetting(SysSetting sysInfo) async {
await _setSetting(sysJson: sysInfo);
}
static Future<void> setUserSetting(UserSetting userInfo) async {
final globalUserInfo = await getUserSetting();
if (globalUserInfo == userInfo) return;
if (ObjectUtil.isEmpty(userInfo.tenantId) &&
ObjectUtil.isNotEmpty(globalUserInfo.tenantId)) {
userInfo.tenantId = globalUserInfo.tenantId;
}
await _setSetting(userJson: userInfo);
}
static Future<void> setCanteenExtraPriceConfig(
CanteenExtraPriceSetting canteenExtraPriceConfig,
) async {
final config = await getCanteenExtraPriceConfig();
if (config == canteenExtraPriceConfig) return;
await _setSetting(canteenExtraPriceConfig: canteenExtraPriceConfig);
}
/// 读取左侧菜单的顺序和隐藏状态,未知或缺失项由控制器按默认菜单补齐。
static Future<({List<String> order, Set<String> hidden})>
getTerminalMenuPreferences() async {
final setting = await _getSetting();
final sysJson = Map<String, dynamic>.from(json.decode(setting.sysJson));
final order = (sysJson[_terminalMenuOrderKey] as List<dynamic>?)
?.whereType<String>()
.toList() ??
const <String>[];
final hidden = (sysJson[_hiddenTerminalMenusKey] as List<dynamic>?)
?.whereType<String>()
.toSet() ??
const <String>{};
return (order: order, hidden: hidden);
}
/// 菜单布局属于设备级 UI 设置,直接复用 setting 表中的 sysJson,避免增加数据库字段和迁移。
static Future<void> setTerminalMenuPreferences({
required List<String> order,
required Set<String> hidden,
}) async {
final app = Get.find<AppControl>();
final settingDao = app.dataBase.getSettingDao;
final setting = await settingDao.get();
if (setting == null) {
throw StateError('终端设置尚未初始化');
}
final sysJson = Map<String, dynamic>.from(json.decode(setting.sysJson));
sysJson[_terminalMenuOrderKey] = order;
sysJson[_hiddenTerminalMenusKey] = hidden.toList();
setting.sysJson = json.encode(sysJson);
await settingDao.edit(setting);
await _settingCache.putFile(
_settingKey,
Uint8List.fromList(utf8.encode(json.encode(setting.toJson()))),
fileExtension: 'json',
);
}
/// 读取支付方式的完整顺序;显隐仍由 SysSetting.usePayDisplayType 维护。
static Future<List<String>> getPayTypeOrder() async {
final setting = await _getSetting();
final sysJson = Map<String, dynamic>.from(json.decode(setting.sysJson));
return (sysJson[_payTypeOrderKey] as List<dynamic>?)
?.whereType<String>()
.toList() ??
const <String>[];
}
/// 支付排序属于终端 UI 扩展配置,与现有支付显隐和默认方式一起原子保存。
static Future<void> setPayTypePreferences({
required SysSetting sysInfo,
required List<OrderPayDisplayType> order,
}) async {
// 在首次异步等待前生成快照,避免连续拖动时后一次修改污染本次落盘内容。
final nextSysJson = sysInfo.toJson();
final visiblePayTypes = nextSysJson['usePayDisplayType'];
final defaultPayType = nextSysJson['defaultPayDisplayType'];
final orderCodes = order.map((item) => item.code).toList();
final app = Get.find<AppControl>();
final settingDao = app.dataBase.getSettingDao;
final setting = await settingDao.get();
if (setting == null) {
throw StateError('终端设置尚未初始化');
}
final current = Map<String, dynamic>.from(json.decode(setting.sysJson));
current['usePayDisplayType'] = visiblePayTypes;
current['defaultPayDisplayType'] = defaultPayType;
current[_payTypeOrderKey] = orderCodes;
setting.sysJson = json.encode(current);
await settingDao.edit(setting);
await _settingCache.putFile(
_settingKey,
Uint8List.fromList(utf8.encode(json.encode(setting.toJson()))),
fileExtension: 'json',
);
}
/// 查询方式顺序和默认项属于页面交互配置,继续保存在 sysJson 中,不增加数据库字段。
static Future<ReservationQueryPagePreferences>
getReservationQueryPagePreferences() async {
final setting = await _getSetting();
final sysJson = Map<String, dynamic>.from(json.decode(setting.sysJson));
final byCode = {
for (final method in ReservationRecognitionMethod.values)
method.code: method,
};
final restored = <ReservationRecognitionMethod>[];
final storedOrder =
(sysJson[_reservationQueryMethodOrderKey] as List<dynamic>?)
?.whereType<String>() ??
const <String>[];
for (final code in storedOrder) {
final method = byCode[code];
if (method != null && !restored.contains(method)) restored.add(method);
}
for (final method in ReservationRecognitionMethod.values) {
if (!restored.contains(method)) restored.add(method);
}
final storedDefault =
byCode[sysJson[_reservationQueryDefaultMethodKey] as String?];
final defaultMethod =
storedDefault != null && restored.contains(storedDefault)
? storedDefault
: restored.first;
final verificationDateScope = ReservationVerificationDateScope.values
.where(
(scope) =>
scope.code ==
sysJson[_reservationQueryVerificationDateScopeKey],
)
.firstOrNull ??
ReservationVerificationDateScope.today;
return ReservationQueryPagePreferences(
methodOrder: List.unmodifiable(restored),
defaultMethod: defaultMethod,
// 旧设置没有该键时继续脱敏,避免升级后意外展示完整姓名。
nameMaskEnabled: sysJson[_reservationQueryNameMaskEnabledKey] != false,
verificationEnabled:
sysJson[_reservationQueryVerificationEnabledKey] == true,
verificationDateScope: verificationDateScope,
);
}
/// 顺序与默认项一起保存,避免连续操作后出现“默认项不在当前列表中”的矛盾状态。
static Future<void> setReservationQueryPagePreferences({
required List<ReservationRecognitionMethod> order,
required ReservationRecognitionMethod defaultMethod,
required bool nameMaskEnabled,
required bool verificationEnabled,
required ReservationVerificationDateScope verificationDateScope,
}) async {
final normalized = <ReservationRecognitionMethod>[];
for (final method in order) {
if (!normalized.contains(method)) normalized.add(method);
}
for (final method in ReservationRecognitionMethod.values) {
if (!normalized.contains(method)) normalized.add(method);
}
final normalizedDefault =
normalized.contains(defaultMethod) ? defaultMethod : normalized.first;
final app = Get.find<AppControl>();
final settingDao = app.dataBase.getSettingDao;
final setting = await settingDao.get();
if (setting == null) {
throw StateError('终端设置尚未初始化');
}
final sysJson = Map<String, dynamic>.from(json.decode(setting.sysJson));
sysJson[_reservationQueryMethodOrderKey] =
normalized.map((method) => method.code).toList();
sysJson[_reservationQueryDefaultMethodKey] = normalizedDefault.code;
sysJson[_reservationQueryNameMaskEnabledKey] = nameMaskEnabled;
sysJson[_reservationQueryVerificationEnabledKey] = verificationEnabled;
sysJson[_reservationQueryVerificationDateScopeKey] =
verificationDateScope.code;
setting.sysJson = json.encode(sysJson);
await settingDao.edit(setting);
await _settingCache.putFile(
_settingKey,
Uint8List.fromList(utf8.encode(json.encode(setting.toJson()))),
fileExtension: 'json',
);
}
/// 读取指定“路由 + 功能键”场景最近使用的人工硬件输入,异常旧数据会被忽略。
static Future<List<HardwareInputHistoryEntry>> getHardwareInputHistory(
String sceneKey,
) async {
final store = await _getHardwareInputHistoryStore();
return List.unmodifiable(store.entriesFor(sceneKey));
}
/// 保存当前场景最近 5 条输入;相同输入只更新备注和时间并置顶,不产生重复记录。
static Future<void> saveHardwareInputHistory({
required String sceneKey,
required String value,
String? remark,
}) async {
final normalizedValue = value.trim();
if (normalizedValue.isEmpty) return;
final app = Get.find<AppControl>();
final settingDao = app.dataBase.getSettingDao;
final setting = await settingDao.get();
if (setting == null) throw StateError('终端设置尚未初始化');
final sysJson = Map<String, dynamic>.from(json.decode(setting.sysJson));
final store = _decodeHardwareInputHistoryStore(
sysJson[_hardwareInputHistoryKey],
);
final nextStore = store.save(
sceneKey: sceneKey,
value: normalizedValue,
remark: remark ?? '',
updatedAt: DateTime.now(),
);
sysJson[_hardwareInputHistoryKey] = nextStore.toJson();
setting.sysJson = json.encode(sysJson);
await settingDao.edit(setting);
await _settingCache.putFile(
_settingKey,
Uint8List.fromList(utf8.encode(json.encode(setting.toJson()))),
fileExtension: 'json',
);
}
/// 从现有 sysJson 扩展配置读取强类型历史模型,单条异常数据不影响人工输入主流程。
static Future<HardwareInputHistoryStore>
_getHardwareInputHistoryStore() async {
final setting = await _getSetting();
final sysJson = Map<String, dynamic>.from(json.decode(setting.sysJson));
return _decodeHardwareInputHistoryStore(sysJson[_hardwareInputHistoryKey]);
}
/// JSON Map 仅停留在设置存储边界,弹窗与去重逻辑统一操作强类型模型。
static HardwareInputHistoryStore _decodeHardwareInputHistoryStore(
Object? source,
) {
if (source is! Map) return HardwareInputHistoryStore.empty;
try {
return HardwareInputHistoryStore.fromJson(
Map<String, dynamic>.from(source),
);
} on Object {
return HardwareInputHistoryStore.empty;
}
}
// 根据后台返回的数据更新到缓存
static Future<void> updateSysInfoByService(ServiceHardware data) async {
if (ObjectUtil.isEmpty(data)) {
final setting = SysSetting(
sysType: SysType.CANTEEN,
androidId: await AppSysTool.getAndroidId(),
sqlAddress: await AppFileTool.getDatabaseBackupPath(),
);
await _setSetting(sysJson: setting);
return;
}
final sysSetting = await AppSettingTool.getSysSetting();
final id = data.id;
if (ObjectUtil.isNotEmpty(id)) {
sysSetting.serviceId = id;
}
final name = data.name;
if (ObjectUtil.isNotEmpty(name)) {
sysSetting.serviceName = name;
}
final type = data.type.toString();
if (ObjectUtil.isNotEmpty(type)) {
final t = SysType.values.fromCode(type);
if (ObjectUtil.isNotEmpty(t)) {
sysSetting.sysType = t!;
}
}
final merchantId = data.merchantId;
if (ObjectUtil.isNotEmpty(merchantId)) {
sysSetting.serviceMerchantId = merchantId;
}
final hardGroupId = data.hardGroupId;
if (ObjectUtil.isNotEmpty(hardGroupId)) {
sysSetting.hardGroupId = hardGroupId;
}
final merchantDeptId = data.merchantDeptId;
if (ObjectUtil.isNotEmpty(merchantDeptId)) {
sysSetting.merchantDeptId = merchantDeptId;
}
final mqClientId = data.mqClientId;
if (ObjectUtil.isNotEmpty(mqClientId)) {
sysSetting.mqClientId = mqClientId;
}
final mainTopic = data.mainTopic;
if (ObjectUtil.isNotEmpty(mainTopic)) {
sysSetting.mainTopic = mainTopic;
}
final mqSocketAddress = data.mqSocketAddress;
if (ObjectUtil.isNotEmpty(mqSocketAddress)) {
sysSetting.mqSocketAddress = mqSocketAddress;
}
final mqSocketPort = data.mqSocketPort;
if (ObjectUtil.isNotEmpty(mqSocketPort)) {
sysSetting.mqSocketPort = mqSocketPort;
}
final mqUsername = data.mqUsername;
if (ObjectUtil.isNotEmpty(mqUsername)) {
sysSetting.mqUsername = mqUsername;
}
final mqPassword = data.mqPassword;
if (ObjectUtil.isNotEmpty(mqPassword)) {
sysSetting.mqPassword = mqPassword;
}
final faceAppId = data.faceAppId;
if (ObjectUtil.isNotEmpty(faceAppId)) {
sysSetting.faceAppId = faceAppId;
}
final faceSdkKey = data.faceSdkKey;
if (ObjectUtil.isNotEmpty(faceSdkKey)) {
sysSetting.faceSdkKey = faceSdkKey;
}
final faceActiveKey = data.faceActiveKey;
if (ObjectUtil.isNotEmpty(faceActiveKey)) {
sysSetting.faceActiveKey = faceActiveKey;
}
final android = data.uniqueCode;
if (ObjectUtil.isNotEmpty(android)) {
sysSetting.androidId = android;
}
sysSetting.syncTime = DateTime.now();
await _setSetting(sysJson: sysSetting);
}
/// 绑定设备信息
static Future<bool> bindSys(int serviceId) async {
var androidId = await AppSysTool.getAndroidId();
if (ObjectUtil.isEmpty(androidId)) return false;
final pa = {"id": serviceId, "uniqueCode": androidId};
try {
final data = await HardApi.bind(pa);
if (data == null) {
logger.e("绑定设备信息pa:|${jsonEncode(pa)}|;res:$data;");
return false;
}
await AppSettingTool.updateSysInfoByService(data);
return true;
} catch (e, stack) {
logger.e("|$_tag|绑定信息错误error:|$e|stack:$stack");
}
return false;
}
}