fix(client): 审计修复——device_limit 相对时间走 relativeTime 单源+裸 Icons 换 PangolinIcons+Payment onDone 三处接通+内联双语文案收进 AppText

This commit is contained in:
wangjia
2026-07-13 09:56:22 +08:00
parent e95995f20f
commit 3a8ff64921
13 changed files with 73 additions and 30 deletions
+3
View File
@@ -565,6 +565,9 @@ abstract class AppText {
String get switchPayMethod; // 换一种支付方式 / Switch payment method
String get cancelOrder; // 取消订单 / Cancel order
String get qrNotSupported;
String get orderCreateFailed; // 下单失败,请稍后重试 / Failed to create order, please retry(errorZh/En 为空时兜底)
String get payUnsupportedRenderType; // 暂不支持,请换支付方式 / Unsupported, switch payment method
String get payNoActiveOrder; // 暂无进行中的订单 / No active order
// ── 支付渠道 switch + 订单 ──
String get payChannel;
+6
View File
@@ -427,6 +427,12 @@ class StringsEn extends AppText {
String get cancelOrder => 'Cancel order';
@override
String get qrNotSupported => 'Copy and open in Alipay';
@override
String get orderCreateFailed => 'Failed to create order, please retry';
@override
String get payUnsupportedRenderType => 'Unsupported, switch payment method';
@override
String get payNoActiveOrder => 'No active order';
// ── 支付渠道 switch + 订单 ──
@override
+6
View File
@@ -428,6 +428,12 @@ class StringsEs extends AppText {
String get cancelOrder => 'Cancelar pedido';
@override
String get qrNotSupported => 'Copia el contenido y ábrelo en Alipay';
@override
String get orderCreateFailed => 'Error al crear el pedido, inténtalo de nuevo más tarde';
@override
String get payUnsupportedRenderType => 'No compatible, cambia el método de pago';
@override
String get payNoActiveOrder => 'No hay pedidos activos';
// ── 支付渠道 switch + 订单 ──
@override
+6
View File
@@ -428,6 +428,12 @@ class StringsJa extends AppText {
String get cancelOrder => '注文をキャンセル';
@override
String get qrNotSupported => '内容をコピーしてAlipayで開いてください';
@override
String get orderCreateFailed => '注文の作成に失敗しました。しばらくして再試行してください';
@override
String get payUnsupportedRenderType => '未対応です。支払い方法を変更してください';
@override
String get payNoActiveOrder => '進行中の注文はありません';
// ── 支付渠道 switch + 订单 ──
@override
+6
View File
@@ -428,6 +428,12 @@ class StringsKo extends AppText {
String get cancelOrder => '주문 취소';
@override
String get qrNotSupported => '내용을 복사해 Alipay에서 여세요';
@override
String get orderCreateFailed => '주문 생성에 실패했습니다. 잠시 후 다시 시도하세요';
@override
String get payUnsupportedRenderType => '지원되지 않습니다. 결제 방법을 변경하세요';
@override
String get payNoActiveOrder => '진행 중인 주문이 없습니다';
// ── 支付渠道 switch + 订单 ──
@override
+6
View File
@@ -428,6 +428,12 @@ class StringsRu extends AppText {
String get cancelOrder => 'Отменить заказ';
@override
String get qrNotSupported => 'Скопируйте и откройте в Alipay';
@override
String get orderCreateFailed => 'Не удалось создать заказ, повторите попытку позже';
@override
String get payUnsupportedRenderType => 'Не поддерживается, смените способ оплаты';
@override
String get payNoActiveOrder => 'Нет активных заказов';
// ── 支付渠道 switch + 订单 ──
@override
+6
View File
@@ -421,6 +421,12 @@ class StringsZh extends AppText {
String get cancelOrder => '取消订单';
@override
String get qrNotSupported => '请复制内容后在支付宝内打开';
@override
String get orderCreateFailed => '下单失败,请稍后重试';
@override
String get payUnsupportedRenderType => '暂不支持,请换支付方式';
@override
String get payNoActiveOrder => '暂无进行中的订单';
// ── 支付渠道 switch + 订单 ──
@override
+5 -4
View File
@@ -64,8 +64,8 @@ class AccountPage extends ConsumerWidget {
NavView.purchase,
PurchaseScreen(
t: t,
onOrderCreated: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (_) => PaymentScreen(t: t))),
onOrderCreated: () => Navigator.of(context).push(MaterialPageRoute(
builder: (_) => PaymentScreen(t: t, onDone: () => Navigator.of(context).pop()))),
),
),
),
@@ -87,8 +87,9 @@ class AccountPage extends ConsumerWidget {
NavView.purchase,
PurchaseScreen(
t: t,
onOrderCreated: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (_) => PaymentScreen(t: t))),
onOrderCreated: () => Navigator.of(context).push(MaterialPageRoute(
builder: (_) =>
PaymentScreen(t: t, onDone: () => Navigator.of(context).pop()))),
),
)),
Divider(height: 1, color: c.border),
+10 -16
View File
@@ -12,6 +12,7 @@ import '../services/auth_api.dart';
import '../services/device_identity.dart';
import '../state/account_providers.dart';
import '../state/auth_provider.dart';
import '../widgets/pangolin_icons.dart';
class DeviceLimitScreen extends ConsumerStatefulWidget {
const DeviceLimitScreen({super.key, required this.t, required this.limit});
@@ -84,7 +85,7 @@ class _DeviceLimitScreenState extends ConsumerState<DeviceLimitScreen> {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 32),
Icon(Icons.devices_other_rounded, size: 40, color: c.accent),
Icon(PangolinIcons.monitorSmartphone, size: 40, color: c.accent),
const SizedBox(height: 14),
Text(t.deviceLimitTitle,
textAlign: TextAlign.center,
@@ -97,7 +98,7 @@ class _DeviceLimitScreenState extends ConsumerState<DeviceLimitScreen> {
if (removableCount > 0)
OutlinedButton.icon(
onPressed: _busyUuid != null ? null : () => _removeOldest(devices, currentId),
icon: const Icon(Icons.history_rounded, size: 18),
icon: const Icon(PangolinIcons.clock, size: 18),
label: Text(t.deviceLimitRemoveOldest),
style: OutlinedButton.styleFrom(
foregroundColor: c.accent,
@@ -136,7 +137,9 @@ class _DeviceLimitScreenState extends ConsumerState<DeviceLimitScreen> {
final busy = _busyUuid == d.uuid;
final sub = d.online
? t.devOnline
: (d.lastSeen != null ? '${t.devLastOnline} ${_rel(d.lastSeen!)}' : t.devOffline);
: (d.lastSeen != null
? '${t.devLastOnline} ${t.lang.relativeTime(DateTime.now().difference(d.lastSeen!.toLocal()))}'
: t.devOffline);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration: BoxDecoration(
@@ -184,18 +187,9 @@ class _DeviceLimitScreenState extends ConsumerState<DeviceLimitScreen> {
}
static IconData _platformIcon(DevicePlatform p) => switch (p) {
DevicePlatform.ios || DevicePlatform.android => Icons.smartphone_rounded,
DevicePlatform.windows || DevicePlatform.linux => Icons.computer_rounded,
DevicePlatform.macos => Icons.laptop_mac_rounded,
DevicePlatform.unknown => Icons.devices_other_rounded,
DevicePlatform.ios || DevicePlatform.android => PangolinIcons.smartphone,
DevicePlatform.windows || DevicePlatform.linux => PangolinIcons.monitor,
DevicePlatform.macos => PangolinIcons.laptop,
DevicePlatform.unknown => PangolinIcons.monitorSmartphone,
};
// 相对时间(粗粒度):x 分钟/小时/天前。
static String _rel(DateTime t) {
final d = DateTime.now().toUtc().difference(t.toUtc());
if (d.inMinutes < 1) return '刚刚';
if (d.inMinutes < 60) return '${d.inMinutes} 分钟前';
if (d.inHours < 24) return '${d.inHours} 小时前';
return '${d.inDays} 天前';
}
}
+11 -1
View File
@@ -252,7 +252,17 @@ class _OrderDetailView extends ConsumerWidget {
builder: (_) => PurchaseScreen(
t: t,
onOrderCreated: () => Navigator.of(context).push(
MaterialPageRoute(builder: (_) => PaymentScreen(t: t)),
MaterialPageRoute(
builder: (_) => PaymentScreen(
t: t,
onDone: () {
Navigator.of(context).pop();
// 支付页「完成/取消」回到订单详情:失效列表 + 本单详情,拉新状态。
ref.invalidate(ordersProvider);
ref.invalidate(orderDetailProvider(order.orderNo));
},
),
),
),
),
));
+4 -3
View File
@@ -209,8 +209,9 @@ class _PaymentScreenState extends ConsumerState<PaymentScreen> {
Text(t.qrNotSupported, style: PangolinText.caption.copyWith(color: c.fg3)),
]));
default:
renderBody = _card(c, child: Text('${session.renderType}: ${_zh ? "暂不支持,请换支付方式" : "Unsupported, switch method"}',
style: PangolinText.body.copyWith(color: c.fg2)));
renderBody = _card(c,
child: Text('${session.renderType}: ${t.payUnsupportedRenderType}',
style: PangolinText.body.copyWith(color: c.fg2)));
}
return ListView(padding: const EdgeInsets.fromLTRB(20, 14, 20, 24), children: [
@@ -306,7 +307,7 @@ class _PaymentScreenState extends ConsumerState<PaymentScreen> {
PaymentPhase.succeeded => _succeeded(context, s),
PaymentPhase.failed => _failed(context, ref, s),
PaymentPhase.idle => Center(
child: Text(_zh ? '暂无进行中的订单' : 'No active order',
child: Text(t.payNoActiveOrder,
style: PangolinText.body.copyWith(color: c.fg3))),
};
+3 -5
View File
@@ -38,6 +38,7 @@ class PurchaseScreen extends ConsumerStatefulWidget {
class _PurchaseScreenState extends ConsumerState<PurchaseScreen> {
AppText get t => widget.t;
bool get _zh => t.lang == AppLang.zh;
// 支付渠道:默认 USDT·加密货币。切换即刷新套餐卡显价币种。
PayChannel _channel = PayChannel.usdt;
@@ -59,9 +60,7 @@ class _PurchaseScreenState extends ConsumerState<PurchaseScreen> {
// 下单失败给可见 toast,别让用户以为「点了没反应」。
showPangolinToast(
context,
t.lang == AppLang.zh
? (st.errorZh ?? '下单失败,请稍后重试')
: (st.errorEn ?? 'Failed to create order, please retry'),
(_zh ? st.errorZh : st.errorEn) ?? t.orderCreateFailed,
);
}
}
@@ -77,8 +76,7 @@ class _PurchaseScreenState extends ConsumerState<PurchaseScreen> {
error: (_, __) => Center(
child: Padding(
padding: const EdgeInsets.all(40),
child: Text(t.lang == AppLang.zh ? '加载失败,请重试' : 'Failed to load, retry',
style: PangolinText.body.copyWith(color: c.fg3)),
child: Text(t.lang.loadFailedRetry, style: PangolinText.body.copyWith(color: c.fg3)),
),
),
data: (items) {
+1 -1
View File
@@ -126,7 +126,7 @@ class UpdateBanner extends ConsumerWidget {
IconButton(
onPressed: () =>
ref.read(dismissedUpdateVersionProvider.notifier).state = info.latestVersion,
icon: Icon(Icons.close, size: 18, color: c.fg3),
icon: Icon(PangolinIcons.x, size: 18, color: c.fg3),
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 36, minHeight: 36),
tooltip: t.updateLater,