ops-console v1.0.2 (versionCode 8) 源码入库
This commit is contained in:
622
lib/models/device.dart
Normal file
622
lib/models/device.dart
Normal file
@@ -0,0 +1,622 @@
|
||||
/// 设备状态枚举。
|
||||
enum DeviceStatus {
|
||||
/// 在线。
|
||||
online,
|
||||
|
||||
/// 离线。
|
||||
offline,
|
||||
|
||||
/// 告警(指标异常)。
|
||||
warning;
|
||||
|
||||
/// 状态中文名称。
|
||||
String get label {
|
||||
switch (this) {
|
||||
case DeviceStatus.online:
|
||||
return '在线';
|
||||
case DeviceStatus.offline:
|
||||
return '离线';
|
||||
case DeviceStatus.warning:
|
||||
return '告警';
|
||||
}
|
||||
}
|
||||
|
||||
/// 从后端状态字符串解析。
|
||||
///
|
||||
/// 规则(与「爱视后台」约定一致):`offline` → 离线、`warning` → 告警、
|
||||
/// 其余(含 `online` 与未知值)→ 在线。
|
||||
static DeviceStatus fromString(String? value) {
|
||||
final status = (value ?? '').toLowerCase();
|
||||
if (status == 'offline') {
|
||||
return DeviceStatus.offline;
|
||||
}
|
||||
if (status == 'warning') {
|
||||
return DeviceStatus.warning;
|
||||
}
|
||||
return DeviceStatus.online;
|
||||
}
|
||||
}
|
||||
|
||||
/// 设备指令常量(对应 /device/command/sendByMac|sendByOrg 的 command 字段)。
|
||||
class DeviceCommand {
|
||||
DeviceCommand._();
|
||||
|
||||
static const String powerOff = 'powerOff';
|
||||
static const String reboot = 'reboot';
|
||||
static const String screenshot = 'screenshot';
|
||||
static const String themeUpdate = 'themeUpdate';
|
||||
static const String versionUpdate = 'versionUpdate';
|
||||
static const String marqueeUpdate = 'marqueeUpdate';
|
||||
static const String weatherUpdate = 'weatherUpdate';
|
||||
static const String mediaZoneVol = 'mediaZoneVol';
|
||||
static const String defaultVol = 'defaultVol';
|
||||
static const String themeClear = 'themeClear';
|
||||
static const String cacheClear = 'cacheClear';
|
||||
static const String storeExpire = 'storeExpire';
|
||||
static const String storeDisable = 'storeDisable';
|
||||
static const String passwordUpdate = 'passwordUpdate';
|
||||
static const String worldAd = 'worldAd';
|
||||
}
|
||||
|
||||
/// 设备运行指标(CPU / 内存 / 磁盘 / 网络)。
|
||||
///
|
||||
/// 注意:真实后端不提供该类指标,仅 mock 模式有值;
|
||||
/// 由 [Device.hasMetrics] 标识当前数据是否包含指标。
|
||||
class DeviceMetrics {
|
||||
const DeviceMetrics({
|
||||
required this.cpuUsage,
|
||||
required this.memoryUsage,
|
||||
required this.diskUsage,
|
||||
required this.networkRxKbps,
|
||||
required this.networkTxKbps,
|
||||
});
|
||||
|
||||
/// 全零指标(真实模式下的占位值,配合 hasMetrics=false 使用)。
|
||||
const DeviceMetrics.zero()
|
||||
: cpuUsage = 0,
|
||||
memoryUsage = 0,
|
||||
diskUsage = 0,
|
||||
networkRxKbps = 0,
|
||||
networkTxKbps = 0;
|
||||
|
||||
/// CPU 使用率(0.0 ~ 1.0)。
|
||||
final double cpuUsage;
|
||||
|
||||
/// 内存使用率(0.0 ~ 1.0)。
|
||||
final double memoryUsage;
|
||||
|
||||
/// 磁盘使用率(0.0 ~ 1.0)。
|
||||
final double diskUsage;
|
||||
|
||||
/// 网络下行速率(KB/s)。
|
||||
final double networkRxKbps;
|
||||
|
||||
/// 网络上行速率(KB/s)。
|
||||
final double networkTxKbps;
|
||||
|
||||
/// 从后端 JSON 解析运行指标。
|
||||
///
|
||||
/// 兼容嵌套 `metrics` 对象与顶层扁平字段两种形态,并对百分比做 0~100→0~1
|
||||
/// 归一化;任意字段缺失时安全回退为 0,保证 UI 不崩溃。
|
||||
factory DeviceMetrics.fromJson(Map<String, dynamic> json) {
|
||||
return DeviceMetrics(
|
||||
cpuUsage: _normalizePercent(
|
||||
json['cpuUsage'] ?? json['cpu_usage'] ?? json['cpu'],
|
||||
),
|
||||
memoryUsage: _normalizePercent(
|
||||
json['memoryUsage'] ??
|
||||
json['memory_usage'] ??
|
||||
json['mem'] ??
|
||||
json['memUsage'] ??
|
||||
json['memory'],
|
||||
),
|
||||
diskUsage: _normalizePercent(
|
||||
json['diskUsage'] ?? json['disk_usage'] ?? json['disk'],
|
||||
),
|
||||
networkRxKbps: _toDouble(
|
||||
json['networkRxKbps'] ?? json['rxKbps'] ?? json['networkRx'],
|
||||
),
|
||||
networkTxKbps: _toDouble(
|
||||
json['networkTxKbps'] ?? json['txKbps'] ?? json['networkTx'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'cpuUsage': cpuUsage,
|
||||
'memoryUsage': memoryUsage,
|
||||
'diskUsage': diskUsage,
|
||||
'networkRxKbps': networkRxKbps,
|
||||
'networkTxKbps': networkTxKbps,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 将后端返回的百分数字段归一化为 0.0~1.0。
|
||||
///
|
||||
/// 兼容后端可能返回 0~100(如 87)或 0~1(如 0.87)两种形态,避免 UI 进度条
|
||||
/// 与百分比文本越界。
|
||||
double _normalizePercent(dynamic value) {
|
||||
if (value == null) return 0;
|
||||
final n = value is num
|
||||
? value.toDouble()
|
||||
: double.tryParse(value.toString());
|
||||
if (n == null) return 0;
|
||||
return n > 1 ? n / 100 : n;
|
||||
}
|
||||
|
||||
/// 将动态值安全转换为 double,无法解析时返回 0。
|
||||
double _toDouble(dynamic value) {
|
||||
if (value == null) return 0;
|
||||
return (value is num
|
||||
? value.toDouble()
|
||||
: double.tryParse(value.toString())) ??
|
||||
0;
|
||||
}
|
||||
|
||||
/// 设备模型。
|
||||
///
|
||||
/// 字段对齐真实后端 DeviceInfo(id / mac / name / deviceSn / deviceUid /
|
||||
/// status / groupId / lastSyncTime / createTime),并保留 UI 展示兼容字段
|
||||
/// (ip / location / os / uptimeSeconds / metrics / storeId),真实模式下取默认值。
|
||||
class Device {
|
||||
const Device({
|
||||
required this.id,
|
||||
required this.mac,
|
||||
required this.name,
|
||||
required this.model,
|
||||
required this.deviceSn,
|
||||
required this.deviceUid,
|
||||
required this.status,
|
||||
required this.groupId,
|
||||
this.storeId = '',
|
||||
required this.lastSyncTime,
|
||||
required this.createTime,
|
||||
this.ip = '',
|
||||
this.location = '',
|
||||
this.storeName = '',
|
||||
this.os = '',
|
||||
this.roomNo = '',
|
||||
this.uptimeSeconds = 0,
|
||||
this.hasMetrics = false,
|
||||
this.metrics = const DeviceMetrics.zero(),
|
||||
});
|
||||
|
||||
/// 设备唯一标识。
|
||||
final String id;
|
||||
|
||||
/// 设备 MAC 地址(指令下发的目标标识)。
|
||||
final String mac;
|
||||
|
||||
/// 设备名称(后端可能返回 MAC、SN 或自定义别名)。
|
||||
final String name;
|
||||
|
||||
/// 设备型号(如 rk3566_r、p321)。
|
||||
///
|
||||
/// 优先从后端 model/modelName/deviceModel/productModel 等字段解析;
|
||||
/// 缺失时降级为 [name](若 name 是 MAC 地址则跳过),再降级为 [deviceSn],
|
||||
/// 最终兜底 [mac],确保不会把 MAC 误当成型号展示。
|
||||
final String model;
|
||||
|
||||
/// 设备序列号。
|
||||
final String deviceSn;
|
||||
|
||||
/// 设备 UID。
|
||||
final String deviceUid;
|
||||
|
||||
/// 当前状态。
|
||||
final DeviceStatus status;
|
||||
|
||||
/// 分组 ID。
|
||||
final String groupId;
|
||||
|
||||
/// 所属门店 ID(用于拉取设备分组列表;后端命名多样,按常见别名解析)。
|
||||
final String storeId;
|
||||
|
||||
/// 房号(房间号,如「802」「A栋301」)。
|
||||
final String roomNo;
|
||||
|
||||
/// 最近一次同步时间。
|
||||
final DateTime? lastSyncTime;
|
||||
|
||||
/// 创建时间。
|
||||
final DateTime? createTime;
|
||||
|
||||
/// IP 地址(真实后端无此字段,默认回退为 MAC)。
|
||||
final String ip;
|
||||
|
||||
/// 机房 / 位置(真实后端无此字段,默认为空)。
|
||||
final String location;
|
||||
|
||||
/// 所属门店名称(真实后端可能返回 storeName / orgName)。
|
||||
final String storeName;
|
||||
|
||||
/// 操作系统描述(真实后端无此字段,默认为空)。
|
||||
final String os;
|
||||
|
||||
/// 已连续运行时长(秒)(真实后端无此字段,默认为 0)。
|
||||
final int uptimeSeconds;
|
||||
|
||||
/// 是否包含运行指标(仅 mock 模式为 true)。
|
||||
final bool hasMetrics;
|
||||
|
||||
/// 运行指标。
|
||||
final DeviceMetrics metrics;
|
||||
|
||||
/// 创建副本并覆盖指定字段。
|
||||
Device copyWith({
|
||||
String? id,
|
||||
String? mac,
|
||||
String? name,
|
||||
String? model,
|
||||
String? deviceSn,
|
||||
String? deviceUid,
|
||||
DeviceStatus? status,
|
||||
String? groupId,
|
||||
String? storeId,
|
||||
String? roomNo,
|
||||
DateTime? lastSyncTime,
|
||||
DateTime? createTime,
|
||||
String? ip,
|
||||
String? location,
|
||||
String? storeName,
|
||||
String? os,
|
||||
int? uptimeSeconds,
|
||||
bool? hasMetrics,
|
||||
DeviceMetrics? metrics,
|
||||
}) {
|
||||
return Device(
|
||||
id: id ?? this.id,
|
||||
mac: mac ?? this.mac,
|
||||
name: name ?? this.name,
|
||||
model: model ?? this.model,
|
||||
deviceSn: deviceSn ?? this.deviceSn,
|
||||
deviceUid: deviceUid ?? this.deviceUid,
|
||||
status: status ?? this.status,
|
||||
groupId: groupId ?? this.groupId,
|
||||
storeId: storeId ?? this.storeId,
|
||||
roomNo: roomNo ?? this.roomNo,
|
||||
lastSyncTime: lastSyncTime ?? this.lastSyncTime,
|
||||
createTime: createTime ?? this.createTime,
|
||||
ip: ip ?? this.ip,
|
||||
location: location ?? this.location,
|
||||
storeName: storeName ?? this.storeName,
|
||||
os: os ?? this.os,
|
||||
uptimeSeconds: uptimeSeconds ?? this.uptimeSeconds,
|
||||
hasMetrics: hasMetrics ?? this.hasMetrics,
|
||||
metrics: metrics ?? this.metrics,
|
||||
);
|
||||
}
|
||||
|
||||
/// 设备标识:统一为「MAC / 房号」组合。
|
||||
/// 优先 MAC + 房号;任一缺失则只用存在的那项;两者皆空时逐级降级到
|
||||
/// name → model → mac,确保永不展示空白。
|
||||
String get macRoomLabel {
|
||||
final m = mac.trim();
|
||||
final r = roomNo.trim();
|
||||
if (m.isNotEmpty && r.isNotEmpty) return '$m / $r';
|
||||
if (m.isNotEmpty) return m;
|
||||
if (r.isNotEmpty) return r;
|
||||
if (name.isNotEmpty) return name;
|
||||
if (model.isNotEmpty) return model;
|
||||
return m;
|
||||
}
|
||||
|
||||
/// 仅展示 MAC 地址(房号改为独立「房号:XXX」列示,不再用「/」把两者粘在一起)。
|
||||
/// MAC 为空时逐级降级到 name → model → mac,确保标题不空白。
|
||||
String get macLabel {
|
||||
final m = mac.trim();
|
||||
if (m.isNotEmpty) return m;
|
||||
if (name.isNotEmpty) return name;
|
||||
if (model.isNotEmpty) return model;
|
||||
return m;
|
||||
}
|
||||
|
||||
factory Device.fromJson(Map<String, dynamic> json) {
|
||||
// 兼容详情接口可能再包一层 {device|info|data:{...}} 的形态,先兜底解包。
|
||||
final Map<String, dynamic> data = _unwrapDeviceJson(json);
|
||||
final mac = data['mac']?.toString() ?? '';
|
||||
final metricsJson = data['metrics'];
|
||||
// 合并嵌套 metrics 与顶层扁平遥测字段(详情接口可能直接返回 cpuUsage /
|
||||
// cpu_usage / cpu 等)。任一使用率字段存在即视为有指标数据,便于详情页展示。
|
||||
final Map<String, dynamic> mergedMetrics = <String, dynamic>{};
|
||||
if (metricsJson is Map<String, dynamic>) {
|
||||
mergedMetrics.addAll(metricsJson);
|
||||
}
|
||||
final flatCpu = _firstNum(data, [
|
||||
'cpuUsage',
|
||||
'cpu_usage',
|
||||
'cpu',
|
||||
'cpuPercent',
|
||||
'cpu_percent',
|
||||
'cpuRate',
|
||||
'cpu_rate',
|
||||
]);
|
||||
if (flatCpu != null) {
|
||||
mergedMetrics.putIfAbsent('cpuUsage', () => flatCpu);
|
||||
}
|
||||
final flatMem = _firstNum(data, [
|
||||
'memoryUsage',
|
||||
'memory_usage',
|
||||
'mem',
|
||||
'memUsage',
|
||||
'memory',
|
||||
'memoryPercent',
|
||||
'mem_percent',
|
||||
]);
|
||||
if (flatMem != null) {
|
||||
mergedMetrics.putIfAbsent('memoryUsage', () => flatMem);
|
||||
}
|
||||
final flatDisk = _firstNum(data, [
|
||||
'diskUsage',
|
||||
'disk_usage',
|
||||
'disk',
|
||||
'diskPercent',
|
||||
'disk_percent',
|
||||
]);
|
||||
if (flatDisk != null) {
|
||||
mergedMetrics.putIfAbsent('diskUsage', () => flatDisk);
|
||||
}
|
||||
// 爱视后台列表字段名为 deviceName / device_name,name 字段常缺失或存 MAC/IP。
|
||||
final name = data['deviceName']?.toString() ??
|
||||
data['device_name']?.toString() ??
|
||||
data['name']?.toString() ??
|
||||
'';
|
||||
// IP 可能落在 ip / ipAddress / ip_address / deviceIp / device_ip 等多个字段。
|
||||
final ip = data['ip']?.toString() ??
|
||||
data['ipAddress']?.toString() ??
|
||||
data['ip_address']?.toString() ??
|
||||
data['deviceIp']?.toString() ??
|
||||
data['device_ip']?.toString() ??
|
||||
'';
|
||||
final deviceSn = data['deviceSn']?.toString() ?? '';
|
||||
// 型号字段后端命名多样,按常见别名逐级解析;解析不到时按规则降级。
|
||||
final rawModel = _firstString(data, [
|
||||
'model',
|
||||
'modelName',
|
||||
'model_name',
|
||||
'deviceModel',
|
||||
'device_model',
|
||||
'productModel',
|
||||
'product_model',
|
||||
'productName',
|
||||
'product_name',
|
||||
'board',
|
||||
'hardware',
|
||||
'brand',
|
||||
'deviceType',
|
||||
'device_type',
|
||||
'type',
|
||||
]) ??
|
||||
'';
|
||||
final model = _resolveModel(rawModel, name, deviceSn, mac);
|
||||
return Device(
|
||||
id: data['id']?.toString() ?? '',
|
||||
mac: mac,
|
||||
name: name,
|
||||
model: model,
|
||||
deviceSn: deviceSn,
|
||||
deviceUid: data['deviceUid']?.toString() ?? '',
|
||||
status: DeviceStatus.fromString(data['status']?.toString()),
|
||||
groupId: data['groupId']?.toString() ?? '',
|
||||
storeId: _resolveStoreId(data),
|
||||
// Web 端设备列表把最近心跳时间存为 heartbeatTime / heartbeat_time,
|
||||
// 详情接口多为 lastSyncTime;兼容两种命名,避免最后活跃时间解析为空。
|
||||
lastSyncTime: _parseDateTime(
|
||||
data['lastSyncTime'] ??
|
||||
data['last_sync_time'] ??
|
||||
data['heartbeatTime'] ??
|
||||
data['heartbeat_time'] ??
|
||||
data['lastHeartbeat'] ??
|
||||
data['last_heartbeat'],
|
||||
),
|
||||
createTime: _parseDateTime(data['createTime']),
|
||||
// UI 兼容字段:真实数据缺失时回退。
|
||||
ip: ip,
|
||||
location: _firstString(data, [
|
||||
'location',
|
||||
'locationDesc',
|
||||
'loc',
|
||||
'position',
|
||||
'place',
|
||||
'area',
|
||||
'region',
|
||||
]) ??
|
||||
'',
|
||||
storeName: data['storeName']?.toString() ??
|
||||
data['orgName']?.toString() ??
|
||||
data['store']?.toString() ?? '',
|
||||
os: _firstString(data, [
|
||||
'os',
|
||||
'osName',
|
||||
'system',
|
||||
'systemVersion',
|
||||
'osVersion',
|
||||
'platform',
|
||||
'osInfo',
|
||||
'sysVersion',
|
||||
]) ??
|
||||
'',
|
||||
// 房号/房间号字段后端命名极不统一,按常见别名逐级回退。
|
||||
roomNo: _firstString(data, [
|
||||
'roomNo',
|
||||
'room',
|
||||
'roomNoStr',
|
||||
'roomNumber',
|
||||
'room_number',
|
||||
'roomNum',
|
||||
'roomNumStr',
|
||||
'roomId',
|
||||
'room_id',
|
||||
'roomName',
|
||||
'houseNumber',
|
||||
'house_number',
|
||||
'houseNo',
|
||||
'houseNoStr',
|
||||
'roomCode',
|
||||
'room_code',
|
||||
]) ??
|
||||
'',
|
||||
uptimeSeconds: _firstNum(data, [
|
||||
'uptimeSeconds',
|
||||
'uptime',
|
||||
'uptimeSec',
|
||||
'uptimeSecs',
|
||||
'runTime',
|
||||
'runningTime',
|
||||
'onlineTime',
|
||||
'bootTime',
|
||||
'upTime',
|
||||
'up_time',
|
||||
])?.toInt() ?? 0,
|
||||
hasMetrics: mergedMetrics.isNotEmpty,
|
||||
metrics: mergedMetrics.isNotEmpty
|
||||
? DeviceMetrics.fromJson(mergedMetrics)
|
||||
: const DeviceMetrics.zero(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 兜底解包设备 JSON。
|
||||
///
|
||||
/// 后端 /device/info/info/{id} 详情接口可能并未把设备对象直接作为响应体,
|
||||
/// 而是再包一层 `{device|info|data: {...}}`;若顶层没有 `id` 但某嵌套字段
|
||||
/// 是含 `id` 的 Map,则返回该嵌套 Map,否则原样返回。
|
||||
/// 这样可避免详情页所有遥测字段因解析不到而显示为空。
|
||||
static Map<String, dynamic> _unwrapDeviceJson(Map<String, dynamic> json) {
|
||||
if (json.containsKey('id')) return json;
|
||||
for (final key in const ['device', 'info', 'data', 'result', 'record']) {
|
||||
final nested = json[key];
|
||||
if (nested is Map<String, dynamic> &&
|
||||
(nested.containsKey('id') || nested.containsKey('mac'))) {
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// 从 [json] 中按 [keys] 顺序取第一个非空数值。
|
||||
///
|
||||
/// 兼容后端返回字符串或数字两种形态;任意匹配键取到有效数值即返回,
|
||||
/// 否则返回 null(交由调用处按默认 0 处理,避免 UI 崩溃)。
|
||||
static num? _firstNum(
|
||||
Map<String, dynamic> json,
|
||||
List<String> keys,
|
||||
) {
|
||||
for (final key in keys) {
|
||||
final value = json[key];
|
||||
if (value == null) continue;
|
||||
if (value is num) return value;
|
||||
final n = double.tryParse(value.toString());
|
||||
if (n != null) return n;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 从 [json] 中按 [keys] 顺序取第一个非空字符串值。
|
||||
static String? _firstString(
|
||||
Map<String, dynamic> json,
|
||||
List<String> keys,
|
||||
) {
|
||||
for (final key in keys) {
|
||||
final value = json[key];
|
||||
if (value == null) continue;
|
||||
final text = value.toString().trim();
|
||||
if (text.isNotEmpty) return text;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 解析设备所属门店 ID。
|
||||
///
|
||||
/// 后端字段命名极不统一:可能直接在顶层返回 `storeId` / `orgId`,也可能嵌套在
|
||||
/// `store` / `storeInfo` / `org` 对象中。本方法先尝试顶层字段,再尝试嵌套对象
|
||||
/// 中的 `id` / `orgId` / `storeId`,尽力避免分组下拉因取不到门店 ID 而无法加载。
|
||||
static String _resolveStoreId(Map<String, dynamic> json) {
|
||||
final topLevel = _firstString(json, [
|
||||
'storeId',
|
||||
'storeOrgId',
|
||||
'orgId',
|
||||
'store_id',
|
||||
'org_id',
|
||||
'storeIdStr',
|
||||
]);
|
||||
if (topLevel != null && topLevel.isNotEmpty) return topLevel;
|
||||
|
||||
for (final nestedKey in const ['store', 'storeInfo', 'org', 'organization']) {
|
||||
final nested = json[nestedKey];
|
||||
if (nested is Map<String, dynamic>) {
|
||||
final id = _firstString(nested, [
|
||||
'id',
|
||||
'orgId',
|
||||
'storeId',
|
||||
'store_id',
|
||||
'org_id',
|
||||
]);
|
||||
if (id != null && id.isNotEmpty) return id;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/// 解析最终展示的型号文本。
|
||||
///
|
||||
/// 优先级:
|
||||
/// 1. 后端明确返回的 [rawModel]
|
||||
/// 2. [name](仅当非空且不是 MAC 地址时)
|
||||
/// 3. [deviceSn]
|
||||
/// 4. [mac](最终兜底,避免空标题)
|
||||
static String _resolveModel(
|
||||
String rawModel,
|
||||
String name,
|
||||
String deviceSn,
|
||||
String mac,
|
||||
) {
|
||||
if (rawModel.isNotEmpty) return rawModel;
|
||||
if (name.isNotEmpty && !_isMacAddress(name)) return name;
|
||||
if (deviceSn.isNotEmpty) return deviceSn;
|
||||
return mac;
|
||||
}
|
||||
|
||||
/// 判断 [value] 是否为 MAC 地址格式(xx:xx:xx:xx:xx:xx 或 xx-xx-xx-xx-xx-xx)。
|
||||
static bool _isMacAddress(String value) {
|
||||
return RegExp(r'^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$')
|
||||
.hasMatch(value.trim());
|
||||
}
|
||||
|
||||
/// 兼容 `yyyy-MM-dd HH:mm:ss` 与 ISO 8601 两种时间格式。
|
||||
static DateTime? _parseDateTime(dynamic value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
final text = value.toString().trim();
|
||||
if (text.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return DateTime.tryParse(text) ??
|
||||
DateTime.tryParse(text.replaceFirst(' ', 'T'));
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'mac': mac,
|
||||
'name': name,
|
||||
'model': model,
|
||||
'deviceSn': deviceSn,
|
||||
'deviceUid': deviceUid,
|
||||
'status': status.name,
|
||||
'groupId': groupId,
|
||||
'storeId': storeId,
|
||||
'lastSyncTime': lastSyncTime?.toIso8601String(),
|
||||
'createTime': createTime?.toIso8601String(),
|
||||
'ip': ip,
|
||||
'location': location,
|
||||
'storeName': storeName,
|
||||
'os': os,
|
||||
'roomId': roomNo,
|
||||
'uptimeSeconds': uptimeSeconds,
|
||||
'metrics': hasMetrics ? metrics.toJson() : null,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user