Files
biz_xfj_cloud/lib/pages/terminal/setting/widgets/setting_sync.dart
T
2026-07-24 15:27:20 +08:00

258 lines
8.4 KiB
Dart

import 'package:common_utils/common_utils.dart';
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
import 'package:flutter/material.dart';
import 'package:forui/forui.dart';
import 'package:get/get.dart';
import 'package:pj_supermarket_cloud/common/index.dart';
import '../controller.dart';
import 'setting_components.dart';
Widget settingSync(BuildContext context) {
final controller = Get.find<SettingController>();
return Obx(() {
final sys = controller.sysSetting.value;
if (sys == null) return const SettingEmpty(message: '暂无同步信息');
final deviceStatus = AppDeviceStatusTool.status.value;
final deviceProgress = AppDeviceStatusTool.progress.value;
final availableDeviceCount = [
deviceStatus.printerAvailable,
deviceStatus.steelyardAvailable,
deviceStatus.cardReaderAvailable,
deviceStatus.subScreenAvailable,
].where((available) => available).length;
final items = <_SyncItem>[
_SyncItem(
'deviceStatus',
'同步设备状态',
deviceStatus.updatedAt,
controller.syncDeviceStatus,
icon: FAssets.icons.hardDrive,
statusText: deviceProgress.running
? deviceProgress.message
: '已连接 $availableDeviceCount/4 个设备',
warning: availableDeviceCount < 4,
forceSyncing: deviceProgress.running,
),
_SyncItem('config', '同步设备信息', sys.syncTime, controller.syncConfig),
_SyncItem('mq', '同步长链接', sys.mqSyncTime, controller.syncMq),
_SyncItem(
'classify',
'同步分类',
sys.sysType == SysType.CANTEEN
? sys.canteenClassifySyncTime
: sys.supermarketClassifySyncTime,
controller.syncClassify,
),
_SyncItem(
'product',
'同步商品',
sys.sysType == SysType.CANTEEN
? sys.canteenProductSyncTime
: sys.supermarketProductSyncTime,
controller.syncProduct,
),
_SyncItem(
'offlineOrder',
'同步离线订单',
sys.offlineOrderSyncTime,
controller.syncOfflineOrder,
icon: FAssets.icons.cloudDownload,
),
];
return Column(
children: [
Expanded(
child: Container(
key: const ValueKey('setting-sync-list'),
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: XfjDesignTokens.surface,
border: Border.all(color: XfjDesignTokens.line),
borderRadius: BorderRadius.circular(XfjDesignTokens.cardRadius),
),
child: ListView.builder(
padding: EdgeInsets.zero,
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
final itemSyncing = controller.syncingKey.value == item.key ||
item.forceSyncing;
return _SyncRow(
item: item,
syncing: itemSyncing,
failed: controller.syncFailedKeys.contains(item.key),
enabled: controller.syncingKey.isEmpty ||
(item.key == 'deviceStatus' &&
(controller.syncingKey.value == 'deviceStatus' ||
item.forceSyncing)),
showDivider: index != items.length - 1,
);
},
),
),
),
SizedBox(height: XfjDesignTokens.cardGap.h),
const SettingNotice(
text: '网络接口可用不等于业务服务可用。同步失败时会保留本地数据,可在网络恢复后重新执行。',
),
],
);
});
}
class _SyncItem {
const _SyncItem(
this.key,
this.title,
this.time,
this.action, {
this.icon,
this.statusText,
this.warning = false,
this.forceSyncing = false,
});
final String key;
final String title;
final DateTime? time;
final Future<void> Function() action;
final SvgAsset? icon;
final String? statusText;
final bool warning;
final bool forceSyncing;
}
class _SyncRow extends StatelessWidget {
const _SyncRow({
required this.item,
required this.syncing,
required this.failed,
required this.enabled,
required this.showDivider,
});
final _SyncItem item;
final bool syncing;
final bool failed;
final bool enabled;
final bool showDivider;
@override
Widget build(BuildContext context) {
final theme = FTheme.of(context);
final statusColor = failed
? XfjDesignTokens.danger
: syncing
? XfjDesignTokens.primary
: item.warning
? XfjDesignTokens.warning
: XfjDesignTokens.success;
return Opacity(
opacity: enabled || syncing ? 1 : 0.55,
child: InkWell(
onTap: enabled ? item.action : null,
child: Container(
constraints: BoxConstraints(minHeight: 76.h),
padding: EdgeInsets.symmetric(horizontal: 18.w, vertical: 10.h),
decoration: BoxDecoration(
border: showDivider
? const Border(
bottom: BorderSide(color: XfjDesignTokens.line),
)
: null,
),
child: Row(
children: [
Container(
width: 44.w,
height: 44.w,
alignment: Alignment.center,
decoration: BoxDecoration(
color: XfjDesignTokens.primarySoft,
borderRadius: BorderRadius.circular(
XfjDesignTokens.controlRadius,
),
),
child: syncing
? SizedBox(
width: 22.w,
height: 22.w,
child: const CircularProgressIndicator(strokeWidth: 2),
)
: FIcon(
item.icon ?? FAssets.icons.refreshCw,
color: XfjDesignTokens.primary,
size: 24.sp,
),
),
SizedBox(width: 14.w),
Expanded(
child: Row(
children: [
Expanded(
child: Text(
item.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.typography.base.copyWith(
color: XfjDesignTokens.ink,
fontWeight: FontWeight.w700,
),
),
),
SizedBox(width: 20.w),
SizedBox(
width: 360.w,
child: Text(
'最后同步:${_formatTime(item.time)}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.right,
style: theme.typography.sm.copyWith(
color: XfjDesignTokens.muted,
fontFeatures: const [FontFeature.tabularFigures()],
),
),
),
SizedBox(width: 20.w),
SizedBox(
width: 320.w,
child: Text(
failed
? '同步失败,点击重试'
: syncing
? item.statusText ?? '同步中...'
: item.statusText ?? '状态正常',
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.right,
style: theme.typography.sm.copyWith(
color: statusColor,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
SizedBox(width: 14.w),
FIcon(
FAssets.icons.chevronRight,
color: XfjDesignTokens.muted,
size: 22.sp,
),
],
),
),
),
);
}
}
String _formatTime(DateTime? time) {
if (time == null) return '未同步';
return DateUtil.formatDate(time, format: DateFormats.full);
}