Vue 仿蚂蚁森林能量球生成获取组件

news/2024/7/10 2:46:57 标签: vue, 仿蚂蚁森林, 组件化

小编最近刚刚结束了一个需求,需求是这样的:通过做任务生成一定的太阳,领取太阳后达到一定等级树苗进行生长。嗯?听起来是不是觉得很熟悉?是的,小编就想到了蚂蚁森林,不知道各位同学获取到多少个证书了呢?这些证书花了你们多少毛爷爷呢?
原本小编不想自己实现的,奈何网上资料太少,所以小编只能自己动手了,代码奉上,有需要的童鞋自取哦~

大致方案如下图所示:
(有些文字打错了,但请不要注意这些细节,图只是为了方便了解代码逻辑而已)
一、 Y轴计算
1、总数为奇数
在这里插入图片描述
2、总数为偶数
在这里插入图片描述

二、 X轴计算
1、总数为奇数

在这里插入图片描述
2、总数为偶数
在这里插入图片描述

组件代码如下:命名为sun.vue

<template>
  <div class="sun-container">
    <ul>
      <li
        v-for="(item, index) in actualSunData"
        :key="index"
        :style="sunStyle(item)"
        :class="isNoMore ? 'transition' : 'opacity'"
        @click="handleReceiveSun(item, index)"
      >
        <img :src="imgUrl" class="sun-img" :style="sunImg" ref="sunImg" />
        <p
          v-if="isShowCountDown && sunCountDownData[index]"
          class="surplus-time"
        >
          {{ sunCountDownData[index] }}<br />消失
        </p>
      </li>
    </ul>
  </div>
</template>
<script>
import sunImgUrl from "@/img/sun-img@2x.png";
export default {
  props: {
    //太阳的图片地址
    imgUrl: {
      type: String,
      default: sunImgUrl,
    },
    //太阳的样式
    sunImgStyle: {
      type: Object,
      default: function () {
        return {
          width: "60px",
          height: "60px",
        };
      },
    },
    //太阳总个数
    allSunCount: {
      type: Number,
      default: 0,
    },
    //太阳能显示的最大个数
    sunCountMax: {
      type: Number,
      default: 6,
    },
    //太阳附带的数据
    sunData: {
      type: Array,
      default: function () {
        return [];
      },
    },
    //是否显示倒计时
    isShowCountDown: {
      type: Boolean,
      default: true,
    },
    //Y轴基线坐标
    yBase: {
      type: Number,
      default: 0,
    },
    //Y轴太阳与太阳之间间隔
    ySpace: {
      type: Number,
      default: 0,
    },
    //X轴太阳与太阳之间间隔
    xSpace: {
      type: Number,
      default: 5,
    },
    //太阳动效到达X轴位置
    xMoveEnd: {
      type: Number,
      default: document.body.clientWidth / 2,
    },
    //太阳动效到达的Y轴位置
    yMoveEnd: {
      type: Number,
      default: 400,
    },
  },
  data() {
    return {
      //实际能显示的太阳个数
      sunCount: 0,
      //X轴基线坐标
      xBase: 0,
      //X轴太阳与太阳之间间隔
      xSpaceNum: 0,
      //Y轴太阳与太阳之间间隔
      ySpaceNum: 0,
      //记录被领取的太阳的X轴坐标
      oldX: 0,
      //记录被领取的太阳的Y轴坐标
      oldY: 0,
      //太阳动效到达X轴位置
      xMove: 0,
      //太阳动效到达Y轴位置
      yMove: 0,
      //当前页面body 默认字体大小,用于px转换rem
      fontSize: 0,
      //屏幕倍数
      clientMultiple: document.body.clientWidth / 375,
      //是否存在备用太阳数据
      isNoMore: true,
      //渲染的太阳数据
      actualSunData: [],
      //未渲染的太阳数据即备用数据
      moreSunData: [],
      //太阳图片实际样式
      sunImg: {},
      //太阳的半径
      sunR: 30,
      //倒计时
      countDownTimer: null,
      //倒计时数据
      sunCountDownData: [],
    };
  },
  created() {
    this.init();
  },
  watch: {
    sunData(newVal, oldVal) {
      console.log(newVal, oldVal);
      this.handleCountDownInterval();
    },
  },
  methods: {
    //初始化
    init() {
      this.initData();
      this.calcXBase();
      this.calcMoveCoordinate();
      this.calcSunImgStyle();
      this.initSun();
    },
    /**
     * 赋值
     */
    initData() {
      this.xSpaceNum = this.xSpace;
      this.ySpaceNum = this.ySpace;
    },
    /**
     * 领取太阳
     * @param {object} item 领取的太阳附带的数据
     * @param {number} index 领取的太阳的下标
     * 逻辑:
     * 1、领取的太阳运动至隐藏位置
     * 2、判断备用太阳数组是否存在数据
     * 3、暴露出领取太阳的操作
     */
    handleReceiveSun(item, index) {
      this.sunMove(item, index);
      setTimeout(() => {
        this.handleSupply(item, index);
      }, 2000);
      const data = {
        item,
        index,
      };
      this.$emit("handleReceiveSun", data);
    },
    /**
     * 初始化太阳
     * */
    initSun() {
      this.caleSunCount();
    },
    /**
     * 计算实际显示的太阳个数
     * */
    caleSunCount() {
      if (this.allSunCount > this.sunCountMax) {
        this.sunCount = this.sunCountMax;
      } else {
        this.sunCount = this.allSunCount;
      }
      this.createSun(this.sunCount);
    },
    /**
     * 创建太阳
     * @param {number} count 需要初始化的太阳个数
     * 将需要显示的太阳的数据存于actualSunData数组
     * 将备用的太阳数据存于moreSunData数组,以备领取太阳后进行补充
     */
    createSun(count) {
      let index = 0;
      while (index < count) {
        index++;
        let y = this.calcY(index);
        let x = this.calcX(index);
        this.actualSunData.push({
          ...this.sunData[index - 1],
          index,
          x,
          y: y / this.fontSize,
          opacity: 1,
        });
      }

      if (this.allSunCount > this.sunCountMax) {
        let otherIndex = 0;
        while (otherIndex < this.allSunCount - this.sunCountMax) {
          otherIndex++;
          this.moreSunData.push({
            ...this.sunData[this.allSunCount - otherIndex],
            opacity: 1,
          });
        }
      }
    },
    /*
      计算领取太阳时运动到的坐标
      */
    calcMoveCoordinate() {
      this.xMove = (this.xMoveEnd * this.clientMultiple) / this.fontSize;
      this.yMove = (this.yMoveEnd * this.clientMultiple) / this.fontSize;
    },
    /*
      计算X轴基线坐标
      */
    calcXBase() {
      const htmlFontSize = document.getElementsByTagName("html")[0].style
        .fontSize;
      this.fontSize = Number(htmlFontSize.replace("px", ""));
      this.xBase = document.body.clientWidth / 2;
    },
    /*
      计算不同屏幕宽度下,相同px值时根据倍数进行适配
      */
    calcMultiple() {
      this.sunR = this.sunR * this.clientMultiple;
      this.ySpaceNum = this.ySpaceNum * this.clientMultiple;
      this.xSpaceNum = this.xSpaceNum * this.clientMultiple;
    },
    /**
     * 计算传进来的太阳样式,将px转成rem
     * */
    calcSunImgStyle() {
      const sunStyle = {};
      for (var k in this.sunImgStyle) {
        if (this.sunImgStyle[k].indexOf("px") != -1) {
          sunStyle[k] = this.calcPxToRem(this.sunImgStyle[k]);
        } else {
          sunStyle[k] = this.sunImgStyle[k];
        }
        if (k == "width") {
          this.sunR = Number(this.sunImgStyle[k].replace("px", "")) / 2;
          this.sunR = this.sunR * this.clientMultiple;
        }
      }
      this.sunImg = sunStyle;
    },
    /** 
      px转rem
      * @param {string} str 带px的字符串
      * @return {string} px转成rem后的结果
      */
    calcPxToRem(str) {
      return (
        (Number(str.replace("px", "")) * this.clientMultiple) / this.fontSize +
        "rem"
      );
    },
    /** 
      判断实际显示太阳为奇数/偶数
       * @param {number} num 太阳总数
       * @return {string} 奇数/偶数
      */
    oddOrEven(num) {
      return num % 2 === 0 ? "even" : "odd";
    },
    /** 
      *计算哪几个下标的太阳在中间位置
      *@return {array} 在中间位置的太阳下标
      *公式:
      *总数为奇数时:Math.floor(总数/2)+(总数%2)
      *总数为偶数时:Math.floor(总数/2)
                  Math.floor(总数/2)+1
      */
    calcCenterSun() {
      if (this.oddOrEven(this.sunCount) == "odd") {
        //奇数
        return [(this.sunCount % 2) + Math.floor(this.sunCount / 2)];
      } else {
        //偶数
        return [
          Math.floor(this.sunCount / 2),
          Math.floor(this.sunCount / 2) + 1,
        ];
      }
    },
    /**
     * 计算X轴间隔
     * @return {number} X轴间隔
     *逻辑:
      1、获取当前设备的宽度
      2、计算去除当前所有太阳个数的宽度总和之后还剩余的空余宽度
      3、计算剩余的宽度计算每个太阳之间的间隔大小
      4、当间隔会出现负数或小于传进来的间隔大小时的情况时,使用传进来的间隔大小
      5、其他情况时,使用计算出来的间隔大小
     */
    calcXSpace() {
      const clientWidth = document.body.clientWidth;
      const surplusWidth = (
        clientWidth -
        this.sunCount * (this.sunR * 2)
      ).toFixed(0);
      const space =
        Math.ceil(surplusWidth / (this.sunCount - 1)) + this.xSpaceNum / 2;
      if (space < this.xSpaceNum) return this.xSpaceNum;
      return space;
    },
    /**
     * 计算太阳的Y轴坐标
     * @param {number} idx 太阳的下标
     * @return {number} 太阳的Y轴坐标(单位rem)
     * 公式
     *总数为奇数时:
     1、中间太阳的左边:Y轴基线 + (中间太阳的下标 - 当前太阳的下标) * (2*太阳半径 + Y轴太阳与太阳之间的间隔)
     2、中间太阳的右边:Y轴基线 + (当前太阳的下标 - 中间太阳的下标) * (2*太阳半径 + Y轴太阳与太阳之间的间隔)
     *总数为偶数时:
     1、左边中间太阳的左边:Y轴基线 + (左边中间太阳的下标 - 当前太阳的下标) * (2*太阳半径 + Y轴太阳与太阳之间的间隔)
     2、右边中间太阳的右边:Y轴基线 + (当前太阳的下标 - 右边中间太阳的下标) * (2*太阳半径 + Y轴太阳与太阳之间的间隔)

     公式中为2*太阳半径,代码中太阳半径未*2的原因:
     若是*2,则太阳排版呈锐角三角型、趋向于直角梯形的样式,故而减少太阳与太阳之间Y轴的间隔大小
     */
    calcY(idx) {
      const centerSunArr = this.calcCenterSun();
      let multiple = 0;
      if (centerSunArr.includes(idx)) {
        return this.yBase;
      }
      if (this.oddOrEven(this.sunCount) == "odd") {
        //奇数
        if (idx < centerSunArr[0]) {
          //中间的太阳的左边
          multiple = centerSunArr[0] - idx;
        } else {
          //中间的太阳的右边
          multiple = idx - centerSunArr[0];
        }
      } else {
        //偶数
        if (idx < centerSunArr[0]) {
          //中间的太阳的左边
          multiple = centerSunArr[0] - idx;
        }
        if (idx > centerSunArr[1]) {
          //中间的太阳的右边
          multiple = idx - centerSunArr[1];
        }
      }
      return this.yBase + multiple * (this.sunR + this.ySpaceNum);
    },
    /**
     * 计算太阳的X轴坐标
     * @param {number} idx 太阳的下标
     * @return {number} 太阳的X轴坐标(单位rem)
     * 公式
      *总数为奇数时:
     1、中间太阳:X轴基线 - 太阳的半径
     2、中间太阳的左边:中间太阳的坐标 - ((中间太阳的下标 - 当前太阳的下标) * (2*太阳半径 + X轴太阳与太阳之间的间隔))
     3、中间太阳的右边:中间太阳的坐标 + ((当前太阳的下标 - 中间太阳的下标) * (2*太阳半径 + X轴太阳与太阳之间的间隔))
     *总数为偶数时:
     1、左边中间太阳:X轴基线 - 太阳的半径*2 - X轴太阳与太阳之间的间隔/2
        右边中间太阳:X轴基线 + 太阳的半径*2
     2、左边中间太阳的左边:左边中间太阳 - ((中间太阳的下标 - 当前太阳的下标) * (2*太阳半径 + X轴太阳与太阳之间的间隔))
     3、右边中间太阳的右边:右边中间太阳 + (当前太阳的下标 - 右边中间太阳的下标) * (2*太阳半径 + Y轴太阳与太阳之间的间隔)

     总数为偶数时左边中间太阳的左边需要减去:2*太阳的半径+X轴间隔的原因:
     总数为偶数时,位于中间的太阳有两个,两个太阳会重合,需要将奇数下标的太阳移除至X轴基线的左部
     */
    calcX(idx) {
      const centerSunArr = this.calcCenterSun();
      const xSpaceNum = this.calcXSpace();
      let xCoordinate = 0;
      if (this.oddOrEven(this.sunCount) == "odd") {
        const cenSunX = this.xBase - this.sunR;
        //奇数
        if (centerSunArr.includes(idx)) {
          //如果为中间的太阳
          xCoordinate = cenSunX;
        }
        if (idx < centerSunArr[0]) {
          //中间的太阳的左边
          xCoordinate =
            cenSunX - (2 * this.sunR + xSpaceNum) * (centerSunArr[0] - idx);
        }
        if (idx > centerSunArr[0]) {
          //中间的太阳的右边
          xCoordinate =
            cenSunX + (2 * this.sunR + xSpaceNum) * (idx - centerSunArr[0]);
        }
      } else {
        //偶数
        if (centerSunArr.includes(idx)) {
          if (idx == centerSunArr[0]) {
            xCoordinate = this.xBase - 2 * this.sunR - xSpaceNum / 2;
          }
          if (idx == centerSunArr[1]) {
            xCoordinate = this.xBase + xSpaceNum / 2;
          }
        }
        if (idx < centerSunArr[0]) {
          //中间的太阳的左边
          xCoordinate =
            this.xBase -
            2 * this.sunR -
            xSpaceNum / 2 -
            (centerSunArr[0] - idx) * (2 * this.sunR + xSpaceNum);
        }
        if (idx > centerSunArr[1]) {
          //中间的太阳的右边
          xCoordinate =
            this.xBase +
            xSpaceNum / 2 +
            (idx - centerSunArr[1]) * (2 * this.sunR + xSpaceNum);
        }
      }
      return xCoordinate / this.fontSize;
    },
    /**
     * 太阳倒计时定时器
     */
    handleCountDownInterval() {
      setCountTimeData.call(this);
      clearInterval(this.countDownTimer);
      this.countDownTimer = setInterval(() => {
        setCountTimeData.call(this);
      }, 1000 * 60);
      /** 设置时间数据 */
      function setCountTimeData() {
        this.sunCountDownData = this.sunData.map(
          item => this.handleCountDown(item.deadline) || ""
        );
        this.countDownTimerFinish();
      }
    },
    /**
     * 太阳倒计时
     * @param {number} deadline 领取截止时间 毫秒制
     * @return {string} hh:mm
     * 逻辑:
     * 1、与当前时间相对比,计算出时间差
     * 2、如果该时间差不大于24小时,则显示倒计时,否则不显示
     */
    handleCountDown(deadline) {
      const nowTime = new Date().getTime() / 1000;
      let receiveS = Math.ceil(nowTime);
      let deadlineS = Math.ceil(deadline);
      // 领取时间差-单位秒
      let time = deadlineS - receiveS; //秒
      // 一天的秒数
      const showTime = 24 * 60 * 60;
      // 剩余小时数
      const restHour = time / 3600;
      var h =
        Math.floor(restHour) < 10
          ? "0" + Math.floor(restHour)
          : Math.floor(restHour);
      var m =
        Math.floor((time / 60) % 60) < 10
          ? "0" + Math.floor((time / 60) % 60)
          : Math.floor((time / 60) % 60);
      // 剩余时间不大于一天,才展示倒计时,否则只展示太阳
      if (time <= showTime) {
        return h + ":" + m;
      } else {
        return false;
      }
    },
    /**
     * 倒计时是否已经结束
     * 逻辑:
     * 若倒计时已结束,若存在备用数据则进行太阳补充,否则太阳消失
     */
    countDownTimerFinish() {
      this.sunCountDownData.map((item, index) => {
        if (item == "00:00") {
          //如果倒计时结束,该太阳消失,若还有多余的太阳数据,补充
          this.actualSunData[index].opacity = 0;
          this.isNoMore = true;
          if (this.moreSunData.length) {
            const sunItem = this.moreSunData.shift();
            sunItem.x = this.actualSunData[index].x;
            sunItem.y = this.actualSunData[index].y;
            sunItem.index = index;
            setTimeout(() => {
              this.isNoMore = false;
              this.$set(this.actualSunData, index, sunItem);
            }, 500);
          }
        }
      });
    },
    /**
     * 太阳的样式
     * @param {object} item 每个太阳的数据
     */
    sunStyle(item) {
      return {
        top: `${item.y}rem`,
        left: `${item.x}rem`,
        opacity: item.opacity,
      };
    },
    /**
     * 领取太阳时的动效
     * @param {object} item 领取的太阳附带的数据
     * @param {number} index 领取的太阳的下标
     * 逻辑:
     * 1、点击太阳进行领取时,记录点击该太阳的X、Y轴坐标数据
     * 2、将该太阳的X、Y轴坐标数据设置为需运动到的位置的X、Y轴坐标
     * 3、设置该太阳的透明度,进行隐藏
     * 4、重新设置该太阳的数据
     * 5、执行CSS3样式
     */
    sunMove(item, index) {
      this.oldX = item.x;
      this.oldY = item.y;
      item.x = this.xMove;
      item.y = this.yMove;
      item.opacity = 0;
      this.$set(this.actualSunData, index, item);
    },
    /**
     * 判断是否有备用太阳数据
     *  @param {object} item 领取的太阳附带的数据
     *  @param {number} index 领取的太阳的下标
     * 逻辑:
     * 1、判断是否有备用太阳数据
     * 2、如果存在备用太阳数据,则将备用太阳第一条数据的X、Y轴、下标设置为领取的太阳相对应数据
     * 3、将备用太阳第一条数据替换领取的太阳的数据,并删除备用太阳第一条数据
     * 4、否则则没有备用数据,太阳消失,不进行补充
     * 定时器作用:
     * 将isNoMore重新设置,以致达到修改样式的目的
     * 原因:若不重置,领取太阳后,若有补充的太阳,补充的太阳会从隐藏的位置出发,到达指定的排布的位置
     * 但我们要的是直接修改透明度而已,即直接在原位置显示
     */
    handleSupply(item, index) {
      if (this.moreSunData.length) {
        this.isNoMore = false;
        this.moreSunData[0].x = this.oldX;
        this.moreSunData[0].y = this.oldY;
        this.moreSunData[0].index = item.index;
        this.$set(this.actualSunData, index, this.moreSunData.shift());
      }
      setTimeout(() => {
        this.isNoMore = true;
      }, 600);
    },
  },
};
</script>
<style lang="scss" scoped>
.sun-container {
  width: 100%;
  height: 100vh;
  position: relative;
  background: red;
  overflow: hidden;
  ul {
    width: 100%;
    height: 100%;
    li {
      position: absolute;
      animation: wave 2.5s infinite ease-in-out;
      img {
        width: 60px;
        height: 60px;
      }
      .surplus-time {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -60%);
        text-align: center;
        font-size: 10px;
        font-weight: bold;
        color: #c12400;
      }
    }
    @keyframes wave {
      0% {
        transform: translateY(-4px);
      }
      50% {
        transform: translateY(4px);
      }
      100% {
        transform: translateY(-4px);
      }
    }
    .transition {
      transition: all 0.5s;
    }
    .opacity {
      transition: opacity 0.5s;
    }
  }
}
</style>

使用:index.vue

<template>
  <div>
    <sunCreateCom
      :allSunCount="allSunCount"
      :sunData="sunData"
      @handleReceiveSun="handleReceiveSun"
    />
  </div>
</template>
<script>
import sunCreateCom from "./sun"
export default {
  components: {
    sunCreateCom,
  },
  data() {
    return {
      //太阳的图片地址
      imgUrl: "",
      //太阳的样式 基础设计稿为w:375px
      sunImgStyle: {
        width: "60px",
        height: "60px",
      },
      //是否显示倒计时
      isShowCountDown: true,
      allSunCount: 6, //太阳总个数
      sunCountMax: 6, //太阳能显示的最大个数
      sunData: [], //太阳附带的数据
      yBase: 0, //Y轴基线坐标 单位:px
      ySpace: 0, //Y轴太阳与太阳之间间隔 单位:px
      xSpace: 10, //X轴太阳与太阳之间间隔 单位:px
      xMoveEnd: document.body.clientWidth / 2, //太阳动效到达的x轴位置  单位:px
      yMoveEnd: 400, //太阳动效到达的y轴位置  单位:px
    };
  },
  methods: {
    /**
     * 领取太阳
     * @param {object} data 领取的太阳的数据
     * @param {object} data.item 领取的太阳附带的数据
     * @param {number} data.index 领取的太阳的下标
     */
    handleReceiveSun(data) {
      //TODO:例如发请求等
      console.log(data, "=>data");
    },
  },
};
</script>
<style lang="scss" scoped>
</style>

可选参数:
在这里插入图片描述

方法:
在这里插入图片描述

弊端:

  1. 太阳能显示的最大个数需要自己根据太阳图片实际尺寸进行计算传入
  2. 领取太阳是太阳的动效以及太阳补充的动效固定

有兴趣的童鞋可以试试更多的扩展和优化


http://www.niftyadmin.cn/n/1202685.html

相关文章

深度解析RocketMQ消息发送的高可用设计

2019独角兽企业重金招聘Python工程师标准>>> 微信公众号「后端进阶」&#xff0c;专注后端技术分享&#xff1a;Java、Golang、WEB框架、分布式中间件、服务治理等等。 老司机倾囊相授&#xff0c;带你一路进阶&#xff0c;来不及解释了快上车&#xff01; 从rocket…

Vue 长按文本复制事件实现

longpress.js export default {install(Vue, options {time: 2000}) {Vue.directive(longpress, {bind: function (el, binding, vNode) {// 确保提供的表达式是函数 if (typeof binding.value ! function) {// 获取组件名称 const compName vNode.contex…

struts 2读书笔记-----Convention插件与“约定”支持

Convention插件的主要特点是“约定优于配置”。 Action的搜索和映射约定 为了能够使用Convention插件&#xff0c;必须在Struts 2 应用中安装Convention插件&#xff0c;即将Struts2-convention-plugin.jar文件复制到Struts 2应用的WEB-INF/lib路径下。 对于convention插件而言…

发送短信验证码-node+阿里云短信

一、准备工作 前端&#xff1a; 表单 提交方式--- get 、post 整体提交 ajax提交 表单验证 正则表达式---不轻易自己写正则&#xff0c;不是不写&#xff0c;一定要考虑好兼容性&#xff08;全面性&#xff09;---- 提示信息的选择性 图形验证码 后端进行提供的一张图片&…

微信小程序新分享功能

原分享api&#xff1a; //1、分享到朋友圈 wx.onMenuShareTimeline({title: , // 分享标题link: , // 分享链接&#xff0c;该链接域名或路径必须与当前页面对应的公众号JS安全域名一致imgUrl: , // 分享图标success: function () {// 用户点击了分享后执行的回调函数} },//2、…

Struts 2读书笔记-----通配符的使用

在我们配置Action时&#xff0c;发现struts.xml中的<action…/>的绝大部分都相同。这时我们可以通过使用struts 2 提供的通配符映射机制来处理这个问题。 我们在配置<action …./>时&#xff0c;允许在指定name属性时使用模式字符串&#xff08;即用“*”来代替一个…

补硒真的能防癌吗?

阅读更多系列文章请访问我的GitHub博客 写作原因 一天早上吃早餐的时候&#xff0c;妈妈跟我说今天早餐中有放一些富硒小麦胚芽&#xff0c;说是多补充硒&#xff0c;可以防癌。 我当时觉得这事不靠谱&#xff0c;就回了一句“根据我的了解&#xff0c;这事多半不靠谱”。 可妈…

图片压缩

插件对比&#xff1a; 一、compressorjs 1、使用方式 npm i compressorjs --save引入&#xff1a;import Compressor from ‘compressorjs’;使用&#xff1a; 2、压缩结果 &#xff08;图片原始大小为9.8M&#xff09;: &#xff08;图片原始大小为4.4M&#xff09;: 由…