300 lines
10 KiB
Dart
300 lines
10 KiB
Dart
/// 首页模板
|
||
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),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|