fix(planting): 修复认种页面动态定价不生效 + 添加涨价倒计时

- 修复 admin-service PublicTreePricingController 路由双重前缀问题
  (@Controller('api/v1/tree-pricing') → @Controller('tree-pricing'))
- Kong 网关新增 /api/v1/tree-pricing 路由到 admin-service
- mobile-app 认种页面添加涨价倒计时功能:
  显示"距下次涨价还有 X天 X小时 X分钟"及涨价后价格

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-26 10:22:27 -08:00
parent 19f350b8e3
commit 3a307b5db7
3 changed files with 91 additions and 18 deletions

View File

@ -240,6 +240,10 @@ services:
paths: paths:
- /api/v1/customer-service-contacts - /api/v1/customer-service-contacts
strip_path: false strip_path: false
- name: admin-tree-pricing-public
paths:
- /api/v1/tree-pricing
strip_path: false
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Presence Service - 在线状态服务 # Presence Service - 在线状态服务

View File

@ -96,7 +96,7 @@ export class AdminTreePricingController {
* *
*/ */
@ApiTags('认种定价配置-公开API') @ApiTags('认种定价配置-公开API')
@Controller('api/v1/tree-pricing') @Controller('tree-pricing')
export class PublicTreePricingController { export class PublicTreePricingController {
constructor( constructor(
private readonly pricingService: TreePricingService, private readonly pricingService: TreePricingService,

View File

@ -1,3 +1,4 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@ -47,6 +48,12 @@ class _PlantingQuantityPageState extends ConsumerState<PlantingQuantityPage> {
/// ///
bool _showExceedWarning = false; bool _showExceedWarning = false;
/// timer
Timer? _priceCountdownTimer;
///
Duration _timeUntilIncrease = Duration.zero;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -65,6 +72,7 @@ class _PlantingQuantityPageState extends ConsumerState<PlantingQuantityPage> {
_pricingConfig = config; _pricingConfig = config;
_pricePerTree = config.totalPrice.toDouble(); _pricePerTree = config.totalPrice.toDouble();
}); });
_startPriceCountdown();
debugPrint('[PlantingQuantity] 动态定价: ${config.totalPrice} USDT/棵 (supplement=${config.currentSupplement})'); debugPrint('[PlantingQuantity] 动态定价: ${config.totalPrice} USDT/棵 (supplement=${config.currentSupplement})');
} }
} catch (e) { } catch (e) {
@ -75,12 +83,40 @@ class _PlantingQuantityPageState extends ConsumerState<PlantingQuantityPage> {
@override @override
void dispose() { void dispose() {
_priceCountdownTimer?.cancel();
_quantityController.removeListener(_onQuantityChanged); _quantityController.removeListener(_onQuantityChanged);
_quantityController.dispose(); _quantityController.dispose();
_quantityFocusNode.dispose(); _quantityFocusNode.dispose();
super.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() { void _onQuantityChanged() {
final text = _quantityController.text; final text = _quantityController.text;
@ -707,31 +743,39 @@ class _PlantingQuantityPageState extends ConsumerState<PlantingQuantityPage> {
color: Color(0xFF000000), color: Color(0xFF000000),
), ),
), ),
// //
if (_pricingConfig != null && if (_pricingConfig != null &&
_pricingConfig!.autoIncreaseEnabled && _pricingConfig!.autoIncreaseEnabled &&
_pricingConfig!.nextAutoIncreaseAt != null) ...[ _pricingConfig!.nextAutoIncreaseAt != null) ...[
const SizedBox(height: 12), const SizedBox(height: 12),
Container( Container(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFFFF3E0), color: const Color(0xFFFFF3E0),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFFFFCC80), width: 0.5),
), ),
child: Row( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
Row(
children: [ children: [
const Icon( const Icon(
Icons.trending_up, Icons.timer_outlined,
color: Color(0xFFE65100), color: Color(0xFFE65100),
size: 18, size: 18,
), ),
const SizedBox(width: 8), const SizedBox(width: 6),
Expanded( Expanded(
child: Text( child: Text(
'预计涨价: ${_pricingConfig!.nextAutoIncreaseAt!.month}${_pricingConfig!.nextAutoIncreaseAt!.day}日后每棵 +${_pricingConfig!.autoIncreaseAmount} 绿积分', _timeUntilIncrease == Duration.zero
? '涨价时间已到,价格即将更新'
: '距下次涨价还有 ${_timeUntilIncrease.inDays}${_timeUntilIncrease.inHours % 24}小时 ${_timeUntilIncrease.inMinutes % 60}分钟',
style: const TextStyle( style: const TextStyle(
fontSize: 13, fontSize: 14,
fontFamily: 'Inter', fontFamily: 'Inter',
fontWeight: FontWeight.w600,
height: 1.4, height: 1.4,
color: Color(0xFFE65100), color: Color(0xFFE65100),
), ),
@ -739,6 +783,31 @@ class _PlantingQuantityPageState extends ConsumerState<PlantingQuantityPage> {
), ),
], ],
), ),
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),
),
),
),
],
),
],
),
), ),
], ],
], ],