国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

圖形編輯器開發(fā):參考線吸附功能,讓圖形自動對齊

這篇具有很好參考價值的文章主要介紹了圖形編輯器開發(fā):參考線吸附功能,讓圖形自動對齊。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

最近我給圖形編輯器增加了參照線吸附功能,講講我的實(shí)現(xiàn)思路。

我正在開發(fā)的圖形設(shè)計(jì)工具:

https://github.com/F-star/suika

線上體驗(yàn):

https://blog.fstars.wang/app/suika/

效果是被移動的圖形會參考周圍圖形,自動與它們進(jìn)行吸附對齊。

不得不說,很酷炫。

感覺這個圖形編輯器突然變得靈動起來,有了靈魂一般。
圖形編輯器開發(fā):參考線吸附功能,讓圖形自動對齊,編輯器

為什么需要參照線吸附功能?

這里的參照線,指的是在移動目標(biāo)圖形時,當(dāng)靠近其他圖形的包圍盒的延長線(看不見)時,會(1)繪制出最近的延長線和延長線上的點(diǎn),(2)并將目標(biāo)圖形吸附上去,輕松實(shí)現(xiàn)(3)對齊的效果。

圖形編輯器開發(fā):參考線吸附功能,讓圖形自動對齊,編輯器

可以看到,通過參照線,我們很容易就能實(shí)現(xiàn)各種對齊,比如兩圖形的底邊和定邊對齊、右下角和左上角對齊。

這在 以對齊為基本要素 的視覺設(shè)計(jì)中,是非常好用的功能。

圖形編輯器開發(fā):參考線吸附功能,讓圖形自動對齊,編輯器

整體思路

整體思路為:

  1. 記錄參照線;
  2. 找出目標(biāo)圖形最靠近的水平參照線和垂直參照線;
  3. 計(jì)算出偏移值 offsetX、offsetY;
  4. 標(biāo)記要繪制的所有參照線段(不是兩端無限延長的);
  5. 修正圖形的 x、y;
  6. 繪制參照線和點(diǎn)。

記錄參照線

首先是確定能夠作為 “參照” 的參照圖形。

通常來說,參照圖形為視口內(nèi)的圖形,并排除掉被移動的目標(biāo)圖形。視口外的圖形通常都不在設(shè)計(jì)師的關(guān)注區(qū)域內(nèi)。

確認(rèn)好參照圖形后,計(jì)算出它們的包圍盒(bbox)。

這次的包圍盒有點(diǎn)特殊,要多給一個中點(diǎn)坐標(biāo),因?yàn)橹芯€也要作為參照線。

接口簽名為:

export interface IBoxWithMid {
  minX: number;
  minY: number;
  midX: number;
  midY: number;
  maxX: number;
  maxY: number;
}

它們組成了參照圖形的 8 個點(diǎn),沿著這些點(diǎn)繪制豎線和橫線,就是被移動的目標(biāo)圖形對應(yīng)要吸附的參照線。

圖形編輯器開發(fā):參考線吸附功能,讓圖形自動對齊,編輯器

被移動的圖形也要計(jì)算包圍盒,并得到 5 個點(diǎn)?;谶@些點(diǎn)的產(chǎn)生的水平線和垂直線,在靠近參照線時會吸附到最近的參照線上,分為水平移動和垂直移動兩個維度。

圖形編輯器開發(fā):參考線吸附功能,讓圖形自動對齊,編輯器

編輯器上的效果:

圖形編輯器開發(fā):參考線吸附功能,讓圖形自動對齊,編輯器

我們首先要把所有的參照線記錄下來,在圖形準(zhǔn)備移動(mousedown)的時候。大致有以下這幾個操作:

  1. 遍歷參照圖形(在視口內(nèi),且不為被移動目標(biāo)圖形);
  2. 計(jì)算出它們的包圍盒,得到 8 個點(diǎn),3 條垂直線和 3 條水平線。在一條垂直線上的多個點(diǎn),其 x 值是相同的,y 不同,我們 x 作為 key,y 的數(shù)組為 value,保存到 hLineMap 映射對象中。每一項(xiàng)代表一條垂直線;
  3. 水平線同理,保存在 vLineMap 中。
  4. 然后對這兩個 map 的 key 保存到 sortedXs 或 sortedYs 數(shù)組中,并排序,方便之后二分查找提高查找效率。

抽象一個 RefLine(參照線)類。

interface IVerticalLine { // 有多個端點(diǎn)的垂直線
  x: number;
  ys: number[];
}

interface IHorizontalLine { // 有多個端點(diǎn)的水平線
  y: number;
  xs: number[];
}


class RefLine {
  // 參照圖形產(chǎn)生的垂直參照線,y 相同(作為 key),x 值不同(作為 value)
  private hLineMap = new Map<number, number[]>();
  // 參照圖形產(chǎn)生的水平照線,x 相同(作為 key),y 值不同(作為 value)
  private vLineMap = new Map<number, number[]>(); 

  // 對 hLineMap 的 key 排序,方便高效二分查找,找到最近的線
  private sortedXs: number[] = []; 
  // 對 vLineMap 的 key 排序
  private sortedYs: number[] = []; 

  private toDrawVLines: IVerticalLine[] = []; // 等待繪制的垂直參照線
  private toDrawHLines: IHorizontalLine[] = []; // 等待繪制的水平參照線

  constructor(private editor: Editor) {}

  cacheXYToBbox() {
    this.clear();

    const hLineMap = this.hLineMap;
    const vLineMap = this.vLineMap;

    const selectIdSet = this.editor.selectedElements.getIdSet();
    const viewportBbox = this.editor.viewportManager.getBbox2();
    for (const graph of this.editor.sceneGraph.children) {
      // 排除掉被移動的圖形
      if (selectIdSet.has(graph.id)) {
        continue;
      }

      const bbox = bboxToBboxWithMid(graph.getBBox2());
      // 排除在視口外的圖形
      if (!isRectIntersect2(viewportBbox, bbox)) {
        continue;
      }
			
      // 將參照圖形記錄下來
   
      // 這里是水平線,特點(diǎn)是 x 相同。
      this.addBboxToMap(hLineMap, bbox.minX, [bbox.minY, bbox.maxY]);
      this.addBboxToMap(hLineMap, bbox.midX, [bbox.minY, bbox.maxY]);
      this.addBboxToMap(hLineMap, bbox.maxX, [bbox.minY, bbox.maxY]);

      this.addBboxToMap(vLineMap, bbox.minY, [bbox.minX, bbox.maxX]);
      this.addBboxToMap(vLineMap, bbox.midY, [bbox.minX, bbox.maxX]);
      this.addBboxToMap(vLineMap, bbox.maxY, [bbox.minX, bbox.maxX]);
    }

    this.sortedXs = Array.from(hLineMap.keys()).sort((a, b) => a - b);
    this.sortedYs = Array.from(vLineMap.keys()).sort((a, b) => a - b);
  }
  
  private addBboxToMap(
    m: Map<number, number[]>,
    xOrY: number,
    xsOrYs: number[],
  ) {
    const line = m.get(xOrY);
    if (line) {
      line.push(...xsOrYs);
    } else {
      m.set(xOrY, [...xsOrYs]);
    }
  }
  
  // ...
}

找出最近參照線

然后是找出目標(biāo)圖形最靠近的水平參照線和垂直參照線。

這一步是在圖形移動(mousemove)時做的,是動態(tài)變化的。

首先我們分別找到目標(biāo)圖形的 minX、midX、maxX 的最近垂直參照線。

然后計(jì)算出它們各自的絕對距離。

最后找出這里面最小的一個。

class RefLinet {
  updateRefLine(_targetBbox: IBox2): {
    offsetX: number;
    offsetY: number;
  } {
    // 重置
    this.toDrawVLines = [];
    this.toDrawHLines = [];
    
    // 目標(biāo)對象的包圍盒,這里補(bǔ)上 midX,midY
    const targetBbox = bboxToBboxWithMid(_targetBbox);

    const hLineMap = this.hLineMap;
    const vLineMap = this.vLineMap;
    const sortedXs = this.sortedXs;
    const sortedYs = this.sortedYs;

    // 一個參照圖形都沒有,結(jié)束
    if (sortedXs.length === 0 && sortedYs.length === 0) {
      return { offsetX: 0, offsetY: 0 };
    }

    // 如果 offsetX 到最后還是 undefined,說明沒有找到最靠近的垂直參照線
    let offsetX: number | undefined = undefined;
    let offsetY: number | undefined = undefined;

    // 分別找到目標(biāo)圖形的 minX、midX、maxX 的最近垂直參照線
    const closestMinX = getClosestValInSortedArr(sortedXs, targetBbox.minX);
    const closestMidX = getClosestValInSortedArr(sortedXs, targetBbox.midX);
    const closestMaxX = getClosestValInSortedArr(sortedXs, targetBbox.maxX);

    // 分別計(jì)算出距離
    const distMinX = Math.abs(closestMinX - targetBbox.minX);
    const distMidX = Math.abs(closestMidX - targetBbox.midX);
    const distMaxX = Math.abs(closestMaxX - targetBbox.maxX);

    // 找到最近距離
    const closestXDist = Math.min(distMinX, distMidX, distMaxX);
    
    // y 同理
  }
}

這里有一個比較重要的算法,就是找出排序數(shù)組中,離目標(biāo)值最近的數(shù)組元素。

該算法二分查找的變體,雖然原理不復(fù)雜,但一次能寫對,很難。這里我是找 gpt 幫我寫的,非常完美。

實(shí)現(xiàn)如下:

const getClosestValInSortedArr = (
  sortedArr: number[],
  target: number,
) => {
  if (sortedArr.length === 0) {
    throw new Error('sortedArr can not be empty');
  }
  if (sortedArr.length === 1) {
    return sortedArr[0];
  }

  let left = 0;
  let right = sortedArr.length - 1;

  while (left <= right) {
    const mid = Math.floor((left + right) / 2);

    if (sortedArr[mid] === target) {
      return sortedArr[mid];
    } else if (sortedArr[mid] < target) {
      left = mid + 1;
    } else {
      right = mid - 1;
    }
  }

  // check if left or right is out of bound
  if (left >= sortedArr.length) {
    return sortedArr[right];
  }
  if (right < 0) {
    return sortedArr[left];
  }

  // check which one is closer
  return Math.abs(sortedArr[right] - target) <=
    Math.abs(sortedArr[left] - target)
    ? sortedArr[right]
    : sortedArr[left];
};

計(jì)算偏移值

前面我們得到了最小距離 closestXDist。

接著我們要判斷其是否小于一個特定的臨界值 tol。不可能你離著十米開外,移動一下就千里迢迢吸附過來了吧。

如果滿足,我們繼續(xù)。

offsetX 就差一步了,我們需要確定正負(fù),因?yàn)?closestXDist 是一個絕對值。

那我們就拿這個最小距離和之前計(jì)算出的三個距離 distMinX、distMidX、distMaxX對比,找到相等的,就能計(jì)算出 offsetX 了。

const isEqualNum = (a: number, b: number) => Math.abs(a - b) < 0.00001;
    
const tol = 5 / zoom; // 最小距離不能超過這個

// 確認(rèn)偏移值 offsetX
if (closestXDist <= tol) {
  // 這里考慮了一下浮點(diǎn)數(shù)誤差
  if (isEqualNum(closestXDist, distMinX)) {
    offsetX = closestMinX - targetBbox.minX;
  } else if (isEqualNum(closestXDist, distMidX)) {
    offsetX = closestMidX - targetBbox.midX;
  } else if (isEqualNum(closestXDist, distMaxX)) {
    offsetX = closestMaxX - targetBbox.maxX;
  } else {
    throw new Error('it should not reach here, please put a issue to us');
  }
}

offsetY 同理,不贅述。

標(biāo)記需繪制參照線段

計(jì)算出了 offsetX 和 offsetY。

接下來要修正一下我們的 targetBbox。

const correctedTargetBbox = { ...targetBbox };
if (offsetX !== undefined) {
  correctedTargetBbox.minX += offsetX;
  correctedTargetBbox.midX += offsetX;
  correctedTargetBbox.maxX += offsetX;
}
if (offsetY !== undefined) {
  correctedTargetBbox.minY += offsetY;
  correctedTargetBbox.midY += offsetY;
  correctedTargetBbox.maxY += offsetY;
}

修正后的目標(biāo)圖形,它的邊就和一些參照線發(fā)生了對齊。

對齊的參照線,可能一條沒有,可能只有一條,也可能有最多的 6 條

基于新的目標(biāo)圖形,我們來找它落在的參照線有哪些。

// offsetX 不為 undefined,說明落在了臨界值內(nèi)
if (offsetX !== undefined) {
  /*************** 左垂直的參考線 ************/
  // 對比 “offset” 和 “離 minX 最近的垂直線到 minX 的距離(不是絕對值)”
  if (isEqualNum(offsetX, closestMinX - targetBbox.minX)) {
    // 創(chuàng)建一個垂直線對象(特點(diǎn)是這些點(diǎn)的 x 相同)
    const vLine: IVerticalLine = {
      x: closestMinX,
      ys: [],
    };

    // 修正后的目標(biāo)圖形的對應(yīng)點(diǎn)。
    vLine.ys.push(correctedTargetBbox.minY);
    vLine.ys.push(correctedTargetBbox.maxY);
    // 參照圖形上的點(diǎn)
    vLine.ys.push(...hLineMap.get(closestMinX)!);

    // 添加到 “待繪制垂線集合”
    this.toDrawVLines.push(vLine);
  }
  /*************** 中間垂直的參考線 ************/
  if (isEqualNum(offsetX, closestMidX - targetBbox.midX)
  ) {
    const vLine: IVerticalLine = {
      x: closestMidX,
      ys: [],
    };

    vLine.ys.push(correctedTargetBbox.midY);
    vLine.ys.push(...hLineMap.get(closestMidX)!);

    this.toDrawVLines.push(vLine);
  }
  /*************** 右垂直的參考線 ************/
  // ...
}

// 水平線同理
if (offsetY !== undefined) {
  /*************** 上水平的參考線 ************/
  /*************** 中間水平的參考線 ************/
  /*************** 下水平的參考線 ************/
}

修正圖形的 x、y

計(jì)算出的 offsetX 和 offsetY,記得拿去修正被移動目標(biāo)圖形的 x 和 y。

const onMousemove = (e) => {
  // ...

  const { offsetX, offsetY } = this.editor.refLine.updateRefLine(
    bboxToBbox2(this.editor.selectedElements.getBBox()!),
  );

  // 修正
  for (let i = 0, len = selectedElements.length; i < len; i++) {
    selectedElements[i].x = startPoints[i].x + dx + offsetX;
    selectedElements[i].y = startPoints[i].y + dy + offsetY;
  }
}

繪制參照線和點(diǎn)

最后是繪制參照線,以繪制垂直線為例。

for (const vLine of this.toDrawVLines) {
  let minY = Infinity;
  let maxY = -Infinity;

  // 這個是世界坐標(biāo)系轉(zhuǎn)視口坐標(biāo)系
  const { x } = this.editor.sceneCoordsToViewport(vLine.x, 0);
  
  // 遍歷繪制點(diǎn)
  for (const y_ of vLine.ys) {
    // TODO: optimize
    const { y } = this.editor.sceneCoordsToViewport(0, y_);
    minY = Math.min(minY, y);
    maxY = Math.max(maxY, y);

    // 可能有重復(fù)的點(diǎn),用備忘錄排除掉
    const key = `${x},${y}`;
    if (pointsSet.has(key)) {
      continue;
    }
    pointsSet.add(key);

    // 繪制點(diǎn)
    drawXShape(ctx, x, y, pointSize);
  }

  // 所有點(diǎn)中的 minY 和 maxY,繪制線段
  drawLine(ctx, x, minY, x, maxY);
}

圖形編輯器開發(fā):參考線吸附功能,讓圖形自動對齊,編輯器

水平線同理。

優(yōu)化點(diǎn)

  1. 這里的實(shí)現(xiàn),在圖形有旋轉(zhuǎn)角度的時候,參照線會過多顯得冗余,可以精簡一些,減少要對比的參照線;
  2. 對齊到像素網(wǎng)格的時候,包圍盒的值要取整;
  3. 考慮和按住 Shift 固定 x 或 y 平移的情況。

最后

總結(jié)一下,參考線吸附的實(shí)現(xiàn),就是找出最近的垂直線和水平線,計(jì)算出 offsetX 和 offsetY,修正被移動圖形的 x 和 y,并記錄并繪制出最終重合的參考線。

我是前端西瓜哥,歡迎關(guān)注我,學(xué)習(xí)更多圖形編輯器知識。文章來源地址http://www.zghlxwxcb.cn/news/detail-549761.html

到了這里,關(guān)于圖形編輯器開發(fā):參考線吸附功能,讓圖形自動對齊的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 圖形編輯器開發(fā):是否要像 Figma 一樣上 wasm

    圖形編輯器開發(fā):是否要像 Figma 一樣上 wasm

    大家好,我是前端西瓜哥。 wasm 拿來做 Web 端的圖形編輯器貌似是不錯的選擇。 因?yàn)閳D形處理會有相當(dāng)多無法利用到 WebGL GPU 加速的 CPU 密集的計(jì)算。比如對一條復(fù)雜貝塞爾曲線進(jìn)行三角化,對多個圖形進(jìn)行復(fù)雜圖形的布爾運(yùn)算。 圖形編輯器性能天花板 Figma 用了 wasm,我們也

    2024年02月15日
    瀏覽(17)
  • 圖形編輯器開發(fā):最基礎(chǔ)但卻復(fù)雜的選擇工具

    圖形編輯器開發(fā):最基礎(chǔ)但卻復(fù)雜的選擇工具

    大家好,我是前端西瓜哥。 對于一個圖形設(shè)計(jì)軟件,它最基礎(chǔ)的工具是什么? 選擇工具 。 但這個選擇工具,卻是相當(dāng)?shù)膹?fù)雜。這次我來和各位,細(xì)說細(xì)說選擇工具的一些彎彎道道。 我正在開發(fā)的圖形設(shè)計(jì)工具的: https://github.com/F-star/suika 線上體驗(yàn): https://blog.fstars.wang/ap

    2024年02月09日
    瀏覽(19)
  • web架構(gòu)師編輯器內(nèi)容-編輯器組件圖層面板功能開發(fā)-鎖定隱藏、鍵盤事件功能的開發(fā)

    web架構(gòu)師編輯器內(nèi)容-編輯器組件圖層面板功能開發(fā)-鎖定隱藏、鍵盤事件功能的開發(fā)

    我們這一部分主要是對最右側(cè)圖層面板功能進(jìn)行剖析,完成對應(yīng)的功能的開發(fā): 每個圖層都對應(yīng)編輯器上面的元素,有多少個元素就對應(yīng)多少個圖層,主要的功能如下: 鎖定功能:點(diǎn)擊鎖定,在編輯器中沒法編輯對應(yīng)的組件屬性,再次點(diǎn)擊是取消鎖定,恢復(fù)到可編輯的模式

    2024年01月18日
    瀏覽(19)
  • web架構(gòu)師編輯器內(nèi)容-圖層拖動排序功能的開發(fā)

    web架構(gòu)師編輯器內(nèi)容-圖層拖動排序功能的開發(fā)

    新的學(xué)習(xí)方法 用手寫簡單方法實(shí)現(xiàn)一個功能 然后用比較成熟的第三方解決方案 即能學(xué)習(xí)原理又能學(xué)習(xí)第三方庫的使用 從兩個DEMO開始 Vue Draggable Next: Vue Draggable Next React Sortable HOC: React Sortable HOC 列表排序的三個階段 拖動開始(dragstart) 被拖動圖層的狀態(tài)變化 會出一個浮層

    2024年01月25日
    瀏覽(15)
  • 【W(wǎng)eb開發(fā)指南】MyEclipse XML編輯器的高級功能簡介

    【W(wǎng)eb開發(fā)指南】MyEclipse XML編輯器的高級功能簡介

    MyEclipse v2023.1.2離線版下載 1. 在MyEclipse中編輯XML 本文檔介紹MyEclipse?XML編輯器中的一些可用的函數(shù),MyEclipse?XML編輯器包括高級XML編輯,例如: 語法高亮顯示 標(biāo)簽和屬性內(nèi)容輔助 實(shí)時驗(yàn)證(當(dāng)您輸入時) 文檔內(nèi)容的源(Source)視圖、設(shè)計(jì)(Design)視圖和大綱(Outline)視圖 文檔

    2024年02月12日
    瀏覽(14)
  • 虛幻引擎架構(gòu)自動化及藍(lán)圖編輯器高級開發(fā)進(jìn)修班

    虛幻引擎架構(gòu)自動化及藍(lán)圖編輯器高級開發(fā)進(jìn)修班

    課程名稱:虛幻引擎架構(gòu)自動化及藍(lán)圖編輯器高級開發(fā)進(jìn)修班 課程介紹 大家好 我們即將推出一套課程 自動化系統(tǒng)開發(fā)。 自動化技術(shù)在項(xiàng)目開發(fā)的前中后期都大量運(yùn)用。如何您是一家游戲公司,做的是網(wǎng)絡(luò)游戲,是不是經(jīng)常會遇到程序員打包加部署需要半天時間,測試demo功

    2024年04月11日
    瀏覽(18)
  • 圖形編輯器:歷史記錄設(shè)計(jì)

    圖形編輯器:歷史記錄設(shè)計(jì)

    大家好,我是前端西瓜哥。今天講一下圖形編輯器如何實(shí)現(xiàn)歷史記錄,做到撤銷重做。 其實(shí)就是版本號的更替。每個版本保存一個狀態(tài)。 要記錄圖形編輯器的歷史記錄,支持撤銷重做功能,需要兩個棧: 撤銷(undo)棧和重做(redo)棧 。 每當(dāng)用戶進(jìn)行一個操作(比如移動一

    2024年02月01日
    瀏覽(30)
  • 免費(fèi)全功能視頻編輯器分享

    免費(fèi)全功能視頻編輯器分享

    最基本的視頻剪輯功能,文字、音頻、畫中畫、變速、濾鏡、貼紙、AI字幕,各種字體、特效都可以免費(fèi)使用 還有許多實(shí)用功能,AI作圖,可以通過輸入咒語,來生成一些圖片,作為視頻配圖使用 背景移除,可以一鍵移除背景,保留主體 。內(nèi)置了提詞器功能。安卓電腦都可以

    2024年01月22日
    瀏覽(20)
  • 電力布局三維編輯器功能設(shè)計(jì)

    電力布局三維編輯器功能設(shè)計(jì)

    最近和一家公司在談一個項(xiàng)目合作,他們公司主要是做電力相關(guān)的。 項(xiàng)目背景大概是這樣的: 國家電網(wǎng)對電網(wǎng)資產(chǎn)需要做到數(shù)字化管理,對現(xiàn)有變壓器臺區(qū)內(nèi)的電表箱電能表做可視化數(shù)字孿生管理。 由于涉及到的臺區(qū)非常多,所以客戶希望開發(fā)的不是單個項(xiàng)目,而是可以實(shí)現(xiàn)

    2023年04月22日
    瀏覽(14)
  • 企升編輯器核心功能介紹

    企升編輯器核心功能介紹

    核心功能介紹(企升編輯器)“排版助手”核心功能介紹(企升編輯器) 下載安裝說明: 如何安裝企升編輯器?“排版助手”如何安裝企升編輯器? 一、支持目錄生成選擇標(biāo)題級別 PB2023的目錄操作有以下特點(diǎn)。一是清晰地區(qū)分出“默認(rèn)目錄內(nèi)容”和“可選目錄內(nèi)容”,界面

    2024年02月11日
    瀏覽(22)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包