flutter开发实战-类似微博帖子列表及下拉刷新上拉加载效果

news2024/11/17 16:33:18

flutter开发实战-类似微博帖子列表及下拉刷新上拉加载效果

在之前处理类似微博帖子列表及下拉刷新上拉加载效果,刷新使用的是EasyRefresh

一、引入EasyRefresh与likeButton

在工程的pubspec.yaml中引入插件

    # 下拉刷新、上拉更多
  easy_refresh: ^3.3.2+1
  pull_to_refresh: ^2.0.0
    

需要使用EasyRefreshController来控制处理刷新,初始化

@override
  void initState() {
    super.initState();
    _controller = EasyRefreshController(
      controlFinishRefresh: true,
      controlFinishLoad: true,
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
    

需要在onRefresh与onLoad来处理下拉刷新上拉加载数据。

二、类似微博帖子列表及下拉刷新上拉加载效果

类似微博帖子列表,这里定义帖子item,每个帖子中可能包括多张图片。

NoteItem:

class NoteItem {
  String? feedId;
  String? coverImageUrl;
  String? title;
  String? textContent;
  String? username;
  String? avatarUrl;
  String? time;
  String? likeNum;
  bool? liked;
  String? categoryName;
  List<NoteImage>? images;
}

class NoteImage {
  String? imageUrl;
  String? imageWidth;
  String? imageHeight;
}
    

定义每个帖子的Widget:NoteListItemWidget,NoteListItemWidget结构是用户头像,描述文本,帖子图片,点赞、评论、分享栏。

NoteListItemWidget:

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_app_demolab/display/note_item.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:like_button/like_button.dart';

class NoteListItemWidget extends StatefulWidget {
  NoteListItemWidget({
    super.key,
    required this.noteItem,
  });

  final NoteItem noteItem;

  @override
  _NoteListItemWidgetState createState() => _NoteListItemWidgetState();
}

class _NoteListItemWidgetState extends State<NoteListItemWidget> {
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.symmetric(vertical: 5.0, horizontal: 0.0),
      padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 15.0),
      //边框设置
      decoration: new BoxDecoration(
        //背景
        color: Color(0xFFFFFFFF),
        //设置四周圆角 角度 这里的角度应该为 父Container height 的一半
        borderRadius: BorderRadius.only(
            topLeft: Radius.circular(10.0), topRight: Radius.circular(10.0)),
        //设置四周边框
        border: new Border.all(width: 1, color: Color(0xFFf1f1f1)),
      ),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          NoteListAuthorBar(
              username: widget.noteItem.username,
              avatarUrl: widget.noteItem.avatarUrl,
              showTime: widget.noteItem.time
          ),
          Text(
            widget.noteItem.textContent??"",
            maxLines: 5,
            overflow: TextOverflow.ellipsis,
            style: TextStyle(
              fontSize: 14,
              fontWeight: FontWeight.normal,
              color: Color(0xFF666666),
            ),
          ),
          NoteListImage(images: (widget.noteItem.images != null
              ? widget.noteItem.images
              : [])),
          NoteListBottom(
              liked: widget.noteItem.liked,
              likeNum: widget.noteItem.likeNum,
              categoryName: widget.noteItem.categoryName
          ),
        ],
      ),
    );
  }
}

// 顶部header
class NoteListAuthorBar extends StatelessWidget {
  const NoteListAuthorBar({
    super.key,
    this.username,
    this.avatarUrl,
    this.showTime,
  });

  final String? username;
  final String? avatarUrl;
  final String? showTime;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.only(bottom: 10),
      height: 60,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          CircleAvatar(
            backgroundImage: NetworkImage(avatarUrl ?? ""),
          ),
          Padding(
            padding: EdgeInsets.only(left: 10),
          ),
          Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                username ?? "",
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(
                  fontSize: 16,
                  fontWeight: FontWeight.bold,
                  color: Color(0xFF444444),
                ),
              ),
              Padding(
                padding: EdgeInsets.only(top: 5),
              ),
              Text(
                showTime ?? "",
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(
                  fontSize: 12,
                  fontWeight: FontWeight.normal,
                  color: Color(0xFF444444),
                ),
              ),
            ],
          )
        ],
      ),
    );
  }
}

// 显示图片
class NoteListImage extends StatelessWidget {
  const NoteListImage({
    super.key,
    this.images,
  });

  final List<NoteImage>? images;

  Widget layoutOfImages(List images, BuildContext context) {
    double screenWidth = MediaQuery
        .of(context)
        .size
        .width;
    double containW = screenWidth - 30;
    if (images.length == 1) {
      NoteImage noteImage = images[0];
      double imgWidth = 0;
      double imgHeight = 0;
      if (noteImage.imageWidth != null &&
          noteImage.imageWidth!.isNotEmpty &&
          noteImage.imageHeight != null &&
          noteImage.imageHeight!.isNotEmpty) {
        imgWidth = double.parse(noteImage.imageWidth!);
        imgHeight = double.parse(noteImage.imageHeight!);
        if (imgWidth > 0 && imgHeight > 0) {
          if (imgWidth < 1.0 && imgHeight < 1.0) {
            double maxLen = containW / 2.0;
            double showW = maxLen;
            double showH = maxLen;
            return Container(
              height: showH,
              width: showW,
              child: CachedNetworkImage(
                imageUrl: noteImage.imageUrl ?? "",
                placeholder: (context, url) =>
                    Center(child: CupertinoActivityIndicator()),
                errorWidget: (context, url, error) => Icon(Icons.error),
                fit: BoxFit.cover,
              ),
            );
          } else {
            double imageScale = imgWidth / imgHeight;
            double len1_3 = (containW - 20) / 3.0;
            double maxLen = len1_3 * 2 + 10;
            double showW = 0;
            double showH = 0;

            if (imageScale > 1.0) {
              // 横图
              showW = maxLen;
              showH = showW / imageScale;
            } else {
              // 竖图
              showH = maxLen;
              showW = imageScale * showH;
            }

            return Container(
              height: showH,
              width: showW,
              child: CachedNetworkImage(
                imageUrl: noteImage.imageUrl??"",
                placeholder: (context, url) =>
                    Center(child: CupertinoActivityIndicator()),
                errorWidget: (context, url, error) => Icon(Icons.error),
                fit: BoxFit.cover,
              ),
            );
          }
        }
      }
    } else if (images.length >= 2) {
      double showImageW = 0;
      if (images.length == 2 || images.length == 4) {
        showImageW = (containW - 10) / 2.0;
      } else {
        showImageW = (containW - 20) / 3.0;
      }
      List<Widget> imageWidgets = [];

      for (int index = 0; index < images.length; index++) {
        NoteImage noteImage = images[index];
        Widget widget = Container(
            height: showImageW,
            width: showImageW,
            child: CachedNetworkImage(
              imageUrl: noteImage.imageUrl??"",
              placeholder: (context, url) =>
                  Center(child: CupertinoActivityIndicator()),
              errorWidget: (context, url, error) => Icon(Icons.error),
              fit: BoxFit.cover,
            )
        );
        imageWidgets.add(widget);
      }

      return Container(
        alignment: Alignment.center,
        width: containW,
        child: Wrap(
          spacing: 8.0,
          // 主轴(水平)方向间距
          runSpacing: 8.0,
          // 纵轴(垂直)方向间距
          alignment: WrapAlignment.start,
          //沿主轴方向居中
          crossAxisAlignment: WrapCrossAlignment.center,
          children: imageWidgets,
        ),
      );
    }

    return Container();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.symmetric(vertical: 10, horizontal: 0),
      child: layoutOfImages(this.images??[], context),
    );
  }
}

// 显示底部
class NoteListBottom extends StatelessWidget {
  const NoteListBottom({
    super.key,
    this.liked,
    this.likeNum,
    this.categoryName,
  });

  final bool? liked;
  final String? likeNum;
  final String? categoryName;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 60,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Container(
            padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0),
            //边框设置
            decoration: new BoxDecoration(
              //背景
              color: Color(0xFFFFFFFF),
              //设置四周圆角 角度 这里的角度应该为 父Container height 的一半
              borderRadius: BorderRadius.only(
                  topLeft: Radius.circular(10.0),
                  topRight: Radius.circular(10.0)),
              //设置四周边框
              border: new Border.all(width: 1, color: Color(0xFFf1f1f1)),
            ),
            child: Text(
              this.categoryName??"",
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
              style: TextStyle(
                fontSize: 12,
                fontWeight: FontWeight.normal,
                color: Color(0xFF444444),
              ),
            ),
          ),
          Container(
            padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.end,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                LikeButton(
                  likeBuilder: (bool isLiked) {
                    return Icon(Icons.thumb_up,
                        color: (isLiked ? Colors.deepOrange : Color(
                            0xFFA9A9A9)));
                  },
                  likeCount: int.parse(this.likeNum??"0"),
                  isLiked: this.liked,
                ),
                TextButton(
                  child: Wrap(
                    // 可以通过设置两个基础组件的间距
                    alignment: WrapAlignment.center, //沿主轴方向居中
                    crossAxisAlignment: WrapCrossAlignment.center,
                    spacing: 5,
                    children: [
                      Icon(Icons.comment, color: Color(0xFFA9A9A9)),
                      Text("评论",
                          style:
                          TextStyle(fontSize: 12, color: Color(0xFF888888)))
                    ],
                  ),
                  onPressed: () {},
                ),
                TextButton(
                  child: Wrap(
                    // 可以通过设置两个基础组件的间距
                    alignment: WrapAlignment.center, //沿主轴方向居中
                    crossAxisAlignment: WrapCrossAlignment.center,
                    spacing: 5,
                    children: [
                      Icon(Icons.share, color: Color(0xFFA9A9A9)),
                      Text("分享",
                          style:
                          TextStyle(fontSize: 12, color: Color(0xFF888888)))
                    ],
                  ),
                  onPressed: () {},
                ),
              ],
            ),
          )
        ],
      ),
    );
  }
}

    

最后,我们在page中处理类似帖子列表DisplayPage

DisplayPage:

import 'dart:math';

import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app_demolab/display/note_item.dart';
import 'package:flutter_app_demolab/display/note_item_widget.dart';

class DisplayPage extends StatefulWidget {
  const DisplayPage({super.key});

  @override
  State<DisplayPage> createState() => _DisplayPageState();
}

class _DisplayPageState extends State<DisplayPage> {
  int _count = 10;
  late EasyRefreshController _controller;

  bool isLoading = false;
  ScrollController scrollController = ScrollController();
  List<NoteItem> list = [];

  List<NoteImage> noteImages = [];

  String randomBit() {
    String scopeF = '0123456789'; //首位
    String result = '';
    result = scopeF[Random().nextInt(scopeF.length)];
    return result;
  }

  @override
  void initState() {
    initItems();
    super.initState();
    _controller = EasyRefreshController(
      controlFinishRefresh: true,
      controlFinishLoad: true,
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void initItems() {
    for (int index = 0; index < 12; index++) {
      NoteImage model = NoteImage();
      noteImages.add(model);

      if (index == 0) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202010/11/20201011085349_R2CjU.thumb.1000_0.jpeg";
        model.imageWidth = "580";
        model.imageHeight = "880";
      } else if (index == 1) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202011/15/20201115080806_7ddba.thumb.1000_0.jpg";
        model.imageWidth = "580";
        model.imageHeight = "580";
      } else if (index == 2) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202011/15/20201115100436_7ffc6.thumb.1000_0.jpg";
        model.imageWidth = "580";
        model.imageHeight = "560";
      } else if (index == 3) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/item/202006/09/20200609232715_yvqkd.thumb.1000_0.jpg";
        model.imageWidth = "580";
        model.imageHeight = "380";
      } else if (index == 4) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202012/03/20201203144923_425bc.thumb.1000_0.jpg";
        model.imageWidth = "580";
        model.imageHeight = "430";
      } else if (index == 5) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202010/11/20201011085351_3d2cS.thumb.1000_0.jpeg";
        model.imageWidth = "580";
        model.imageHeight = "850";
      } else if (index == 6) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202009/28/20200928184639_sxrum.thumb.1000_0.jpeg";
        model.imageWidth = "580";
        model.imageHeight = "810";
      } else if (index == 7) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202012/03/20201203145208_55d8c.thumb.1000_0.jpg";
        model.imageWidth = "580";
        model.imageHeight = "430";
      } else if (index == 8) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/item/202006/09/20200609232715_pjsin.thumb.1000_0.jpg";
        model.imageWidth = "580";
        model.imageHeight = "860";
      } else if (index == 9) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202012/03/20201203144929_cd406.thumb.1000_0.jpg";
        model.imageWidth = "580";
        model.imageHeight = "450";
      } else if (index == 10) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202012/03/20201203144705_40405.thumb.1000_0.jpg";
        model.imageWidth = "580";
        model.imageHeight = "450";
      } else if (index == 11) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202012/03/20201203144708_d5287.thumb.1000_0.jpg";
        model.imageWidth = "580";
        model.imageHeight = "750";
      }
    }

    for (int index = 0; index < 20; index++) {
      NoteItem model = NoteItem();
      model.liked = false;
      list.add(model);

      if (index % 5 == 0) {
        model.username = "可可";
        model.avatarUrl =
        "https://c-ssl.duitang.com/uploads/item/202007/22/20200722212206_ifscm.thumb.1000_0.jpg";
        model.time = "08/01 13:51";
        model.textContent = "等轮到你讲话时再说,认真聆听对方的谈话,千万别打断对方讲话,耐心等着,轮到你时再讲。";
        model.likeNum = "861380";
        model.categoryName = "兽圈";
      } else if (index % 5 == 2) {
        model.username = "琪琪";
        model.avatarUrl =
        "https://c-ssl.duitang.com/uploads/item/202007/22/20200722212209_insvm.thumb.1000_0.png";
        model.time = "08/01 13:51";
        model.textContent = "记得有活动,在活动现场等你哦~";
        model.likeNum = "50999";
        model.categoryName = "娃圈";
      } else if (index % 5 == 4) {
        model.username = "悦悦";
        model.avatarUrl =
        "https://c-ssl.duitang.com/uploads/item/202007/19/20200719102754_jrcoe.thumb.1000_0.jpg";
        model.time = "08/01 13:51";
        model.textContent = "说一说你想要的形象模型,晒一晒你的喜欢";
        model.likeNum = "6150";
        model.categoryName = "Pia戏圈";
      } else {
        model.username = "爱丽多啦";
        model.avatarUrl =
        "https://c-ssl.duitang.com/uploads/item/202007/19/20200719102756_ihfku.thumb.1000_0.jpg";
        model.time = "08/01 13:51";
        model.textContent = "你永远不懂我喜欢的东西,555";
        model.likeNum = "1507";
        model.categoryName = "语c圈";
      }

      List<NoteImage> tmpImages = [];
      String randomf = randomBit();
      String randomt = randomBit();
      int f = int.parse(randomf);
      int t = int.parse(randomt);
      print("f:$f");
      print("t:$t");

      if (f < t) {
        tmpImages.addAll(noteImages.sublist(f, t));
      } else if (f > t) {
        tmpImages.addAll(noteImages.sublist(t, f));
      } else {
        tmpImages.addAll(noteImages.sublist(t, t+1));
      }
      model.images = tmpImages;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('EasyRefresh'),
      ),
      body: EasyRefresh(
        controller: _controller,
        header: const ClassicHeader(),
        footer: const ClassicFooter(),
        onRefresh: () async {
          await Future.delayed(const Duration(seconds: 4));
          if (!mounted) {
            return;
          }
          list = [];
          initItems();
          setState(() {
            _count = 10;
          });
          _controller.finishRefresh();
          _controller.resetFooter();
        },
        onLoad: () async {
          await Future.delayed(const Duration(seconds: 4));
          if (!mounted) {
            return;
          }
          initItems();
          setState(() {
            _count += 5;
          });
          _controller.finishLoad(
              _count >= 200 ? IndicatorResult.noMore : IndicatorResult.success);
        },
        child: ListView.builder(
          itemBuilder: (context, index) {
            return NoteListItemWidget(noteItem: this.list[index]);
          },
          itemCount: this.list.length,
        ),
      ),
    );
  }
}
    

效果图如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、小结

flutter开发实战-类似微博帖子列表及下拉刷新上拉加载效果

学习记录,每天不停进步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1713558.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

每天写两道(二)LRU缓存、数组中最大的第k个元素

146.LRU 缓存 . - 力扣&#xff08;LeetCode&#xff09; 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存…

【考研数学】数学一和数学二哪个更难?如何复习才能上90分?

很明显考研数学一更难&#xff01; 不管是复习量还是题目难度 对比项考研数学一考研数学二适用专业理工科类及部分经济学类理工科类考试科目高等数学、线性代数、概率论与数理统计高等数学、线性代数试卷满分150分150分考试时间180分钟180分钟试卷内容结构高等数学约60%&…

精通推荐算法7:多任务学习 -- 总体架构

1 多任务学习的总体架构 目前的互联网主流推荐场景在大多数情况下需要优化多个业务目标。例如在淘宝商品推荐中&#xff0c;需要兼顾点击率和转化率。在抖音短视频推荐中&#xff0c;需要考虑完播率、播放时长、点赞率、评论率、关注率等目标。为了提升各项业务目标&#xff0…

虹科Pico汽车示波器 | 免拆诊断案例 | 2017款吉利帝豪GL车发动机偶尔无法起动

故障现象  一辆2017款吉利帝豪GL车&#xff0c;搭载JLC-4G18发动机和手动变速器&#xff0c;累计行驶里程约为39.3万km。车主反映&#xff0c;该车发动机偶尔无法起动。故障发生频率比较频繁&#xff0c;冷机状态下故障比较容易出现。 故障诊断  接车后试车&#xff0c;故…

一款超好用的国产Redis可视化工具

一、简介 1、这是一款追求极致性能&#xff08;它可以支持前面100万数据的展示。&#xff09;海量数据下低内存占用、极简布局、高效交互、跨平台、支持反序列化Java字节码的redis可视化客户端工具。 支持三大操作系统Windows、MacOS、Linux&#xff0c;适合不同操作系统口味的…

树莓派开箱

1.树莓派4B配置 CPU&#xff1a;64位1.5GHZ四核处理器。 GPU:Broadcom VideoCore VI500MHZ 蓝牙5.0 电源Type C(5V 3A),也可以使用排针链接5V锂电池最大放电电流必须达到3A。 还有千兆以太网等以后用到再说。 接下来进入文章重点 2.镜像文件烧录 前期准备&#xff1a;1…

ChatGPT魔法,定制个性化提示词!

扮演Prompt创作者的角色 我想让你成为我的Prompt创作者。你的目标是帮助我创建最佳的Prompt&#xff0c;这个Prompt将由 你ChatGPT使用。 你将遵循以下过程&#xff1a; 1.首先&#xff0c;你会问我Prompt是关于什么的。我会告诉你&#xff0c;但我们需要通过不断的重复来改进…

在 CentOS 上安装 PostgreSQL 的全面指南

PostgreSQL 是一种功能强大的开源关系型数据库管理系统&#xff0c;广泛应用于各种领域。它提供了诸如事务处理、并发控制和数据完整性等高级功能&#xff0c;因此深受开发者和企业的欢迎。本指南将逐步引导您在 CentOS 上安装 PostgreSQL&#xff0c;以便您充分利用其众多优势…

Facebook:社交世界的接口

在当今数字时代&#xff0c;社交媒体已经成为了人们生活中不可或缺的一部分&#xff0c;而Facebook作为其中的巨头之一&#xff0c;扮演着至关重要的角色。本文将带您深入探索Facebook这张社交世界的画卷&#xff0c;全面了解这个令人着迷的平台。 起源与历程 Facebook的故事始…

无线麦克风什么牌子的音质效果好?一文读懂无线领夹麦克风哪款好

​在当今的数字时代&#xff0c;无线技术已经深入到我们生活的方方面面&#xff0c;无线领夹麦克风便是其中的佼佼者。它们为讲者、表演者以及那些需要在移动中讲话的人们提供了解放双手和自由移动的可能。本文旨在探讨无线领夹麦克风的多种用途&#xff0c;以及如何挑选最适合…

Pycharm打开django支持

在 PyCharm 中打开 “Settings/Preferences” -> “Languages & Frameworks” -> “Django”。 勾上Enable Django support 然后配置好文件根目录就好了

基于PTP实现主机与相机系统时钟同步功能

基于PTP实现主机与相机系统时钟同步功能 一、PTP简介二、工业相机PTP功能支持三、工业相机时间戳介绍3.1基本概念3.2海康工业相机时间戳介绍3.2.1相机参数时间戳3.2.2图像嵌入式时间戳3.2.3相机event事件时间戳3.2.4各种时间戳的时序关系3.2.5通过工业相机SDK获取相机时间戳 四…

如何将md文件精确的转换成docx文件

如何将md文件转换成docx&#xff1f; 文章目录 如何将md文件转换成docx&#xff1f;一、如何将MD文件比较完美的转换成word呢&#xff1f;二、方法3 步骤1、下载一个可用的MarkDown编辑器2、下载Pandoc安装 三、来进行转化了 一、如何将MD文件比较完美的转换成word呢&#xff1…

MySQL实战行转列(或称为PIVOT)实战sales的表记录了不同产品在不同月份的销售情况,进行输出

有一个sales的表&#xff0c;它记录了不同产品在不同月份的销售情况&#xff1a; productJanuaryFebruaryMarchProduct AJanuary10Product AFebruary20Product BJanuary5Product BFebruary15Product CJanuary8Product CFebruary12 客户需求展示为如下的样子&#xff1a; pro…

【优选算法】分治 {三分快排:三指针优化,随机选key,快速选择算法;归并排序:统计数组中的逆序对,统计数组中的翻转对;相关编程题解析}

一、经验总结 1.1 三分快排 优化一&#xff1a;三指针优化 之前学习的快速排序无法妥善处理相等或重复序列的排序问题&#xff08;有序且三数取中无效&#xff09;&#xff0c;使快速排序的效率无法达到最优。 为了解决重复序列的问题&#xff0c;我们将原先的双指针法&…

Spire.PDF for .NET【文档操作】演示:将PDF 拆分为多个 PDF

Spire.PDF 完美支持将多页 PDF 拆分为单页。但是&#xff0c;更常见的情况是&#xff0c;您可能希望提取选定的页面范围并保存为新的 PDF 文档。在本文中&#xff0c;您将学习如何通过 Spire.PDF 在 C#、VB.NET 中根据页面范围拆分 PDF 文件。 Spire.PDF for .NET 是一款独立 …

光速进化!易天万兆光模块全面升级

易天光通信宣布10G SFP/25G SFP28系列光模块产品进行了全新升级&#xff0c;旨在为客户提供更优质、更高效、更可靠的光通信解决方案。这次升级不仅是技术的突破&#xff0c;更是对未来光通信发展趋势的深刻洞察和精准把握。 一、技术革新&#xff0c;性能卓越 本次全系列产品…

记一次艰难的SQL注入(过安全狗)

1.前言 最近在挖补天的src&#xff0c;然后挖出了不少SQL注入&#xff0c;完了出了数据库名就不管那么多提交了。今天挖了个报错注入的&#xff0c;突然一激灵&#xff0c;说我不能这样颓废下去了&#xff0c;刚好是个后台登录的界面&#xff0c;我决心要登进它的后台。 2.注入…

【UnityShader入门精要学习笔记】第十五章 使用噪声

本系列为作者学习UnityShader入门精要而作的笔记&#xff0c;内容将包括&#xff1a; 书本中句子照抄 个人批注项目源码一堆新手会犯的错误潜在的太监断更&#xff0c;有始无终 我的GitHub仓库 总之适用于同样开始学习Shader的同学们进行有取舍的参考。 文章目录 使用噪声上…

WebGL实现医学教学软件

使用WebGL实现医学教学软件是一个复杂但非常有益的项目&#xff0c;可以显著提升医学教育的互动性和效果。以下是详细的实现步骤&#xff0c;包括需求分析、技术选型、开发流程和注意事项。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作…