盐城新淘科技有限公司 1 тиждень тому
батько
коміт
04c4dfec9b

BIN
xinhuaribao/ios/Runner/bg.png


+ 3 - 0
xinhuaribao/lib/constant/api_const.dart

@@ -189,6 +189,9 @@ final String apiSpecialDetail = '/api/cms/column/detail';
 //系统信息
 final String apiSystemInfo = '/api/system/info';
 
+//开屏页配置
+final String apiSplashConfig = '/api/system/splash';
+
 final String apiUploadMulti = '/api/system/uploadMulti';
 
 

+ 205 - 10
xinhuaribao/lib/ui/login/splash_page.dart

@@ -6,10 +6,15 @@ import 'package:flutter/services.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:go_router/go_router.dart';
 import 'package:news_app/widget/my_txt.dart';
+import 'package:flutter_swiper_view/flutter_swiper_view.dart';
 
+import '../../constant/api_const.dart';
 import '../../constant/color_res.dart';
 import '../../gen/assets.gen.dart';
+import '../../http/http_util.dart';
+import '../../util/log.util.dart';
 import '../../util/shared_prefs_instance_util.dart';
+import '../../widget/load_image.dart';
 
 /// @author: bo.zeng
 /// @email: cnhbwds@gmail.com
@@ -22,15 +27,99 @@ class SplashPage extends StatefulWidget {
   State<SplashPage> createState() => _SplashPageState();
 }
 
-class _SplashPageState extends State<SplashPage> {
+class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateMixin {
   TapGestureRecognizer? aa;
+  List<String> _splashImages = [];
+  int _currentIndex = 0;
+  late Timer _sliderTimer;
+  Timer? _navigateTimer;
+  bool _hasStartedNavigation = false;
+
+  // 本地默认开屏图片(当网络不可用时使用)
+  static const List<String> _defaultSplashImages = [
+    'assets/images/splash_bg.png', // 本地默认图片
+  ];
 
   @override
   void initState() {
     super.initState();
+    // 初始化使用本地默认图片
+    _splashImages = _defaultSplashImages;
+    // 启动轮播
+    _startSlider();
+    // 从后台获取开屏页配置
+    _fetchSplashConfig();
+  }
+
+  /// 开始轮播
+  void _startSlider() {
+    _sliderTimer = Timer.periodic(const Duration(seconds: 3), (timer) {
+      if (mounted && _splashImages.isNotEmpty) {
+        setState(() {
+          _currentIndex = (_currentIndex + 1) % _splashImages.length;
+        });
+      }
+    });
+  }
+
+  /// 从后台获取开屏页配置
+  Future<void> _fetchSplashConfig() async {
+    try {
+      final response = await HttpUtil().get(apiSplashConfig);
+      if (response != null && response['code'] == 200) {
+        final data = response['data'];
+        if (data != null) {
+          // 支持多种数据格式
+          List<String> fetchedImages = [];
+
+          // 格式1: imageUrl 为单个图片
+          if (data['imageUrl'] != null && data['imageUrl'].toString().isNotEmpty) {
+            fetchedImages.add(data['imageUrl']);
+          }
+
+          // 格式2: images 为图片数组
+          if (data is List) {
+            for (var img in data) {
+              if (img.toString().isNotEmpty) {
+                fetchedImages.add(img['url']);
+              }
+            }
+          }
 
-    // 延迟 3 秒执行跳转逻辑
-    Future.delayed(const Duration(seconds: 3), () async {
+          // 格式3: imageList 为图片数组
+          if (data['imageList'] is List) {
+            final images = data['imageList'] as List;
+            for (var img in images) {
+              if (img.toString().isNotEmpty) {
+                fetchedImages.add(img.toString());
+              }
+            }
+          }
+
+          // 如果获取到了图片,更新显示
+          if (fetchedImages.isNotEmpty && mounted) {
+            setState(() {
+              _splashImages = fetchedImages;
+              _currentIndex = 0;
+            });
+          }
+        }
+      }
+    } catch (e) {
+      // 请求失败或无网络权限,继续使用本地默认图片,不做任何处理
+      consoleLog('Splash fetch error (will use default): $e');
+    }
+
+    // 延迟 3-5 秒后执行跳转逻辑
+    _startNavigationTimer();
+  }
+
+  /// 启动导航计时器
+  void _startNavigationTimer() {
+    if (_hasStartedNavigation) return;
+    _hasStartedNavigation = true;
+
+    _navigateTimer = Timer(const Duration(seconds: 4), () async {
       bool? first = await getIsFirst();
       // 确保页面未被销毁
       if (!mounted) return;
@@ -44,6 +133,8 @@ class _SplashPageState extends State<SplashPage> {
 
   @override
   void dispose() {
+    _sliderTimer.cancel();
+    _navigateTimer?.cancel();
     aa?.dispose();
     super.dispose();
   }
@@ -153,20 +244,124 @@ class _SplashPageState extends State<SplashPage> {
   Widget build(BuildContext context) {
     double w = MediaQuery.of(context).size.width;
     double h = MediaQuery.of(context).size.height;
+
     return Scaffold(
-      body: Center(
+      body: SizedBox(
+        width: w,
+        height: h,
         child: Stack(
-          alignment: Alignment.center,
           children: [
-            Image.asset(
-              Assets.images.splashBg.path,
-              fit: BoxFit.cover,
-              width: w,
-              height: h,
+            // 轮播图片
+            if (_splashImages.length == 1)
+              // 只有一张图片时直接显示
+              _buildSingleImage()
+            else
+              // 多张图片时使用轮播
+              _buildCarousel(w, h),
+
+            // 指示器(多张图片时显示)
+            if (_splashImages.length > 1)
+              Positioned(
+                bottom: 50.h,
+                left: 0,
+                right: 0,
+                child: Row(
+                  mainAxisAlignment: MainAxisAlignment.center,
+                  children: List.generate(
+                    _splashImages.length,
+                    (index) => AnimatedContainer(
+                      duration: const Duration(milliseconds: 300),
+                      margin: EdgeInsets.symmetric(horizontal: 4.w),
+                      width: _currentIndex == index ? 20.w : 8.w,
+                      height: 8.h,
+                      decoration: BoxDecoration(
+                        color: _currentIndex == index
+                            ? Colors.white
+                            : Colors.white.withOpacity(0.5),
+                        borderRadius: BorderRadius.circular(4.r),
+                      ),
+                    ),
+                  ),
+                ),
+              ),
+
+            // 跳过按钮
+            Positioned(
+              top: 54.h,
+              right: 20.w,
+              child: GestureDetector(
+                onTap: () {
+                  _navigateTimer?.cancel();
+                  _navigateToMain();
+                },
+                child: Container(
+                  padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
+                  decoration: BoxDecoration(
+                    color: Colors.black.withOpacity(0.3),
+                    borderRadius: BorderRadius.circular(20.r),
+                  ),
+                  child: myTxt(
+                    text: "跳过",
+                    color: Colors.white,
+                    fontSize: 14.sp,
+                  ),
+                ),
+              ),
             ),
           ],
         ),
       ),
     );
   }
+
+  /// 构建单张图片
+  Widget _buildSingleImage() {
+    final imageUrl = _splashImages.first;
+    if (imageUrl.startsWith('http') || imageUrl.startsWith('https')) {
+      return LoadImage(imageUrl, fit: BoxFit.cover);
+    } else {
+      return Image.asset(imageUrl.startsWith('assets') ? imageUrl : 'assets/$imageUrl', fit: BoxFit.cover);
+    }
+  }
+
+  /// 构建轮播
+  Widget _buildCarousel(double w, double h) {
+    return Swiper(
+      itemCount: _splashImages.length,
+      index: _currentIndex,
+      onIndexChanged: (index) {
+        if (mounted) {
+          setState(() {
+            _currentIndex = index;
+          });
+        }
+      },
+      itemBuilder: (BuildContext context, int index) {
+        final imageUrl = _splashImages[index];
+        if (imageUrl.startsWith('http') || imageUrl.startsWith('https')) {
+          return LoadImage(imageUrl, fit: BoxFit.cover);
+        } else {
+          return Image.asset(
+            imageUrl.startsWith('assets') ? imageUrl : 'assets/$imageUrl',
+            fit: BoxFit.cover,
+          );
+        }
+      },
+      autoplay: false, // 使用自定义定时器控制
+      loop: true,
+      viewportFraction: 1.0,
+      scale: 1.0,
+    );
+  }
+
+  /// 导航到主页
+  void _navigateToMain() async {
+    bool? first = await getIsFirst();
+    if (!mounted) return;
+    if (first == true) {
+      context.go('/main');
+    } else {
+      _showFirstDialog(context);
+    }
+  }
 }