优化套餐显示逻辑新增多语言

This commit is contained in:
2025-10-18 15:25:28 +08:00
parent a3b7c418b1
commit 89c7334caf
11 changed files with 198 additions and 77 deletions
+9 -1
View File
@@ -231,7 +231,15 @@
"expand": "Expand", "expand": "Expand",
"collapse": "Collapse", "collapse": "Collapse",
"confirmPurchase": "Confirm Purchase", "confirmPurchase": "Confirm Purchase",
"confirmPurchaseDesc": "Are you sure you want to purchase this package?" "confirmPurchaseDesc": "Are you sure you want to purchase this package?",
"timeUnit": {
"oneWeek": "1 Week",
"oneMonth": "1 Month",
"oneQuarter": "1 Quarter",
"halfYear": "6 Months",
"oneYear": "1 Year",
"days": "{count} Days"
}
}, },
"orderStatus": { "orderStatus": {
"title": "Order Status", "title": "Order Status",
+9 -1
View File
@@ -284,7 +284,15 @@
"expand": "Expandir", "expand": "Expandir",
"collapse": "Colapsar", "collapse": "Colapsar",
"confirmPurchase": "Confirmar Compra", "confirmPurchase": "Confirmar Compra",
"confirmPurchaseDesc": "¿Está seguro de que desea comprar este paquete?" "confirmPurchaseDesc": "¿Está seguro de que desea comprar este paquete?",
"timeUnit": {
"oneWeek": "1 Semana",
"oneMonth": "1 Mes",
"oneQuarter": "1 Trimestre",
"halfYear": "6 Meses",
"oneYear": "1 Año",
"days": "{count} Días"
}
}, },
"orderStatus": { "orderStatus": {
"title": "Estado del Pedido", "title": "Estado del Pedido",
+9 -1
View File
@@ -228,7 +228,15 @@
"expand": "Laienda", "expand": "Laienda",
"collapse": "Sulge", "collapse": "Sulge",
"confirmPurchase": "Kinnita Ost", "confirmPurchase": "Kinnita Ost",
"confirmPurchaseDesc": "Kas olete kindel, et soovite selle paketi osta?" "confirmPurchaseDesc": "Kas olete kindel, et soovite selle paketi osta?",
"timeUnit": {
"oneWeek": "1 nädal",
"oneMonth": "1 kuu",
"oneQuarter": "1 kvartal",
"halfYear": "6 kuud",
"oneYear": "1 aasta",
"days": "{count} päeva"
}
}, },
"orderStatus": { "orderStatus": {
"title": "Tellimuse olek", "title": "Tellimuse olek",
+9 -1
View File
@@ -245,7 +245,15 @@
"collapse": "折りたたむ", "collapse": "折りたたむ",
"confirmPurchase": "購入を確認", "confirmPurchase": "購入を確認",
"confirmPurchaseDesc": "このパッケージを購入してもよろしいですか?", "confirmPurchaseDesc": "このパッケージを購入してもよろしいですか?",
"subscriptionPrivacyInfo": "サブスクリプションとプライバシー情報" "subscriptionPrivacyInfo": "サブスクリプションとプライバシー情報",
"timeUnit": {
"oneWeek": "1週間",
"oneMonth": "1ヶ月",
"oneQuarter": "1四半期",
"halfYear": "6ヶ月",
"oneYear": "1年",
"days": "{count}日"
}
}, },
"orderStatus": { "orderStatus": {
"title": "注文状態", "title": "注文状態",
+9 -1
View File
@@ -246,7 +246,15 @@
"expand": "Развернуть", "expand": "Развернуть",
"collapse": "Свернуть", "collapse": "Свернуть",
"confirmPurchase": "Подтвердить покупку", "confirmPurchase": "Подтвердить покупку",
"confirmPurchaseDesc": "Вы уверены, что хотите приобрести этот пакет?" "confirmPurchaseDesc": "Вы уверены, что хотите приобрести этот пакет?",
"timeUnit": {
"oneWeek": "1 Неделя",
"oneMonth": "1 Месяц",
"oneQuarter": "1 Квартал",
"halfYear": "6 Месяцев",
"oneYear": "1 Год",
"days": "{count} Дней"
}
}, },
"orderStatus": { "orderStatus": {
"title": "Статус заказа", "title": "Статус заказа",
+9 -1
View File
@@ -294,7 +294,15 @@
"collapse": "收起", "collapse": "收起",
"confirmPurchase": "确认购买", "confirmPurchase": "确认购买",
"confirmPurchaseDesc": "您确定要购买此套餐吗?", "confirmPurchaseDesc": "您确定要购买此套餐吗?",
"subscriptionPrivacyInfo": "订阅和隐私信息" "subscriptionPrivacyInfo": "订阅和隐私信息",
"timeUnit": {
"oneWeek": "一周",
"oneMonth": "一个月",
"oneQuarter": "一个季度",
"halfYear": "半年",
"oneYear": "一年",
"days": "{count}天"
}
}, },
"orderStatus": { "orderStatus": {
"title": "订单状态", "title": "订单状态",
@@ -231,7 +231,15 @@
"expand": "展開", "expand": "展開",
"collapse": "收起", "collapse": "收起",
"confirmPurchase": "確認購買", "confirmPurchase": "確認購買",
"confirmPurchaseDesc": "您確定要購買此套餐嗎?" "confirmPurchaseDesc": "您確定要購買此套餐嗎?",
"timeUnit": {
"oneWeek": "一週",
"oneMonth": "一個月",
"oneQuarter": "一季度",
"halfYear": "半年",
"oneYear": "一年",
"days": "{count}天"
}
}, },
"home": { "home": {
"welcome": "歡迎使用 BearVPN", "welcome": "歡迎使用 BearVPN",
@@ -749,6 +749,25 @@ class AppTranslationsPurchaseMembership {
/// 确认购买描述 /// 确认购买描述
String get confirmPurchaseDesc => 'purchaseMembership.confirmPurchaseDesc'.tr; String get confirmPurchaseDesc => 'purchaseMembership.confirmPurchaseDesc'.tr;
/// 时间单位:一周
String get oneWeek => 'purchaseMembership.timeUnit.oneWeek'.tr;
/// 时间单位:一个月
String get oneMonth => 'purchaseMembership.timeUnit.oneMonth'.tr;
/// 时间单位:一个季度
String get oneQuarter => 'purchaseMembership.timeUnit.oneQuarter'.tr;
/// 时间单位:半年
String get halfYear => 'purchaseMembership.timeUnit.halfYear'.tr;
/// 时间单位:一年
String get oneYear => 'purchaseMembership.timeUnit.oneYear'.tr;
/// 时间单位:天数
String days(int count) =>
'purchaseMembership.timeUnit.days'.trParams({'count': count.toString()});
} }
/// 订单状态模块的翻译类 /// 订单状态模块的翻译类
@@ -372,20 +372,26 @@ class KRPurchaseMembershipController extends GetxController {
} }
/// 获取套餐价格 /// 获取套餐价格
/// discount: 0 表示无折扣(原价),其他值表示折扣百分比
double kr_getPlanPrice(KRPackageListItem plan, {int? discountIndex}) { double kr_getPlanPrice(KRPackageListItem plan, {int? discountIndex}) {
if (discountIndex != null && if (discountIndex != null &&
discountIndex >= 0 && discountIndex >= 0 &&
discountIndex < plan.kr_discount.length) { discountIndex < plan.kr_discount.length) {
// 计算折扣价格 // 计算折扣价格
final discount = plan.kr_discount[discountIndex]; final discount = plan.kr_discount[discountIndex];
// 如果 discount 是 0,则表示原价(100%
final discountRate = discount.kr_discount == 0 ? 100.0 : discount.kr_discount.toDouble();
return (plan.kr_unitPrice / 100) * return (plan.kr_unitPrice / 100) *
discount.kr_quantity * discount.kr_quantity *
(discount.kr_discount / 100); (discountRate / 100);
} }
return plan.kr_unitPrice / 100; return plan.kr_unitPrice / 100;
} }
/// 获取时间字符串 /// 获取时间字符串
/// 根据 quantity 和 unit_time 转换成多语言描述
/// Day: 7天 -> "一周"30天 -> "一个月"90天 -> "一个季度"180天 -> "半年"365天 -> "一年"
/// Month: 3月 -> "一个季度"6月 -> "半年"12月 -> "一年"
String kr_getTimeStr(KRPackageListItem plan, {int? discountIndex}) { String kr_getTimeStr(KRPackageListItem plan, {int? discountIndex}) {
final quantity = discountIndex != null && final quantity = discountIndex != null &&
discountIndex >= 0 && discountIndex >= 0 &&
@@ -393,25 +399,52 @@ class KRPurchaseMembershipController extends GetxController {
? plan.kr_discount[discountIndex].kr_quantity ? plan.kr_discount[discountIndex].kr_quantity
: 1; : 1;
if (plan.kr_unitTime == 'Month') { // 如果是 Day 单位,需要转换为对应的描述
return AppTranslations.kr_purchaseMembership.month(quantity); if (plan.kr_unitTime == 'Day') {
switch (quantity) {
case 7:
return AppTranslations.kr_purchaseMembership.oneWeek;
case 30:
return AppTranslations.kr_purchaseMembership.oneMonth;
case 90:
return AppTranslations.kr_purchaseMembership.oneQuarter;
case 180:
return AppTranslations.kr_purchaseMembership.halfYear;
case 365:
return AppTranslations.kr_purchaseMembership.oneYear;
default:
return AppTranslations.kr_purchaseMembership.days(quantity);
}
} else if (plan.kr_unitTime == 'Month') {
// 月份也需要转换
switch (quantity) {
case 1:
return AppTranslations.kr_purchaseMembership.oneMonth;
case 3:
return AppTranslations.kr_purchaseMembership.oneQuarter;
case 6:
return AppTranslations.kr_purchaseMembership.halfYear;
case 12:
return AppTranslations.kr_purchaseMembership.oneYear;
default:
return AppTranslations.kr_purchaseMembership.month(quantity);
}
} else if (plan.kr_unitTime == 'Year') { } else if (plan.kr_unitTime == 'Year') {
return AppTranslations.kr_purchaseMembership.year(quantity); return AppTranslations.kr_purchaseMembership.year(quantity);
} else if (plan.kr_unitTime == 'Day') {
return AppTranslations.kr_purchaseMembership.day(quantity);
} }
return ''; return '';
} }
/// 获取折扣文本 /// 获取折扣文本
/// discount: 0 或 100 表示原价(无折扣),其他值表示折扣百分比
String kr_getDiscountText(KRPackageListItem plan, int discountIndex) { String kr_getDiscountText(KRPackageListItem plan, int discountIndex) {
if (discountIndex >= 0 && discountIndex < plan.kr_discount.length) { if (discountIndex >= 0 && discountIndex < plan.kr_discount.length) {
final discount = plan.kr_discount[discountIndex]; final discount = plan.kr_discount[discountIndex];
// 折扣值为 100 表示原价,不需要显示折扣 // 折扣值为 0 或 100 表示原价,不需要显示折扣
if (discount.kr_discount == 100) { if (discount.kr_discount == 0 || discount.kr_discount == 100) {
return ''; return '';
} }
// 计算折扣百分比(例如:95% 显示为 -5% // 计算折扣百分比(例如:97% 显示为 -3%
final discountPercent = 100 - discount.kr_discount; final discountPercent = 100 - discount.kr_discount;
return '-${discountPercent}%'; return '-${discountPercent}%';
} }
@@ -172,6 +172,14 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final plan = controller.kr_plans[controller.kr_selectedPlanIndex.value]; final plan = controller.kr_plans[controller.kr_selectedPlanIndex.value];
final discountIndex = plan.kr_discount.isEmpty ? null : index; final discountIndex = plan.kr_discount.isEmpty ? null : index;
// 如果是 Day 单位且 quantity 是 1(一天),则不显示
if (discountIndex != null &&
plan.kr_unitTime == 'Day' &&
plan.kr_discount[discountIndex].kr_quantity == 1) {
return SizedBox.shrink();
}
return _kr_buildPlanOptionCard( return _kr_buildPlanOptionCard(
plan, plan,
controller.kr_selectedPlanIndex.value, controller.kr_selectedPlanIndex.value,
@@ -695,42 +703,37 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
], ],
), ),
SizedBox(height: 6.h), SizedBox(height: 6.h),
if (discountIndex != null && plan.kr_discount.isNotEmpty) ...[ if (discountIndex != null &&
plan.kr_discount.isNotEmpty &&
plan.kr_discount[discountIndex].kr_discount != 0 &&
plan.kr_discount[discountIndex].kr_discount != 100) ...[
Container( Container(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: 8.w, horizontal: 8.w,
vertical: 2.h vertical: 2.h
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: plan.kr_discount[discountIndex].kr_discount < 100 color: Colors.red.withOpacity(0.08),
? Colors.red.withOpacity(0.08)
: Colors.transparent,
borderRadius: BorderRadius.circular(4.r), borderRadius: BorderRadius.circular(4.r),
border: plan.kr_discount[discountIndex].kr_discount < 100 border: Border.all(
? Border.all( color: Colors.red.withOpacity(0.2),
color: Colors.red.withOpacity(0.2), width: 1,
width: 1, ),
) boxShadow: [
: null, BoxShadow(
boxShadow: plan.kr_discount[discountIndex].kr_discount < 100 color: Colors.red.withOpacity(0.05),
? [ blurRadius: 4,
BoxShadow( offset: Offset(0, 2),
color: Colors.red.withOpacity(0.05), spreadRadius: 0,
blurRadius: 4, ),
offset: Offset(0, 2), ],
spreadRadius: 0,
),
]
: null,
), ),
child: Text( child: Text(
controller.kr_getDiscountText(plan, discountIndex), controller.kr_getDiscountText(plan, discountIndex),
style: KrAppTextStyle( style: KrAppTextStyle(
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: plan.kr_discount[discountIndex].kr_discount < 100 color: Colors.red.withOpacity(0.9),
? Colors.red.withOpacity(0.9)
: Colors.transparent,
), ),
), ),
), ),
+49 -39
View File
@@ -44,14 +44,9 @@ class HttpUtil {
/// 对dio进行配置 /// 对dio进行配置
void initDio() { void initDio() {
Loggy.initLoggy(logPrinter: _KRSimpleLogPrinter()); // 不使用 Loggy,改用自定义简洁拦截器
_dio.interceptors.add(LoggyDioInterceptor(requestBody: true)); _dio.interceptors.add(_KRSimpleHttpInterceptor());
_dio.options.baseUrl = AppConfig.getInstance().baseUrl; _dio.options.baseUrl = AppConfig.getInstance().baseUrl;
// 添加日志拦截器
_dio.interceptors.add(LoggyDioInterceptor(
requestBody: true,
responseBody: true,
));
// 设置连接超时时间 // 设置连接超时时间
_dio.options.connectTimeout = const Duration(seconds: 60); _dio.options.connectTimeout = const Duration(seconds: 60);
@@ -246,55 +241,70 @@ class HttpUtil {
} }
} }
/// 拦截器 /// 拦截器(简洁格式,无边框)
class MyInterceptor extends Interceptor { class MyInterceptor extends Interceptor {
@override @override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) { void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
KRLogUtil.kr_d( print('>>> Request │ ${options.method}${options.uri}');
'>>> Request │ ${options.method}${options.uri}\n' if (options.data != null) {
'╔ Headers\n' print('Body: ${options.data}');
'${options.headers}\n' }
'╚════════════════════════════════════════════════════════════════════════════════════════╝\n'
'╔ Body\n'
'${options.data}\n'
'╚════════════════════════════════════════════════════════════════════════════════════════╝',
tag: 'DioLoggy');
handler.next(options); handler.next(options);
} }
@override @override
void onResponse(Response response, ResponseInterceptorHandler handler) { void onResponse(Response response, ResponseInterceptorHandler handler) {
KRLogUtil.kr_d( print('<<< Response │ ${response.requestOptions.method}${response.statusCode} ${response.statusMessage}${response.requestOptions.uri}');
'<<< Response │ ${response.requestOptions.method}${response.statusCode} ${response.statusMessage}${response.requestOptions.uri}\n' if (response.data != null) {
'Body\n' print('Body: ${response.data}');
'${response.data}\n' }
'╚════════════════════════════════════════════════════════════════════════════════════════╝',
tag: 'DioLoggy');
handler.next(response); handler.next(response);
} }
@override @override
void onError(DioException err, ErrorInterceptorHandler handler) { void onError(DioException err, ErrorInterceptorHandler handler) {
KRLogUtil.kr_e( print('<<< Error │ ${err.requestOptions.method}${err.requestOptions.uri}');
'<<< Error ${err.requestOptions.method}${err.requestOptions.uri}\n' print('Error Type: ${err.type}');
'╔ Error Type\n' if (err.message != null) {
' ${err.type}\n' print('Error Message: ${err.message}');
'╚════════════════════════════════════════════════════════════════════════════════════════╝\n' }
'╔ Error Message\n' if (err.response?.data != null) {
'${err.message}\n' print('Response Data: ${err.response?.data}');
'╚════════════════════════════════════════════════════════════════════════════════════════╝\n' }
'╔ Response Data\n'
'${err.response?.data}\n'
'╚════════════════════════════════════════════════════════════════════════════════════════╝',
tag: 'DioLoggy');
handler.next(err); handler.next(err);
} }
} }
/// 自定义简洁日志打印机(无边框符号) /// 自定义简洁 HTTP 拦截器(无边框符号)
class _KRSimpleLogPrinter extends LoggyPrinter { class _KRSimpleHttpInterceptor extends Interceptor {
@override @override
void onLog(LogRecord record) { void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
print(record.message); print('>>> Request │ ${options.method}${options.uri}');
if (options.data != null) {
print('Body: ${options.data}');
}
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
print('<<< Response │ ${response.requestOptions.method}${response.statusCode} ${response.statusMessage}${response.requestOptions.uri}');
if (response.data != null) {
print('Body: ${response.data}');
}
handler.next(response);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
print('<<< Error │ ${err.requestOptions.method}${err.requestOptions.uri}');
print('Error Type: ${err.type}');
if (err.message != null) {
print('Error Message: ${err.message}');
}
if (err.response?.data != null) {
print('Response Data: ${err.response?.data}');
}
handler.next(err);
} }
} }