flutter/lib/pages/index/index.dart
2025-08-21 10:50:38 +08:00

300 lines
10 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/// 首页模板
library;
import 'package:card_swiper/card_swiper.dart';
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:get/get.dart';
import 'package:loopin/components/backtop.dart';
import 'package:loopin/components/loading.dart';
import 'package:loopin/controller/shop_index_controller.dart';
import 'package:loopin/utils/index.dart';
class IndexPage extends StatefulWidget {
const IndexPage({super.key});
@override
State<IndexPage> createState() => _IndexPageState();
}
class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMixin {
// 分类列表
// List cateList = [
// {
// 'id': 1,
// 'list': [
// {
// 'icon': 'order.svg',
// 'label': '我的订单',
// },
// {
// 'icon': 'chongzhi.svg',
// 'label': '充值中心',
// },
// {'icon': 'qianbao.svg', 'label': '余额'},
// {'icon': 'comment.svg', 'label': '评价中心'}
// ]
// }
// ];
final ScrollController pageScrollController = ScrollController();
final ShopIndexController controller = Get.put(ShopIndexController());
// 瀑布流卡片
Widget cardList(item) {
return GestureDetector(
child: Container(
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(15.0), boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(5),
offset: Offset(0.0, 1.0),
blurRadius: 1.0,
spreadRadius: 0.0,
),
]),
child: Column(
children: [
Image.network('${item['pic']}'),
Container(
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 5.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 5.0,
children: [
Text(
'${item['name']}',
style: TextStyle(fontSize: 14.0, height: 1.2),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
Row(
spacing: 5.0,
children: [
Text.rich(
TextSpan(style: TextStyle(color: Colors.red, fontSize: 12.0, fontWeight: FontWeight.w700, fontFamily: 'Arial'), children: [
TextSpan(text: '¥'),
TextSpan(
text: '${item['price']}',
style: TextStyle(
fontSize: 16.0,
)),
]),
),
Text(
'已售${Utils().graceNumber(int.parse(item['sales'] ?? '0'))}',
style: TextStyle(color: Colors.grey, fontSize: 10.0),
),
],
),
Text(
'${item['storeName']}',
style: TextStyle(color: Colors.grey, fontSize: 12.0),
),
],
),
)
],
),
),
onTap: () {
Get.toNamed('/goods', arguments: item['id']);
},
);
}
@override
void initState() {
super.initState();
controller.initTabs();
}
@override
Widget build(BuildContext context) {
return Obx(() {
final tabIndex = controller.currentTabIndex.value;
final currentTab = controller.tabs[tabIndex];
return Scaffold(
backgroundColor: Colors.grey[50],
body: Column(
children: [
// 顶部固定区域(轮播图 + TabBar
_buildTopSection(),
// 内容区域
Expanded(
child: TabBarView(
controller: controller.tabController,
children: controller.tabList.asMap().entries.map((entry) {
final index = entry.key;
return _buildTabContent(index);
}).toList(),
),
),
],
),
floatingActionButton: currentTab != null
? Backtop(
controller: currentTab.scrollController,
offset: currentTab.scrollOffset.value,
)
: null,
);
});
}
// 构建顶部固定区域
Widget _buildTopSection() {
double screenWidth = MediaQuery.of(context).size.width;
int tabCount = controller.tabList.length;
// 每个 Tab 的最小宽度
double minTabWidth = 80;
// 是否可滚动
bool isScrollable = tabCount * minTabWidth > screenWidth;
return Column(
children: [
// 轮播图
Container(
width: double.infinity,
height: 240,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFFFF5000), Color(0xFFfcaec4)],
),
),
child: controller.swiperData.length <= 1
? (controller.swiperData.isNotEmpty
? Image.network(
controller.swiperData.first['images'] ?? '',
fit: BoxFit.fill,
)
: const SizedBox.shrink())
: Swiper(
itemCount: controller.swiperData.length,
autoplay: true,
loop: true,
pagination: SwiperPagination(
builder: DotSwiperPaginationBuilder(
color: Colors.white70,
activeColor: Colors.white,
),
),
itemBuilder: (context, index) {
final imageUrl = controller.swiperData[index]['images'] ?? '';
return imageUrl.isNotEmpty ? Image.network(imageUrl, fit: BoxFit.fill) : const SizedBox.shrink();
},
),
),
// TabBar
Container(
color: Colors.white,
child: TabBar(
controller: controller.tabController,
tabs: controller.tabList.map((item) {
return Tab(
child: Text(item['name'], style: const TextStyle(fontWeight: FontWeight.bold)),
);
}).toList(),
isScrollable: isScrollable,
overlayColor: WidgetStateProperty.all(Colors.transparent),
unselectedLabelColor: Colors.black87,
labelColor: Color.fromARGB(255, 236, 108, 49),
indicator: const UnderlineTabIndicator(borderSide: BorderSide(color: Color.fromARGB(255, 236, 108, 49), width: 2.0)),
unselectedLabelStyle: const TextStyle(fontSize: 16.0, fontFamily: 'Microsoft YaHei'),
labelStyle: const TextStyle(fontSize: 18.0, fontFamily: 'Microsoft YaHei', fontWeight: FontWeight.bold),
dividerHeight: 0,
),
),
],
);
}
// 构建标签页内容
Widget _buildTabContent(int index) {
final tabState = controller.tabs[index]!;
return Obx(() {
if (tabState.dataList.isEmpty && tabState.isLoading.value) {
return Center(
child: RefreshProgressIndicator(
backgroundColor: Colors.white,
color: Color(0xFFFF5000),
),
);
}
// 添加 下拉刷新
return EasyRefresh(
onRefresh: () async {
await controller.refreshData(index);
},
header: ClassicHeader(
dragText: '下拉刷新',
armedText: '释放刷新',
readyText: '刷新中...',
processingText: '刷新完成',
messageText: '最后更新于 %T',
),
child: CustomScrollView(
controller: tabState.scrollController,
key: PageStorageKey('tab_$index'),
physics: const AlwaysScrollableScrollPhysics(), // 确保可下拉
slivers: [
SliverPadding(
padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
sliver: tabState.dataList.isEmpty
? SliverToBoxAdapter(
child: SizedBox(
height: MediaQuery.of(context).size.height - 500, // 给个足够高度让下拉触发
child: Center(child: _emptyTip('暂无数据')),
),
)
: SliverMasonryGrid.count(
crossAxisCount: 2,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childCount: tabState.dataList.length,
itemBuilder: (context, idx) => cardList(tabState.dataList[idx]),
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Center(
child: tabState.isLoading.value
? const Loading(title: 'loading...')
: (tabState.dataList.isNotEmpty ? const Text('没有更多数据了') : const SizedBox.shrink()),
),
),
),
],
),
);
});
}
// 空状态提示
Widget _emptyTip(String text) {
return Center(
child: Padding(
padding: const EdgeInsets.only(top: 50),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset('assets/images/empty.png', width: 100),
const SizedBox(height: 8),
Text(
text,
style: const TextStyle(color: Colors.grey, fontSize: 13),
),
],
),
),
);
}
}