diff --git a/backend/api-gateway/kong.yml b/backend/api-gateway/kong.yml index 39c6423c..04a1e5d9 100644 --- a/backend/api-gateway/kong.yml +++ b/backend/api-gateway/kong.yml @@ -240,6 +240,10 @@ services: paths: - /api/v1/customer-service-contacts strip_path: false + - name: admin-tree-pricing-public + paths: + - /api/v1/tree-pricing + strip_path: false # --------------------------------------------------------------------------- # Presence Service - 在线状态服务 diff --git a/backend/services/admin-service/src/pricing/tree-pricing.controller.ts b/backend/services/admin-service/src/pricing/tree-pricing.controller.ts index 425d00f1..fa0416a5 100644 --- a/backend/services/admin-service/src/pricing/tree-pricing.controller.ts +++ b/backend/services/admin-service/src/pricing/tree-pricing.controller.ts @@ -96,7 +96,7 @@ export class AdminTreePricingController { * 不需要管理员认证 */ @ApiTags('认种定价配置-公开API') -@Controller('api/v1/tree-pricing') +@Controller('tree-pricing') export class PublicTreePricingController { constructor( private readonly pricingService: TreePricingService, diff --git a/frontend/mobile-app/lib/features/planting/presentation/pages/planting_quantity_page.dart b/frontend/mobile-app/lib/features/planting/presentation/pages/planting_quantity_page.dart index b8f5f2fc..8394db3f 100644 --- a/frontend/mobile-app/lib/features/planting/presentation/pages/planting_quantity_page.dart +++ b/frontend/mobile-app/lib/features/planting/presentation/pages/planting_quantity_page.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; @@ -47,6 +48,12 @@ class _PlantingQuantityPageState extends ConsumerState { /// 是否显示超出警告 bool _showExceedWarning = false; + /// 涨价倒计时 timer + Timer? _priceCountdownTimer; + + /// 距下次涨价的剩余时间 + Duration _timeUntilIncrease = Duration.zero; + @override void initState() { super.initState(); @@ -65,6 +72,7 @@ class _PlantingQuantityPageState extends ConsumerState { _pricingConfig = config; _pricePerTree = config.totalPrice.toDouble(); }); + _startPriceCountdown(); debugPrint('[PlantingQuantity] 动态定价: ${config.totalPrice} USDT/棵 (supplement=${config.currentSupplement})'); } } catch (e) { @@ -75,12 +83,40 @@ class _PlantingQuantityPageState extends ConsumerState { @override void dispose() { + _priceCountdownTimer?.cancel(); _quantityController.removeListener(_onQuantityChanged); _quantityController.dispose(); _quantityFocusNode.dispose(); super.dispose(); } + /// 启动涨价倒计时 + void _startPriceCountdown() { + _priceCountdownTimer?.cancel(); + final config = _pricingConfig; + if (config == null || !config.autoIncreaseEnabled || config.nextAutoIncreaseAt == null) { + return; + } + _updateCountdown(); + _priceCountdownTimer = Timer.periodic(const Duration(minutes: 1), (_) { + _updateCountdown(); + }); + } + + void _updateCountdown() { + final nextAt = _pricingConfig?.nextAutoIncreaseAt; + if (nextAt == null) return; + final remaining = nextAt.difference(DateTime.now()); + if (mounted) { + setState(() { + _timeUntilIncrease = remaining.isNegative ? Duration.zero : remaining; + }); + } + if (remaining.isNegative) { + _priceCountdownTimer?.cancel(); + } + } + /// 输入框数量变化监听 void _onQuantityChanged() { final text = _quantityController.text; @@ -707,35 +743,68 @@ class _PlantingQuantityPageState extends ConsumerState { color: Color(0xFF000000), ), ), - // 下次涨价预告 + // 下次涨价倒计时预告 if (_pricingConfig != null && _pricingConfig!.autoIncreaseEnabled && _pricingConfig!.nextAutoIncreaseAt != null) ...[ const SizedBox(height: 12), Container( - padding: const EdgeInsets.all(10), + padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFFFFF3E0), borderRadius: BorderRadius.circular(8), + border: Border.all(color: const Color(0xFFFFCC80), width: 0.5), ), - child: Row( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Icon( - Icons.trending_up, - color: Color(0xFFE65100), - size: 18, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - '预计涨价: ${_pricingConfig!.nextAutoIncreaseAt!.month}月${_pricingConfig!.nextAutoIncreaseAt!.day}日后每棵 +${_pricingConfig!.autoIncreaseAmount} 绿积分', - style: const TextStyle( - fontSize: 13, - fontFamily: 'Inter', - height: 1.4, + // 倒计时行 + Row( + children: [ + const Icon( + Icons.timer_outlined, color: Color(0xFFE65100), + size: 18, ), - ), + const SizedBox(width: 6), + Expanded( + child: Text( + _timeUntilIncrease == Duration.zero + ? '涨价时间已到,价格即将更新' + : '距下次涨价还有 ${_timeUntilIncrease.inDays}天 ${_timeUntilIncrease.inHours % 24}小时 ${_timeUntilIncrease.inMinutes % 60}分钟', + style: const TextStyle( + fontSize: 14, + fontFamily: 'Inter', + fontWeight: FontWeight.w600, + height: 1.4, + color: Color(0xFFE65100), + ), + ), + ), + ], + ), + const SizedBox(height: 6), + // 涨价后价格 + Row( + children: [ + const Icon( + Icons.trending_up, + color: Color(0xFFE65100), + size: 16, + ), + const SizedBox(width: 6), + Expanded( + child: Text( + '涨价后每棵: ${(_pricingConfig!.totalPrice + _pricingConfig!.autoIncreaseAmount).toInt()} 绿积分 (+${_pricingConfig!.autoIncreaseAmount.toInt()})', + style: const TextStyle( + fontSize: 13, + fontFamily: 'Inter', + height: 1.4, + color: Color(0xFFBF360C), + ), + ), + ), + ], ), ], ),