diff --git a/ln_jq_app/assets/images/mall_pay_success@2x.png b/ln_jq_app/assets/images/mall_pay_success@2x.png new file mode 100644 index 0000000..5398a16 Binary files /dev/null and b/ln_jq_app/assets/images/mall_pay_success@2x.png differ diff --git a/ln_jq_app/lib/pages/c_page/mall/detail/controller.dart b/ln_jq_app/lib/pages/c_page/mall/detail/controller.dart new file mode 100644 index 0000000..7dc9741 --- /dev/null +++ b/ln_jq_app/lib/pages/c_page/mall/detail/controller.dart @@ -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 goodsDetail = Rx(null); + final RxBool isLoading = true.obs; + + final addressController = TextEditingController(); + final nameController = TextEditingController(); + final phoneController = TextEditingController(); + + final formKey = GlobalKey(); + + @override + void onInit() { + super.onInit(); + goodsId = Get.arguments['goodsId'] as int; + getGoodsDetail(); + } + + @override + bool get listenLifecycleEvent => true; + + Future 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.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(); + 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(); + } +} diff --git a/ln_jq_app/lib/pages/c_page/mall/detail/view.dart b/ln_jq_app/lib/pages/c_page/mall/detail/view.dart new file mode 100644 index 0000000..70eb3fc --- /dev/null +++ b/ln_jq_app/lib/pages/c_page/mall/detail/view.dart @@ -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 { + const MallDetailPage({super.key}); + + @override + Widget build(BuildContext context) { + return GetBuilder( + 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, + ), + ), + ), + ); + } +} diff --git a/ln_jq_app/lib/pages/c_page/mall/exchange_success/view.dart b/ln_jq_app/lib/pages/c_page/mall/exchange_success/view.dart new file mode 100644 index 0000000..bcf0464 --- /dev/null +++ b/ln_jq_app/lib/pages/c_page/mall/exchange_success/view.dart @@ -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")); + } +} diff --git a/ln_jq_app/lib/pages/c_page/mall/mall_controller.dart b/ln_jq_app/lib/pages/c_page/mall/mall_controller.dart index f431385..9a16b16 100644 --- a/ln_jq_app/lib/pages/c_page/mall/mall_controller.dart +++ b/ln_jq_app/lib/pages/c_page/mall/mall_controller.dart @@ -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(); + }); } }