积分兑换

This commit is contained in:
2026-02-09 15:10:00 +08:00
parent 87e890f97e
commit 45e45d8160
5 changed files with 472 additions and 6 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1,117 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:ln_jq_app/common/model/base_model.dart';
import 'package:ln_jq_app/pages/c_page/mall/exchange_success/view.dart';
import '../mall_controller.dart';
class MallDetailController extends GetxController with BaseControllerMixin {
@override
String get builderId => 'mall_detail';
late final int goodsId;
final Rx<GoodsModel?> goodsDetail = Rx<GoodsModel?>(null);
final RxBool isLoading = true.obs;
final addressController = TextEditingController();
final nameController = TextEditingController();
final phoneController = TextEditingController();
final formKey = GlobalKey<FormState>();
@override
void onInit() {
super.onInit();
goodsId = Get.arguments['goodsId'] as int;
getGoodsDetail();
}
@override
bool get listenLifecycleEvent => true;
Future<void> getGoodsDetail() async {
isLoading.value = true;
updateUi();
try {
var response = await HttpService.to.post(
'appointment/score/getScoreGoodsDetail',
data: {'goodsId': goodsId},
);
if (response != null && response.data != null) {
var result = BaseModel<GoodsModel>.fromJson(
response.data,
dataBuilder: (dataJson) => GoodsModel.fromJson(dataJson),
);
if (result.code == 0 && result.data != null) {
goodsDetail.value = result.data;
} else {
showErrorToast('加载失败: ${result.message}');
Get.back();
}
}
} catch (e) {
log('获取商品详情失败: $e');
showErrorToast('网络异常,请稍后重试');
Get.back();
} finally {
isLoading.value = false;
updateUi();
}
}
/// 兑换商品
void exchange() async {
if (!formKey.currentState!.validate()) {
return;
}
/*
final mallController = Get.find<MallController>();
if (mallController.userScore.value < (goodsDetail.value?.score ?? 0)) {
showWarningToast('积分不足');
return;
}*/
// 接口调用预留
showLoading('兑换中...');
final goods = goodsDetail.value;
if (goods == null) {
showErrorToast('兑换失败,请稍后重试');
return;
}
try {
var response = await HttpService.to.post(
'appointment/score/scoreExchange',
data: {
"goodsId": goods.id,
"address": addressController.text,
"name": nameController.text,
"phone": phoneController.text,
},
);
if (response != null && response.data != null) {
var result = BaseModel.fromJson(response.data);
if (result.code == 0) {
Get.off(() => MallExchangeSuccessPage());
}
}
} catch (e) {
log('兑换失败: $e');
showErrorToast('兑换失败,请稍后重试');
} finally {
dismissLoading();
}
}
@override
void onClose() {
addressController.dispose();
nameController.dispose();
phoneController.dispose();
super.onClose();
}
}

View File

@@ -0,0 +1,283 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'controller.dart';
class MallDetailPage extends GetView<MallDetailController> {
const MallDetailPage({super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<MallDetailController>(
init: MallDetailController(),
id: 'mall_detail',
builder: (_) {
return Scaffold(
backgroundColor: const Color(0xFFF7F8FA),
appBar: AppBar(
title: const Text('商品兑换'),
backgroundColor: Colors.white,
foregroundColor: Colors.black,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, size: 20),
onPressed: () => Get.back(),
),
),
body: controller.isLoading.value
? const Center(child: CircularProgressIndicator())
: GestureDetector(
onTap: () {
hideKeyboard();
},
child: _buildBody(),
),
bottomNavigationBar: _buildBottomButton(),
);
},
);
}
Widget _buildBody() {
final goods = controller.goodsDetail.value;
if (goods == null) return const Center(child: Text('商品信息不存在'));
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Form(
key: controller.formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildGoodsInfoCard(goods),
const SizedBox(height: 24),
_buildSectionTitle('填写收货信息'),
const SizedBox(height: 16),
_buildInputLabel('详细地址'),
_buildTextField(
controller: controller.addressController,
hint: '请输入完整的收货地址',
icon: Icons.location_on_outlined,
),
const SizedBox(height: 16),
_buildInputLabel('收货人姓名'),
_buildTextField(
controller: controller.nameController,
hint: '请输入收货人姓名',
icon: Icons.person_outline,
),
const SizedBox(height: 16),
_buildInputLabel('联系电话'),
_buildTextField(
controller: controller.phoneController,
hint: '请输入手机号码',
icon: Icons.phone_android_outlined,
keyboardType: TextInputType.phone,
),
const SizedBox(height: 40),
Center(
child: Text(
'兑换成功后商品会在3个工作日内邮寄\n请注意查收',
textAlign: TextAlign.center,
style: TextStyle(
color: Color(0xFF999999),
fontSize: 12.sp,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
);
}
Widget _buildGoodsInfoCard(goods) {
return Container(
padding: const EdgeInsets.all(17),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: goods.goodsImage != null
? Image.network(
goods.goodsImage!,
width: 94.w,
height: 94.h,
fit: BoxFit.cover,
)
: Container(
width: 80,
height: 80,
color: Colors.grey[200],
child: const Icon(Icons.image, color: Colors.grey),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
goods.goodsName,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: Color(0xFF333333),
),
),
SizedBox(height: 8.h),
Row(
children: [
Text(
'${goods.score}',
style: TextStyle(
fontSize: 20.sp,
color: Color(0xFF4CAF50),
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 4),
Text(
'积分',
style: TextStyle(
fontSize: 10.sp,
fontWeight: FontWeight.w600,
color: Color(0xFF999999),
),
),
],
),
SizedBox(height: 10.h),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: const Color(0xFFF2F3F5),
borderRadius: BorderRadius.circular(4),
),
child: Text(
'数量: 1',
style: TextStyle(
fontSize: 10.sp,
fontWeight: FontWeight.w500,
color: Color(0xFF666666),
),
),
),
],
),
),
],
),
);
}
Widget _buildSectionTitle(String title) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 6.w,
height: 16.h,
decoration: BoxDecoration(
color: const Color(0xFF4CAF50),
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
Text(
title,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: Color.fromRGBO(148, 163, 184, 1),
),
),
],
);
}
Widget _buildInputLabel(String label) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
label,
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w600,
color: Color.fromRGBO(100, 116, 139, 1),
),
),
);
}
Widget _buildTextField({
required TextEditingController controller,
required String hint,
required IconData icon,
TextInputType? keyboardType,
}) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: TextFormField(
controller: controller,
keyboardType: keyboardType,
textAlign: TextAlign.start,
decoration: InputDecoration(
hintText: hint,
hintStyle: TextStyle(
color: Color.fromRGBO(134, 144, 156, 1),
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
prefixIcon: Icon(icon, color: const Color(0xFF999999), size: 20),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '内容不能为空';
}
return null;
},
),
);
}
Widget _buildBottomButton() {
return Container(
padding: const EdgeInsets.fromLTRB(16, 10, 16, 30),
child: ElevatedButton(
onPressed: controller.exchange,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF007A45),
minimumSize: const Size(double.infinity, 50),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)),
elevation: 0,
),
child: const Text(
'兑换商品',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
);
}
}

View File

@@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:getx_scaffold/common/index.dart';
import 'package:ln_jq_app/common/login_util.dart';
class MallExchangeSuccessPage extends StatelessWidget {
const MallExchangeSuccessPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, size: 20, color: Colors.black),
onPressed: () => Get.back(), // 返回首页
),
title: const Text('商品兑换', style: TextStyle(color: Colors.black, fontSize: 18)),
),
body: Center(
child: Column(
children: [
SizedBox(height: 114.h),
_buildSuccessIcon(),
const SizedBox(height: 24),
Text(
'兑换成功',
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.w600,
color: Color(0xFF333333),
),
),
const SizedBox(height: 8),
Text(
'预计 3 日内发货\n请留意查收',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 14.sp, color: Color(0xFF999999)),
),
const SizedBox(height: 60),
ElevatedButton(
onPressed: () => Get.back(),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF007A45),
minimumSize: const Size(140, 50),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)),
),
child: Text(
'返回首页',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w400,
fontSize: 16.sp,
),
),
),
],
),
),
);
}
Widget _buildSuccessIcon() {
return Container(child: LoginUtil.getAssImg("mall_pay_success@2x"));
}
}

View File

@@ -1,5 +1,6 @@
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:ln_jq_app/common/model/base_model.dart';
import 'package:ln_jq_app/pages/c_page/mall/detail/view.dart';
class GoodsModel {
final int id;
@@ -140,11 +141,8 @@ class MallController extends GetxController with BaseControllerMixin {
/// 兑换商品 (预留)
void exchangeGoods(GoodsModel goods) {
if (userScore.value < goods.score) {
showWarningToast('积分不足');
return;
}
//todo 跳转
Get.to(() => MallDetailPage(), arguments: {'goodsId': goods.id})?.then((val) {
refreshData();
});
}
}