Flutter项目之页面实现以及路由fluro

目录:

1、项目代码结构

2、页面编写以及路由配置

main.dart(入口文件)

dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter_haoke/application.dart';

void main() {
  //启动后去加载application.dart这个文件
  runApp(const Application());
}

page_content.dart

dart 复制代码
import 'package:flutter/material.dart';

import '../routes.dart';

//‌StatelessWidget‌是Flutter框架中的一个基本组件,用于构建不可变的用户界面元素。StatelessWidget的特点是它的状态不会因为用户交互或数据变化而改变,其UI完全由build()方法描述。
//定义和用途:StatelessWidget是Flutter中不需要状态改变的Widget组件,其内部没有需要管理的状态。它主要用于描述那些不会因为外部变化而改变的界面元素,如文本、按钮、图标等静态UI元素‌

class PageContent extends StatelessWidget {
  final String name;

  const PageContent({Key? key, required this.name}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("页面是:$name"),
      ),
      body: ListView(
        children: [
        //FloatingActionButton是一个圆形的浮动操作按钮,通常用于在移动应用中提供一个快速的、突出的操作入口。它不仅外观醒目,而且可以提升用户体验‌
          FloatingActionButton(
          //onPressed属性常用于各种按钮控件,如登录按钮、提交按钮、返回按钮等,以响应用户的点击行为
              onPressed: () {
              //Navigator.pushNamed方法是用于在路由之间进行页面跳转的方法,它可以接收两个参数:context和routeName
                Navigator.pushNamed(context, Routes.home);
              },
              child: Text(Routes.home)),
          FloatingActionButton(
              onPressed: () {
                Navigator.pushNamed(context, Routes.login);
              },
              child: Text(Routes.login)),
          FloatingActionButton(
              onPressed: () {
              //跳转的页面路径不存在,就走notfoundpage这个请求的处理逻辑
                Navigator.pushNamed(context, "/aaa");
              },
              child: Text("Not")),
          FloatingActionButton(
              onPressed: () {
                Navigator.pushNamed(context, "/room/345");
              },
              child: Text("传值")),
        ],
      ),
    );
  }
}

index.dart(首页)

dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter_haoke/pages/home/tab_index/index.dart';
import 'package:flutter_haoke/pages/home/tab_info/index.dart';
import 'package:flutter_haoke/pages/home/tab_profile/index.dart';
import 'package:flutter_haoke/pages/home/tab_search/index.dart';

List<Widget> tabViewList = [TabIndex(), TabSearch(), TabInfo(), TabProfile()];

List<BottomNavigationBarItem> barItemList = [
  BottomNavigationBarItem(icon: Icon(Icons.home), label: "首页"),
  BottomNavigationBarItem(icon: Icon(Icons.search), label: "搜索"),
  BottomNavigationBarItem(icon: Icon(Icons.info), label: "资讯"),
  BottomNavigationBarItem(icon: Icon(Icons.account_circle), label: "我的"),
];
//‌StatefulWidget‌是Flutter框架中的一个重要概念,它代表有状态的组件,即其状态可以改变的组件。与StatelessWidget相比,StatefulWidget允许其内部状态发生变化,从而影响组件的显示。
//定义与特点:StatefulWidget的主要特点是其内部状态可以改变。它通过一个独立的State对象来管理其状态,当状态发生变化时,组件的UI也会相应更新。StatefulWidget适用于那些需要根据用户交互或数据变化来更新界面的场景,例如计数器、下拉刷新、上拉加载更多等功能‌

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _selectedIndex = 0;

  void _onTapItem(index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: tabViewList[_selectedIndex],
      bottomNavigationBar: BottomNavigationBar(
        type: BottomNavigationBarType.fixed,
        items: barItemList,
        onTap: _onTapItem,
        currentIndex: _selectedIndex,
        selectedItemColor: Colors.green,
      ),
    );
  }
}

application.dart(启动加载类)

dart 复制代码
import 'package:fluro/fluro.dart';
import 'package:flutter/material.dart';
import 'package:flutter_haoke/pages/home/index.dart';
import 'package:flutter_haoke/pages/loading.dart';
import 'package:flutter_haoke/routes.dart';
import 'package:flutter_haoke/scopoed_model/auth_model.dart';
import 'package:flutter_haoke/scopoed_model/city.dart';
import 'package:flutter_haoke/scopoed_model/room_filter.dart';
import 'package:scoped_model/scoped_model.dart';

class Application extends StatelessWidget {
  const Application({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    FluroRouter router = FluroRouter();
    //读取路由配置类routes.dart
    Routes.configureRoutes(router);
    return ScopedModel<CityModel>(
        model: CityModel(),
        child: ScopedModel<AuthModel>(
          model: AuthModel(),
          child: ScopedModel<FilterBarModel>(
            model: FilterBarModel(),
            child: MaterialApp(
              theme: ThemeData(primarySwatch: Colors.green),
              onGenerateRoute: router.generator,
              // home: HomePage(),
              initialRoute: Routes.loading,
            ),
          ),
        ));
    ;
  }
}

pubspec.yaml(依赖配置文件)

依赖配置文件,使用路由等都需要在这个文件中引入依赖。

dart 复制代码
name: flutter_haoke
description: A new Flutter project.

# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1

environment:
  sdk: ">=2.12.0 <3.0.0"

# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
  flutter:
    sdk: flutter

  fluro: ^2.0.3
  flutter_swiper: ^1.1.6
  # ^8.0.8
  fluttertoast: ^8.0.8
  image_picker: ^0.8.1
  share: ^2.0.4 
  # 数据model全局可以用
  scoped_model: ^2.0.0-nullsafety.0
  dio: ^4.0.3
  # 数据存储
  shared_preferences: ^2.0.11
  # model自动化工具
  json_annotation: ^4.4.0
  city_pickers: ^1.0.0
  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  
  

dev_dependencies:
  flutter_test:
    sdk: flutter

  # The "flutter_lints" package below contains a set of recommended lints to
  # encourage good coding practices. The lint set provided by the package is
  # activated in the `analysis_options.yaml` file located at the root of your
  # package. See that file for information about deactivating specific lint
  # rules and activating additional ones.
  # 代码自动化工具
  build_runner: ^2.1.7
  json_serializable: ^6.1.3
  
  flutter_lints: ^1.0.0

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg
  assets:
   # 首页------第一个tab-导航图标
    - static/images/home_index_navigator_total.png
    - static/images/home_index_navigator_map.png
    - static/images/home_index_navigator_share.png
    - static/images/home_index_navigator_rent.png
    - static/images/home_index_recommend_1.png
    - static/images/home_index_recommend_2.png
    - static/images/home_index_recommend_3.png
    - static/images/home_index_recommend_4.png
    - static/icons/widget_search_bar_map.png
    - static/images/home_profile_record.png
    - static/images/home_profile_order.png
    - static/images/home_profile_favor.png
    - static/images/home_profile_id.png
    - static/images/home_profile_message.png
    - static/images/home_profile_contract.png
    - static/images/home_profile_house.png
    - static/images/home_profile_wallet.png
    - static/images/loading.jpg
    
    - static/apps/bingxiang.png
    - static/apps/dianshiji.png
    - static/apps/kongdiao.png
    - static/apps/kuandai.png
    - static/apps/nuanqi.png
    - static/apps/reshuiqi.png
    - static/apps/shafa.png
    - static/apps/tianranqi.png
    - static/apps/xiyiji.png
    - static/apps/yigui.png
    

  # 字体图标
  fonts:
    - family: CommonIcon
      fonts:
      - asset: static/fonts/iconfont.ttf 
    
  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware.

  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/assets-and-images/#from-packages

  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  # fonts:
  #   - family: Schyler
  #     fonts:
  #       - asset: fonts/Schyler-Regular.ttf
  #       - asset: fonts/Schyler-Italic.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/custom-fonts/#from-packages

登录效果图:

login.dart(登录页)

dart 复制代码
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_haoke/routes.dart';
import 'package:flutter_haoke/scopoed_model/auth_model.dart';
import 'package:flutter_haoke/utils/common_toast.dart';
import 'package:flutter_haoke/utils/dio_http.dart';
import 'package:flutter_haoke/utils/scopoed_mode_helper.dart';
import 'package:flutter_haoke/utils/store.dart';
import 'package:flutter_haoke/utils/string_is_bull_or_empty.dart';
import 'package:flutter_haoke/widget/page_content.dart';

class LoginPage extends StatefulWidget {
  const LoginPage({Key? key}) : super(key: key);

  @override
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  bool showPassword = false;
  var usernameController = TextEditingController();
  var passwordController = TextEditingController();

  _onloginHandler() async {
    var username = usernameController.text;
    var password = passwordController.text;
    if (StringIsNullOrEmpty(username) || StringIsNullOrEmpty(password)) {
      CommontToast.showToast("用户名或密码不能为空!");
      return;
    }
    var url = '/user/login';
    var params = {'username': username, 'password': password};
    //远程请求工具类
    var res = await DioHttp.of(context).post(url, params);

    print(res);
    //伪造假数据模拟登录成功
    CommontToast.showToast('登录成功');
    String token = '123';
    //获取缓存变量
    Store store = await Store.getInstance();
    //存入token
    await store.setString(StoreKeys.token, token);
    //更新全局model
    ScopoedModelHelper.getModel<AuthModel>(context).login(token, context);
    Timer(Duration(seconds: 1), () {
      Navigator.of(context).pop();
    });

    //真实运行模式
    // if (res.data!['data']['code'] == 0) {

    //   CommontToast.showToast(res.data!['data']['message']);
    //   String token = res.data!['data']['body'];
    //   //获取缓存变量
    //   Store store = await Store.getInstance();
    //   //存入token
    //   await store.setString(StoreKeys.token, token);
    //   //更新全局model
    //   ScopoedModelHelper.getModel<AuthModel>(context).login(token, context);
    //   Timer(Duration(seconds: 1), () {
    //     Navigator.of(context).pop();
    //   });
    // }
  }

  @override
  Widget build(BuildContext context) {
  //推荐把Scaffold作为每个页面的基础组件,并为它搭配上AppBar组件.AppBar中的SystemUiOverlayStyle,是设置手机 系统状态栏和系统导航栏的重要属性
    return Scaffold(
    //AppBar是顶部的组件,它包含Toolbar 和 StatusBar,
//StatusBar 属于系统区域了,里面会显示时间和WIFI信号和GPS等图标。
//toolbarHeight 设置为0的时候,工具栏就不能显示了,这时候返回按钮和标题不能正常工作,一般页面都不会这样设置。
//Toolbar 中包含 leading(默认会生成返回按钮),title(一般放Text显示标题),action(一般放右上角的按钮,是一个数组,当设置action时,title就不居中了,如果需要title居中可以设置centerTitle)
        appBar: AppBar(
          title: Text("登录"),
        ),
        //SafeArea安全区域指的是 排除刘海屏,水滴屏和圆角屏幕的边缘的区域。 ListView 默认是有一个SafeArea区域Padding的,当你为ListView设置Padding时,这个Safe边距将会失效
        body: SafeArea(
          minimum: EdgeInsets.all(30),
          child: ListView(
            children: [
              TextField(
                controller: usernameController,
                decoration: InputDecoration(
                  labelText: "用户名",
                ),
              ),
              Padding(padding: EdgeInsets.all(10)),
              TextField(
                controller: passwordController,
                obscureText: !showPassword,
                decoration: InputDecoration(
                    labelText: "密码",
                    suffixIcon: IconButton(
                        onPressed: () {
                          setState(() {
                            showPassword = !showPassword;
                          });
                        },
                        icon: showPassword
                            ? Icon(Icons.visibility)
                            : Icon(Icons.visibility_off))),
              ),
              Padding(padding: EdgeInsets.all(10)),
              ElevatedButton(
                child: Text("登录"),
                onPressed: () {
                  _onloginHandler();
                },
              ),
              Padding(padding: EdgeInsets.all(10)),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text("还没有账号,"),
                  TextButton(
                      onPressed: () {
                        Navigator.pushReplacementNamed(
                            context, Routes.register);
                      },
                      child: Text("去注册~"))
                ],
              )
            ],
          ),
        ));
  }
}

dio_http.dart(远程请求类)

dart 复制代码
import 'dart:convert';
import 'dart:ffi';
import 'dart:io';

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_haoke/config.dart';
import 'package:flutter_haoke/routes.dart';
import 'package:flutter_haoke/scopoed_model/auth_model.dart';
import 'package:flutter_haoke/utils/common_toast.dart';
import 'package:flutter_haoke/utils/scopoed_mode_helper.dart';

class DioHttp {
  late Dio _client;
  late BuildContext context;

  static DioHttp of(BuildContext context) {
    return DioHttp._innernal(context);
  }

  DioHttp._innernal(BuildContext context) {
    this.context = context;
    var options = BaseOptions(
        baseUrl: Config.BaseUrl,
        connectTimeout: 1000 * 10, //请求超时时间:10秒
        receiveTimeout: 1000 * 3,
        extra: {'context': context});
    Interceptor interceptor =
        InterceptorsWrapper(onResponse: (response, handler) {
      if (response == null) return handler.next(response);
      var code = json.decode(response.toString())['code'];
      if (code == 404) {
        CommontToast.showToast("地址路径错误");
        return handler.next(response);
      }
      if (code.toString().startsWith('4')) {
        //退出登录
        ScopoedModelHelper.getModel<AuthModel>(context).logout();
        //登录过期时如果是启动页不用跳转到登录页
        if (ModalRoute.of(context)!.settings.name == Routes.loading) {
          return handler.next(response);
        }
        CommontToast.showToast("登录过期!");
        Navigator.of(context).pushNamed(Routes.login); 
      }
      return handler.next(response);
    });
    _client = Dio(options);
    _client.interceptors.add(interceptor);
    this._client = _client;
    // if (_client == null || context != this.context) {
    //   this.context = context;
    //   var options = BaseOptions(
    //       baseUrl: Config.BaseUrl,
    //       connectTimeout: 1000 * 10, //请求超时时间:10秒
    //       receiveTimeout: 1000 * 3,
    //       extra: {'context': context});
    //   _client = Dio(options);
    //   this._client = _client;
    // }
  }

  Future<Response<Map<String, dynamic>>> get(String path,
      [Map<String, dynamic>? params, String? token]) async {
    var options = Options(headers: {'Authorization': token});
    return await _client.get(path, queryParameters: params, options: options);
  }

  Future<Response<Map<String, dynamic>>> post(String path,
      [Map<String, dynamic>? params, String? token]) async {
    var options = Options(headers: {'Authorization': token});
    return await _client.post(path, data: params, options: options);
  }

  Future<Response<Map<String, dynamic>>> postFormData(String path,
      [dynamic params, String? token]) async {
    var options = Options(
        contentType: 'multipart/form-data', headers: {'Authorization': token});
    return await _client.post(path, data: params, options: options);
  }
}

3、Fluro路由

routes.dart(路由配置类)

dart 复制代码
// 1.编写route文件 并编写routes基本结构
import 'package:fluro/fluro.dart';
import 'package:flutter/material.dart';
import 'package:flutter_haoke/loading.dart';
import 'package:flutter_haoke/pages/community_picker.dart';
import 'package:flutter_haoke/pages/home/index.dart';
import 'package:flutter_haoke/pages/loading.dart';
import 'package:flutter_haoke/pages/login.dart';
import 'package:flutter_haoke/pages/notfound.dart';
import 'package:flutter_haoke/pages/register.dart';
import 'package:flutter_haoke/pages/room_add/index.dart';
import 'package:flutter_haoke/pages/room_manager/index.dart';
import 'package:flutter_haoke/pages/roomdetail/index.dart';
import 'package:flutter_haoke/pages/setting.dart';

class Routes {
  // 2.定义路由名称
  static String home = '/';
  static String login = '/login';
  static String register = '/register';
  static String roomdetail = '/roomDetail/:roomId';
  static String setting = '/setting';
  static String roommanager = '/roommanager';
  static String roomAdd = '/roomAdd';
  static String community = '/community';
  static String loading = '/loading';

  // 3.定义路由处理函数
  static Handler _homeHandel = Handler(
    handlerFunc: (context, parameters) {
      return HomePage();
    },
  );
  static Handler _loginHandel = Handler(
    handlerFunc: (context, parameters) {
      return LoginPage();
    },
  );
  static Handler _registerHandel = Handler(
    handlerFunc: (context, parameters) {
      return RegisterPage();
    },
  );
  static Handler _settingHandel = Handler(
    handlerFunc: (context, parameters) {
      return SettingPage();
    },
  );
  static Handler _roommanagerHandel = Handler(
    handlerFunc: (context, parameters) {
      return RoomManagerPage();
    },
  );
  static Handler _roomdetailHandel = Handler(
    handlerFunc: (context, parameters) {
      return RoomDetailPage(
        roomId: parameters["roomId"]![0],
      );
    },
  );
  static Handler _notfoundHandel = Handler(
    handlerFunc: (context, parameters) {
      return NotFoundPage();
    },
  );

  static Handler _roomAddHandel = Handler(
    handlerFunc: (context, parameters) {
      return RoomAddPage();
    },
  );
  static Handler _communityHandel = Handler(
    handlerFunc: (context, parameters) {
      return CommunityPickerPage();
    },
  );
  static Handler _loadingHandel = Handler(
    handlerFunc: (context, parameters) {
      return LoadingPage();
    },
  );
  // 4.编写函数 configureRoutes 关联路由名称和处理函数
  static void configureRoutes(FluroRouter router) {
    router.define(home, handler: _homeHandel);
    router.define(login, handler: _loginHandel);
    router.define(register, handler: _registerHandel);
    router.define(roomdetail, handler: _roomdetailHandel);
    router.define(setting, handler: _settingHandel);
    router.define(roommanager, handler: _roommanagerHandel);
    router.define(roomAdd, handler: _roomAddHandel);
    router.define(community, handler: _communityHandel);
    router.define(loading, handler: _loadingHandel);
    //定义找不到页面时走这个找不到页面的Handel
    router.notFoundHandler = _notfoundHandel;
  }
}

not_found_page.dart(路由优化,找不到页面时展示此页面)

dart 复制代码
import 'package:flutter/material.dart';

class NotFoundPage extends StatelessWidget {
  const NotFoundPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("404"),
      ),
      body: Center(
        child: Text("页面不存在!"),
      ),
    );
  }
}

4、注册页面

dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter_haoke/utils/common_toast.dart';
import 'package:flutter_haoke/utils/dio_http.dart';
import 'package:flutter_haoke/utils/string_is_bull_or_empty.dart';

class RegisterPage extends StatefulWidget {
  const RegisterPage({Key? key}) : super(key: key);
//createState是State对象的初始化方法,初始化时执行_RegisterPageState方法
  @override
  State<RegisterPage> createState() => _RegisterPageState();
}

class _RegisterPageState extends State<RegisterPage> {
  var usernameController = TextEditingController();
  var passwordController = TextEditingController();
  var surePsdController = TextEditingController();

  _onRegisterHandle() async {
    var username = usernameController.text;
    var password = passwordController.text;
    var surePsd = surePsdController.text;
    if (password != surePsd) {
      return CommontToast.showToast("两次输入的密码不一致!");
    }
    if (StringIsNullOrEmpty(username) || StringIsNullOrEmpty(password)) {
      return CommontToast.showToast("用户名或密码不能为空!");
    }
    const url = "/register";
    var params = {'username': username, 'password': password};
    var res = await DioHttp.of(context).post(url, params);
//  {"data":{"code":0,"data":[{"name":"admin","ps":123}],"message":"success"}}
    if (res.data!["data"]["code"] == 0) {
      CommontToast.showToast(res.data!["data"]["message"]);
      Navigator.of(context).pushReplacementNamed("login");
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("注册"),
        ),
        body: SafeArea(
          minimum: EdgeInsets.all(30),
          child: ListView(
            children: [
              TextField(
                controller: usernameController,
                decoration: InputDecoration(
                  labelText: "用户名",
                ),
              ),
              Padding(padding: EdgeInsets.all(10)),
              TextField(
                controller: passwordController,
                obscureText: true,
                decoration: InputDecoration(
                  labelText: "密码",
                ),
              ),
              Padding(padding: EdgeInsets.all(10)),
              TextField(
                controller: surePsdController,
                obscureText: true,
                decoration: InputDecoration(
                  labelText: "确认密码",
                ),
              ),
              Padding(padding: EdgeInsets.all(10)),
              ElevatedButton(
                child: Text("注册"),
                onPressed: () {
                  _onRegisterHandle();
                },
              ),
              Padding(padding: EdgeInsets.all(10)),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text("已有账号,"),
                  TextButton(
                      onPressed: () {
                        Navigator.pushReplacementNamed(context, "register");
                      },
                      child: Text("去登录~"))
                ],
              )
            ],
          ),
        ));
  }
}
相关推荐
YeeWang24 分钟前
🎉 Eficy 让你的 Cherry Studio 直接生成可预览的 React 页面
前端·javascript
gnip26 分钟前
Jenkins部署前端项目实战方案
前端·javascript·架构
Orange30151141 分钟前
《深入源码理解webpack构建流程》
前端·javascript·webpack·typescript·node.js·es6
whysqwhw1 小时前
安卓图片性能优化技巧
android
风往哪边走1 小时前
自定义底部筛选弹框
android
江上清风山间明月1 小时前
Flutter AlwaysScrollableScrollPhysics详解
flutter·滚动·scrollable·scrollphysics
李明卫杭州2 小时前
CSS `clamp()` 函数详解
javascript
奶丝兔蜜柚2 小时前
栈溢出优化
javascript
Yyyy4822 小时前
MyCAT基础概念
android
小高0072 小时前
📈前端图片压缩实战:体积直降 80%,LCP 提升 2 倍
前端·javascript·面试