diff --git a/ln_jq_app/assets/images/rule_bg@2x.png b/ln_jq_app/assets/images/rule_bg@2x.png new file mode 100644 index 0000000..016c043 Binary files /dev/null and b/ln_jq_app/assets/images/rule_bg@2x.png differ diff --git a/ln_jq_app/assets/images/rule_bg_1@2x.png b/ln_jq_app/assets/images/rule_bg_1@2x.png new file mode 100644 index 0000000..30fed05 Binary files /dev/null and b/ln_jq_app/assets/images/rule_bg_1@2x.png differ diff --git a/ln_jq_app/assets/images/tips_1@2x.png b/ln_jq_app/assets/images/tips_1@2x.png new file mode 100644 index 0000000..c7cbb9b Binary files /dev/null and b/ln_jq_app/assets/images/tips_1@2x.png differ diff --git a/ln_jq_app/assets/images/tips_2@2x.png b/ln_jq_app/assets/images/tips_2@2x.png new file mode 100644 index 0000000..0f0b9a9 Binary files /dev/null and b/ln_jq_app/assets/images/tips_2@2x.png differ diff --git a/ln_jq_app/assets/images/tips_3@2x.png b/ln_jq_app/assets/images/tips_3@2x.png new file mode 100644 index 0000000..36157fb Binary files /dev/null and b/ln_jq_app/assets/images/tips_3@2x.png differ diff --git a/ln_jq_app/assets/images/tips_4@2x.png b/ln_jq_app/assets/images/tips_4@2x.png new file mode 100644 index 0000000..82df4b1 Binary files /dev/null and b/ln_jq_app/assets/images/tips_4@2x.png differ diff --git a/ln_jq_app/assets/images/tips_5@2x.png b/ln_jq_app/assets/images/tips_5@2x.png new file mode 100644 index 0000000..b05d6bd Binary files /dev/null and b/ln_jq_app/assets/images/tips_5@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 index 7dc9741..74eb736 100644 --- a/ln_jq_app/lib/pages/c_page/mall/detail/controller.dart +++ b/ln_jq_app/lib/pages/c_page/mall/detail/controller.dart @@ -14,7 +14,6 @@ class MallDetailController extends GetxController with BaseControllerMixin { late final int goodsId; final Rx goodsDetail = Rx(null); - final RxBool isLoading = true.obs; final addressController = TextEditingController(); final nameController = TextEditingController(); @@ -33,8 +32,6 @@ class MallDetailController extends GetxController with BaseControllerMixin { bool get listenLifecycleEvent => true; Future getGoodsDetail() async { - isLoading.value = true; - updateUi(); try { var response = await HttpService.to.post( 'appointment/score/getScoreGoodsDetail', @@ -57,7 +54,6 @@ class MallDetailController extends GetxController with BaseControllerMixin { showErrorToast('网络异常,请稍后重试'); Get.back(); } finally { - isLoading.value = false; updateUi(); } } 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 index 70eb3fc..d67db55 100644 --- a/ln_jq_app/lib/pages/c_page/mall/detail/view.dart +++ b/ln_jq_app/lib/pages/c_page/mall/detail/view.dart @@ -24,14 +24,12 @@ class MallDetailPage extends GetView { onPressed: () => Get.back(), ), ), - body: controller.isLoading.value - ? const Center(child: CircularProgressIndicator()) - : GestureDetector( - onTap: () { - hideKeyboard(); - }, - child: _buildBody(), - ), + body: GestureDetector( + onTap: () { + hideKeyboard(); + }, + child: _buildBody(), + ), bottomNavigationBar: _buildBottomButton(), ); }, 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 9a16b16..32384e8 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,6 +1,9 @@ + 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'; +import 'package:ln_jq_app/pages/c_page/mall/orders/view.dart'; +import 'package:ln_jq_app/pages/c_page/mall/rule/view.dart'; class GoodsModel { final int id; @@ -139,10 +142,19 @@ class MallController extends GetxController with BaseControllerMixin { } } - /// 兑换商品 (预留) + /// 兑换商品 void exchangeGoods(GoodsModel goods) { - Get.to(() => MallDetailPage(), arguments: {'goodsId': goods.id})?.then((val) { - refreshData(); - }); + Get.to(() => const MallDetailPage(), arguments: {'goodsId': goods.id}) + ?.then((_) => refreshData()); + } + + ///规则说明 + void toRuleDes() { + Get.to(() => const MallRulePage()); + } + + ///历史订单 + void toOrders() { + Get.to(() => const MallOrdersPage()); } } diff --git a/ln_jq_app/lib/pages/c_page/mall/mall_view.dart b/ln_jq_app/lib/pages/c_page/mall/mall_view.dart index 992d0fc..be9b622 100644 --- a/ln_jq_app/lib/pages/c_page/mall/mall_view.dart +++ b/ln_jq_app/lib/pages/c_page/mall/mall_view.dart @@ -117,7 +117,13 @@ class MallPage extends GetView { style: TextStyle(color: Colors.white70, fontSize: 14.sp), ), const SizedBox(width: 4), - const Icon(Icons.help_outline, color: Colors.white70, size: 14), + GestureDetector( + onTap: (){ + controller.toRuleDes(); + }, + child: const Icon(Icons.help_outline, color: Colors.white70, size: 14), + ) + ], ), Text( @@ -160,7 +166,9 @@ class MallPage extends GetView { ), ), TextButton( - onPressed: () {}, + onPressed: () { + controller.toOrders(); + }, child: Text( '历史订单', style: TextStyle( diff --git a/ln_jq_app/lib/pages/c_page/mall/orders/controller.dart b/ln_jq_app/lib/pages/c_page/mall/orders/controller.dart new file mode 100644 index 0000000..9d49911 --- /dev/null +++ b/ln_jq_app/lib/pages/c_page/mall/orders/controller.dart @@ -0,0 +1,94 @@ + +import 'dart:developer'; +import 'package:get/get.dart'; +import 'package:getx_scaffold/getx_scaffold.dart'; +import 'package:ln_jq_app/common/model/base_model.dart'; + +class OrderModel { + final int id; + final int scoreGoodsId; + final String goodsName; + final String? goodsImage; + final String? goodsContent; + final String address; + final String createTime; + final String score; + + OrderModel({ + required this.id, + required this.scoreGoodsId, + required this.goodsName, + this.goodsImage, + this.goodsContent, + required this.address, + required this.createTime, + required this.score, + }); + + factory OrderModel.fromJson(Map json) { + return OrderModel( + id: json['id'] as int, + scoreGoodsId: json['scoreGoodsId'] as int, + goodsName: json['goodsName']?.toString() ?? '', + goodsImage: json['goodsImage'], + goodsContent: json['goodsContent'], + address: json['address']?.toString() ?? '', + createTime: json['createTime']?.toString() ?? '', + score: json['score']?.toString() ?? '', + ); + } +} + +class MallOrdersController extends GetxController with BaseControllerMixin { + @override + String get builderId => 'mall_orders'; + + final RxList orderList = [].obs; + final RxBool isLoading = true.obs; + int pageNum = 1; + final int pageSize = 50; + + @override + void onInit() { + super.onInit(); + getOrders(); + } + + Future getOrders({bool isRefresh = true}) async { + if (isRefresh) { + pageNum = 1; + isLoading.value = true; + updateUi(); + } + + try { + var response = await HttpService.to.post( + 'appointment/score/getScoreExchangeList', + data: { + "status": "", + "pageNum": pageNum.toString(), + "pageSize": pageSize.toString() + }, + ); + + if (response != null && response.data != null) { + var result = BaseModel>.fromJson(response.data); + if (result.code == 0 && result.data != null) { + var records = result.data!['records'] as List; + var list = records.map((e) => OrderModel.fromJson(e)).toList(); + if (isRefresh) { + orderList.assignAll(list); + } else { + orderList.addAll(list); + } + pageNum++; + } + } + } catch (e) { + log('获取订单列表失败: $e'); + } finally { + isLoading.value = false; + updateUi(); + } + } +} diff --git a/ln_jq_app/lib/pages/c_page/mall/orders/view.dart b/ln_jq_app/lib/pages/c_page/mall/orders/view.dart new file mode 100644 index 0000000..8de3674 --- /dev/null +++ b/ln_jq_app/lib/pages/c_page/mall/orders/view.dart @@ -0,0 +1,138 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:getx_scaffold/getx_scaffold.dart'; +import 'controller.dart'; + +class MallOrdersPage extends GetView { + const MallOrdersPage({super.key}); + + @override + Widget build(BuildContext context) { + return GetBuilder( + init: MallOrdersController(), + id: 'mall_orders', + 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: RefreshIndicator( + onRefresh: () => controller.getOrders(isRefresh: true), + child: controller.isLoading.value && controller.orderList.isEmpty + ? const Center(child: CircularProgressIndicator()) + : _buildOrderList(), + ), + ); + }, + ); + } + + Widget _buildOrderList() { + if (controller.orderList.isEmpty) { + return const Center( + child: Text('暂无订单记录', style: TextStyle(color: Color(0xFF999999))), + ); + } + + return ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: controller.orderList.length, + itemBuilder: (context, index) { + final order = controller.orderList[index]; + return _buildOrderItem(order); + }, + ); + } + + Widget _buildOrderItem(OrderModel order) { + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '兑换时间:${order.createTime}', + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w500, + color: Color.fromRGBO(107, 114, 128, 1), + ), + ), + const SizedBox(height: 12), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: order.goodsImage != null + ? Image.network( + order.goodsImage!, + width: 80.w, + height: 80.h, + fit: BoxFit.cover, + ) + : Container( + width: 80.w, + height: 80.h, + color: Colors.grey[200], + child: const Icon(Icons.image, color: Colors.grey), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + order.goodsName, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: Color(0xFF333333), + ), + ), + const SizedBox(height: 8), + Row( + children: [ + Text( + order.score, + style: TextStyle( + fontSize: 16.sp, + color: Color(0xFF4CAF50), + fontWeight: FontWeight.bold, + ), + ), + SizedBox(width: 4), + Text( + '积分', + style: TextStyle(fontSize: 11.sp, color: Color(0xFF4CAF50)), + ), + ], + ), + ], + ), + ), + Text( + 'x1', + style: TextStyle(color: Color(0xFFCCCCCC), fontSize: 16.sp), + ), + ], + ), + ], + ), + ); + } +} diff --git a/ln_jq_app/lib/pages/c_page/mall/rule/view.dart b/ln_jq_app/lib/pages/c_page/mall/rule/view.dart new file mode 100644 index 0000000..c898c9a --- /dev/null +++ b/ln_jq_app/lib/pages/c_page/mall/rule/view.dart @@ -0,0 +1,131 @@ +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 MallRulePage extends StatelessWidget { + const MallRulePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Color.fromRGBO(64, 199, 154, 1), + appBar: AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios, color: Colors.white, size: 20), + onPressed: () => Get.back(), + ), + ), + body: Stack( + children: [ + // 顶部装饰图 + Positioned( + top: 30, + right: Get.width * 0.15, + child: LoginUtil.getAssImg("rule_bg@2x"), + ), + Container( + margin: const EdgeInsets.fromLTRB(20, 100, 20, 20), + padding: const EdgeInsets.all(24), + width: double.infinity, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/rule_bg_1@2x.png'), + fit: BoxFit.fill, + ), + ), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '积分获取规则', + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Color(0xFF2C3E50), + ), + ), + const SizedBox(height: 30), + _buildRuleItem( + icon: 'tips_1@2x', + title: '每日首次签到积分规则', + content: '每日首签,立得 1 积分', + ), + _buildRuleItem( + icon: 'tips_2@2x', + title: '每日预约加氢积分规则', + content: '每日前 2 次预约加氢,各得 1 积分', + ), + _buildRuleItem( + icon: 'tips_3@2x', + title: '连续签到累计赠分规则', + content: '连续签到 3 天赠 2 积分,7 天赠 5 积分', + ), + _buildRuleItem( + icon: 'tips_4@2x', + title: '连续签到周期及断签重置规则', + content: '7 天为一个签到周期,中途断签则重新从第 1 天计算', + ), + _buildRuleItem( + icon: 'tips_5@2x', + title: '积分使用规则', + content: + '个人账户内累计的所有有效积分,可在平台积分商城中,用于兑换商城内上架的各类商品、权益或服务,兑换时将按照商品标注的积分值扣除对应积分,积分兑换后不支持撤销、退换,商品兑换规则以积分商城内公示为准。', + ), + const SizedBox(height: 40), + const Center( + child: Text( + '本活动最终解释权归官方所有,如有疑问可咨询客服。', + style: TextStyle(color: Color(0xFF999999), fontSize: 12), + ), + ), + ], + ), + ), + ), + ], + ), + ); + } + + Widget _buildRuleItem({ + required String icon, + required String title, + required String content, + }) { + return Padding( + padding: const EdgeInsets.only(bottom: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + LoginUtil.getAssImg(icon), + const SizedBox(width: 8), + Text( + title, + style: TextStyle( + fontSize: 15.sp, + fontWeight: FontWeight.bold, + color: Color(0xFF333333), + ), + ), + ], + ), + const SizedBox(height: 6), + Padding( + padding: const EdgeInsets.only(left: 0), + child: Text( + content, + style: TextStyle(fontSize: 13.sp, color: Color(0xFF666666), height: 1.5), + ), + ), + ], + ), + ); + } +}