JS 實現滑鼠框選(頁面選擇)時返回對應的代碼或文本內容

来源:https://www.cnblogs.com/risheng/p/18207069
-Advertisement-
Play Games

當用戶進行滑鼠框選選擇了頁面上的內容時,把選擇的內容進行上報。 分為以下幾點: 選擇文案時 選擇圖片、svg、iframe、video、audio 等標簽時 選擇 input、select、textarea 等標簽時 選擇input、textarea 標簽內容時 選擇類似   字元時 鍵盤全選時 鼠... ...


JS 實現滑鼠框選(頁面選擇)時返回對應的代碼或文案內容

一、需求背景

1、項目需求

當用戶進行滑鼠框選選擇了頁面上的內容時,把選擇的內容進行上報。

2、需求解析

雖然這需求就一句話的事,但是很顯然,沒那麼簡單...

因為滑鼠框選說起來簡單,就是選擇的內容,但是這包含很多中情況,比如:只選擇文案、選擇圖片、選擇輸入框、輸入框中的內容選擇、iframe、等。

簡單總結,分為以下幾點:

  1. 選擇文案時

  2. 選擇圖片、svgiframevideoaudio 等標簽時

  3. 選擇 inputselecttextarea 等標簽時

  4. 選擇 inputtextarea 標簽內容時

  5. 選擇類似   字元時

  6. 鍵盤全選時

  7. 滑鼠右鍵選擇

  8. 以上各模塊結合時

  9. 當包含標簽的時候,返回 html 結構,只有文本時返迴文本內容

二、技術要點

滑鼠框選包含以下幾點:

  1. debounce 防抖

  2. addEventListener 事件監聽

  3. Range 對象

  4. Selection 對象

1、debounce

老生常談的技術點了,這裡不能用節流,因為肯定不能你滑鼠選擇的時候,隔一段時間返回一段內容,肯定是選擇之後一起返回。

這裡用 debounce 主要也是用在事件監聽和事件處理上。

2、addEventListener

事件監聽,因為滑鼠選擇,不僅僅是滑鼠按下到滑鼠抬起,還包括雙擊、右鍵、全選。

需要使用事件監聽對事件作處理。

3、Range

Range 介面表示一個包含節點與文本節點的一部分的文檔片段。

Range 是瀏覽器原生的對象。

image

3.1. 創建 Range 實例,並設置起始位置

<body>
  <ul>
    <li>Vite</li>
    <li>Vue</li>
    <li>React</li>
    <li>VitePress</li>
    <li>NaiveUI</li>
  </ul>
</body>
<script>
  // 創建 Range 對象
  const range = new Range()
  const liDoms = document.querySelectorAll("li");
  // Range 起始位置在 li 2
  range.setStartBefore(liDoms[1]);
  // Range 結束位置在 li 3
  range.setEndAfter(liDoms[2]);
  // 獲取 selection 對象
  const selection = window.getSelection();
  // 添加游標選擇的範圍
  selection.addRange(range);
</script>

image

可以看到,選擇內容為第二行和第三行

3.1.1 瀏覽器相容情況

image

3.2. Range 屬性

  1. startContainer:起始節點。

  2. startOffset:起始節點偏移量。

  3. endContainer:結束節點。

  4. endOffset:結束節點偏移量。

  5. collapsed:範圍的開始和結束是否為同一點。

  6. commonAncestorContainer:返回完整包含 startContainerendContainer 的最深一級的節點。

3.2.1. 用我們上面創建的實例來看下 range 屬性的值

image

3.2.2. 如果我們只選擇文本內容時

只選擇 li 中的 itePres

image

可以看出 range 屬性對應的值

image

3.3. Range 方法

  1. cloneContents():複製範圍內容,並將複製的內容作為 DocumentFragment 返回。

  2. cloneRange():創建一個具有相同起點/終點的新範圍, 非引用,可以隨意改變,不會影響另一方。

  3. collapse(toStart):如果 toStart=true 則設置 end=start,否則設置 start=end,從而摺疊範圍。

  4. compareBoundaryPoints(how, sourceRange):兩個範圍邊界點進行比較,返回一個數字 -1、0、1。

  5. comparePoint(referenceNode, offset):返回-1、0、1具體取決於 是 referenceNode 在 之前、相同還是之後。

  6. createContextualFragment(tagString):返回一個 DocumentFragment

  7. deleteContents():刪除框選的內容。

  8. extractContents():從文檔中刪除範圍內容,並將刪除的內容作為 DocumentFragment 返回。

  9. getBoundingClientRect():和 dom 一樣,返回 DOMRect 對象。

  10. getClientRects():返回可迭代的對象序列 DOMRect

  11. insertNode(node):在範圍的起始處將 node 插入文檔。

  12. intersectsNode(referenceNode):判斷與給定的 node 是否相交。

  13. selectNode(node):設置範圍以選擇整個 node

  14. selectNodeContents(node):設置範圍以選擇整個 node 的內容。

  15. setStart(startNode, startOffset):設置起點。

  16. setEnd(endNode, endOffset):設置終點。

  17. setStartBefore(node):將起點設置在 node 前面。

  18. setStartAfter(node):將起點設置在 node 後面。

  19. setEndBefore(node):將終點設置為 node 前面。

  20. setEndAfter(node):將終點設置為 node 後面。

  21. surroundContents(node):使用 node 將所選範圍內容包裹起來。

3.4. 創建 Range 的方法

3.4.1. Document.createRange
const range = document.createRange();
3.4.2. Selection 的 getRangeAt() 方法
const range = window.getSelection().getRangeAt(0)
3.4.3. caretRangeFromPoint() 方法
if (document.caretRangeFromPoint) {
    range = document.caretRangeFromPoint(e.clientX, e.clientY);
}
3.4.4. Range() 構造函數
const range = new Range()

3.5. Range 相容性

image

4、Selection

Selection 對象表示用戶選擇的文本範圍或插入符號的當前位置。它代表頁面中的文本選區,可能橫跨多個元素。

4.1. 獲取文本對象

window.getSelection()

image

image

4.2. Selection 術語

4.2.1. 錨點 (anchor)

錨指的是一個選區的起始點(不同於 HTML 中的錨點鏈接)。當我們使用滑鼠框選一個區域的時候,錨點就是我們滑鼠按下瞬間的那個點。在用戶拖動滑鼠時,錨點是不會變的。

4.2.2. 焦點 (focus)

選區的焦點是該選區的終點,當你用滑鼠框選一個選區的時候,焦點是你的滑鼠鬆開瞬間所記錄的那個點。隨著用戶拖動滑鼠,焦點的位置會隨著改變。

4.2.3. 範圍 (range)

範圍指的是文檔中連續的一部分。一個範圍包括整個節點,也可以包含節點的一部分,例如文本節點的一部分。用戶通常下只能選擇一個範圍,但是有的時候用戶也有可能選擇多個範圍。

4.2.4. 可編輯元素 (editing host)

一個用戶可編輯的元素(例如一個使用 contenteditableHTML 元素,或是在啟用了 designModeDocument 的子元素)。

4.3. Selection 的屬性

首先要清楚,選擇的起點稱為錨點(anchor),終點稱為焦點(focus)。

  1. anchorNode:選擇的起始節點。

  2. anchorOffset:選擇開始的 anchorNode 中的偏移量。

  3. focusNode:選擇的結束節點。

  4. focusOffset:選擇開始處 focusNode 的偏移量。

  5. isCollapsed:如果未選擇任何內容(空範圍)或不存在,則為 true

  6. rangeCount:選擇中的範圍數,之前說過,除 Firefox 外,其他瀏覽器最多為1。

  7. type:類型:NoneCaretRange

4.4. Selection 方法

  1. addRange(range): 將一個 Range 對象添加到當前選區。

  2. collapse(node, offset): 將選區摺疊到指定的節點和偏移位置。

  3. collapseToEnd(): 將選區摺疊到當前選區的末尾。

  4. collapseToStart(): 將選區摺疊到當前選區的起始位置。

  5. containsNode(node, partlyContained): 判斷選區是否包含指定的節點,可以選擇是否部分包含。

  6. deleteFromDocument(): 從文檔中刪除選區內容。

  7. empty(): 從選區中移除所有範圍(同 `removeAllRanges()``,已廢棄)。

  8. extend(node, offset): 將選區的焦點節點擴展到指定的節點和偏移位置。

  9. getRangeAt(index): 返回選區中指定索引處的 Range 對象。

  10. removeAllRanges(): 移除所有選區中的範圍。

  11. removeRange(range): 從選區中移除指定的 Range 對象。

  12. selectAllChildren(node): 選中指定節點的所有子節點。

  13. setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset): 設置選區的起始和結束節點及偏移位置。

  14. setPosition(node, offset)collapse 的別名

4.5. Selection 相容性

image

三、項目實現

1、實現思路

  1. 先獲取選擇的內容,開發 getSelectContent 函數
  2. 對獲取的內容進行判斷,是否存在 selection 實例,沒有直接返回 null
  3. 判斷 selection 實例的 isCollapsed 屬性
    • 沒有選中,對 selection 進行 toString().trim() 操作,判斷內容
      • 有內容,直接返回 text 類型
      • 無內容,返回 null
    • 有選中,則判斷內容
  4. 判斷選中的內容有沒有節點
    • 沒有節點,則和沒有選中一樣處理,進行 toString().trim() 操作,判斷內容
      • 有內容,直接返回 text 類型
      • 無內容,返回 null
    • 有節點,進行 toString().trim() 操作,判斷內容
      • 沒有內容,判斷是否有特殊節點
        • 'iframe', 'svg', 'img', 'audio', 'video' 節點,返回 html 類型
        • 'input', 'textarea', 'select',判斷 value 值,是否存在
          • 存在:返回 html 類型
          • 不存在:返回 null
        • 沒有特殊節點,返回 null
      • 有內容,返回 html 類型
  5. 對滑鼠 mousedownmouseup 事件和 selectionchangecontextmenudblclick 事件進行監聽,觸發 getSelectContent 函數
  6. 在需要的地方進行 debounce 防抖處理

2、簡易流程圖

image

2、Debounce 方法實現

2.1. JS

function debounce (fn, time = 500) {
  let timeout = null; // 創建一個標記用來存放定時器的返回值
  return function () {
    clearTimeout(timeout) // 每當觸發時,把前一個 定時器 clear 掉
    timeout = setTimeout(() => { // 創建一個新的 定時器,並賦值給 timeout
      fn.apply(this, arguments)
    }, time)
  }
}

2.2. TS

/**
 * debounce 函數類型
 */
type DebouncedFunction<F extends (...args: any[]) => any> = (...args: Parameters<F>) => void
/**
 * debounce 防抖函數
 * @param {Function} func 函數
 * @param {number} wait 等待時間
 * @param {false} immediate 是否立即執行
 * @returns {DebouncedFunction}
 */
function debounce<F extends (...args: any[]) => any>(
  func: F,
  wait = 500,
  immediate = false
): DebouncedFunction<F> {
  let timeout: ReturnType<typeof setTimeout> | null
  return function (this: ThisParameterType<F>, ...args: Parameters<F>) {
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const context = this
    const later = function () {
      timeout = null
      if (!immediate) {
        func.apply(context, args)
      }
    }
    const callNow = immediate && !timeout
    if (timeout) {
      clearTimeout(timeout)
    }
    timeout = setTimeout(later, wait)
    if (callNow) {
      func.apply(context, args)
    }
  }
}

3、獲取選擇的文本/html 元素

3.1. 獲取文本/html 元素

nterface IGetSelectContentProps {
  type: 'html' | 'text'
  content: string
}
/**
 * 獲取選擇的內容
 * @returns {null | IGetSelectContentProps} 返回選擇的內容
 */
const getSelectContent = (): null | IGetSelectContentProps => {
  const selection = window.getSelection()
  if (selection) {
    // 1. 是焦點在 input 輸入框
    // 2. 沒有選中
    // 3. 選擇的是輸入框
    if (selection.isCollapsed) {
      return selection.toString().trim().length
        ? {
            type: 'text',
            content: selection.toString().trim()
          }
        : null
    }
    // 獲取選擇範圍
    const range = selection.getRangeAt(0)
    // 獲取選擇內容
    const rangeClone = range.cloneContents()
    // 判斷選擇內容裡面有沒有節點
    if (rangeClone.childElementCount > 0) {
      // 創建 div 標簽
      const container = document.createElement('div')
      // div 標簽 append 複製節點
      container.appendChild(rangeClone)
      // 如果複製的內容長度為 0
      if (!selection.toString().trim().length) {
        // 判斷是否有選擇特殊節點
        const isSpNode = hasSpNode(container)
        return isSpNode
          ? {
              type: 'html',
              content: container.innerHTML
            }
          : null
      }
      return {
        type: 'html',
        content: container.innerHTML
      }
    } else {
      return selection.toString().trim().length
        ? {
            type: 'text',
            content: selection.toString().trim()
          }
        : null
    }
  } else {
    return null
  }
}
/**
 * 判斷是否包含特殊元素
 * @param {Element} parent 父元素
 * @returns {boolean} 是否包含特殊元素
 */
const hasSpNode = (parent: Element): boolean => {
  const nodeNameList = ['iframe', 'svg', 'img', 'audio', 'video']
  const inpList = ['input', 'textarea', 'select']
  return Array.from(parent.children).some((node) => {
    if (nodeNameList.includes(node.nodeName.toLocaleLowerCase())) return true
    if (
      inpList.includes(node.nodeName.toLocaleLowerCase()) &&
      (node as HTMLInputElement).value.trim().length
    )
      return true
    if (node.children) {
      return hasSpNode(node)
    }
    return false
  })
}

3.2. 只需要文本

/**
 * 獲取框選的文案內容
 * @returns {string} 返回框選的內容
 */
const getSelectTextContent = (): string => {
  const selection = window.getSelection()
  return selection?.toString().trim() || ''
}

4、添加事件監聽

// 是否時滑鼠點擊動作
let selectionchangeMouseTrack: boolean = false
const selectionChangeFun = debounce(() => {
  const selectContent = getSelectContent()
  console.log('selectContent', selectContent)
  // todo... 處理上報
  selectionchangeMouseTrack = false
})
// 添加 mousedown 監聽事件
document.addEventListener('mousedown', () => {
  selectionchangeMouseTrack = true
})
// 添加 mouseup 監聽事件
document.addEventListener(
  'mouseup',
  debounce(() => {
    selectionChangeFun()
  }, 100)
)
// 添加 selectionchange 監聽事件
document.addEventListener(
  'selectionchange',
  debounce(() => {
    if (selectionchangeMouseTrack) return
    selectionChangeFun()
  })
)
// 添加 dblclick 監聽事件
document.addEventListener('dblclick', () => {
  selectionChangeFun()
})
// 添加 contextmenu 監聽事件
document.addEventListener(
  'contextmenu',
  debounce(() => {
    selectionChangeFun()
  })
)

也可以進行封裝

/**
 * addEventlistener function 類型
 */
export interface IEventHandlerProps {
  [eventName: string]: EventListenerOrEventListenerObject
}

let selectionchangeMouseTrack: boolean = false
const eventHandlers: IEventHandlerProps = {
  // 滑鼠 down 事件
  mousedown: () => {
    selectionchangeMouseTrack = true
  },
  // 滑鼠 up 事件
  mouseup: debounce(() => selectionChangeFun(), 100),
  // 選擇事件
  selectionchange:  debounce(() => {
    if (selectionchangeMouseTrack) return
    selectionChangeFun()
  }),
  // 雙擊事件
  dblclick: () => selectionChangeFun(),
  // 右鍵事件
  contextmenu: debounce(() => selectionChangeFun())
}
Object.keys(eventHandlers).forEach((event) => {
  document.addEventListener(event, eventHandlers[event])
})

5、返回內容

5.1. 純文本內容

image

5.2. html 格式

image

6. 完整 JS 代碼

function debounce (fn, time = 500) {
  let timeout = null; // 創建一個標記用來存放定時器的返回值
  return function () {
    clearTimeout(timeout) // 每當觸發時,把前一個 定時器 clear 掉
    timeout = setTimeout(() => { // 創建一個新的 定時器,並賦值給 timeout
      fn.apply(this, arguments)
    }, time)
  }
}

let selectionchangeMouseTrack = false
document.addEventListener('mousedown', (e) => {
  selectionchangeMouseTrack = true
  console.log('mousedown', e)
})
document.addEventListener('mouseup', debounce((e) => {
  console.log('mouseup', e)
  selectionChangeFun()
}, 100))
document.addEventListener('selectionchange', debounce((e) => {
  console.log('selectionchange', e)
  if (selectionchangeMouseTrack) return
  selectionChangeFun()
}))
document.addEventListener('dblclick', (e) => {
  console.log('dblclick', e)
  selectionChangeFun()
})
document.addEventListener('contextmenu',debounce(() => {
  selectionChangeFun()
}))

const selectionChangeFun = debounce(() => {
  const selectContent = getSelectContent()
  selectionchangeMouseTrack = false
  console.log('selectContent', selectContent)
})

const getSelectContent = () => {
  const selection = window.getSelection();
  if (selection) {
    // 1. 是焦點在 input 輸入框
    // 2. 沒有選中
    // 3. 選擇的是輸入框
    if (selection.isCollapsed) {
      return selection.toString().trim().length ? {
        type: 'text',
        content: selection.toString().trim()
      } : null
    }
    // 獲取選擇範圍
    const range = selection.getRangeAt(0);
    // 獲取選擇內容
    const rangeClone = range.cloneContents()
    // 判斷選擇內容裡面有沒有節點
    if (rangeClone.childElementCount > 0) {
      const container = document.createElement('div');
      container.appendChild(rangeClone);
      if (!selection.toString().trim().length) {
        const hasSpNode = getSpNode(container)
        return hasSpNode ? {
          type: 'html',
          content: container.innerHTML
        } : null
      }
      return {
        type: 'html',
        content: container.innerHTML
      }
    } else {
      return selection.toString().trim().length ? {
        type: 'text',
        content: selection.toString().trim()
      } : null
    }
  } else {
    return null
  }
}

const getSpNode = (parent) => {
  const nodeNameList = ['iframe', 'svg', 'img', 'audio', 'video']
  const inpList = ['input', 'textarea', 'select']
  return Array.from(parent.children).some((node) => {
    if (nodeNameList.includes(node.nodeName.toLocaleLowerCase())) return true
    if (inpList.includes(node.nodeName.toLocaleLowerCase()) && node.value.trim().length) return true
    if (node.children) {
      return getSpNode(node)
    }
    return false
  })
}

7. 線上預覽

四、總結

  1. 滑鼠框選上報能監控用戶在頁面的行為,能為後續的數據分析等提供便利

  2. 基於 JS 中的 SelectionRange 實現的,使用原生 JS

  3. 涉及到的操作比較多,包含鍵盤、滑鼠右鍵、全選等

  4. 能對框選的內容進行分類,區別 htmltext,更方便的看出用戶選擇了哪些內容

引用


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 組件容器是一種用於管理和組織組件的工具或環境。它可以提供一些基本的功能,如組件的註冊、創建、銷毀和查找。組件容器通常會維護一個組件的依賴關係,並負責將這些依賴註入到組件中。它還可以提供一些其他的功能,如生命周期管理、事件通知、配置管理等。通過使用組件容器,開發者可以更方便地管理和使用組件, ...
  • ADB Remote ATV Android TV 的遙控器,基於 ADB Shell 命令 ADB Remote ATV 是一個 Android TV 的遙控器,基於 ADB Shell 命令,泛用性更高。 下麵的 shell 命令,是軟體的基本原理,通過 shell 命令可模擬物理遙控器的基本按 ...
  • 版本控制在軟體開發中至關重要,而 Git 是廣泛使用的代碼管理工具。有時,我們可能需要在多個平臺 (如 GitHub、GitLab 和 Gitee) 上同步同一 Git 倉庫,以便備份、協作等。 本文將帶你玩轉此操作,其中關鍵是“配置 SSH” 和“遠程倉庫”。首先,我們來講述 SSH 的配置。 配 ...
  • 一、卡片數據交互 HarmonyOS卡片數據交互是指在基於鴻蒙操作系統的設備上,卡片界面之間進行數據的傳輸和交互。 HarmonyOS的卡片是一種輕量級的應用界面,可以在設備的屏幕上顯示信息和提供操作功能。卡片可以包含各種類型的內容,如文本、圖片、按鈕、輸入框等,並可以根據用戶的操作進行相應的響 ...
  • 用戶在頁面訪問時發送數據到後臺,頁面關閉時也發送數據到後臺。 第一次進入頁面時觸發頁面訪問 刷新當前頁面時觸發頁面訪問 新 tab 進入頁面時觸發頁面訪問 當前頁面點擊 nav 進入其他模塊時,觸發頁面關閉&頁面訪問 關閉頁面時觸發頁面關閉 ...
  • 本章內容: 行分隔符(U + 2028)和段分隔符(U + 2029)符號現在允許在字元串文字中,與 JSON 匹配 更加友好的 JSON.stringify 新增了 Array 的flat()方法和flatMap()方法 新增了 String 的trimStart()方法和trimEnd()方法 ...
  • 一、區別 前面兩節我們有提到Loader與Plugin對應的概念,先來回顧下 loader 是文件載入器,能夠載入資源文件,並對這些文件進行一些處理,諸如編譯、壓縮等,最終一起打包到指定的文件中 plugin 賦予了 webpack 各種靈活的功能,例如打包優化、資源管理、環境變數註入等,目的是解決 ...
  • FreeTube —— 一款開源桌面 YouTube 播放器,基於 Electron 實現,同時支 Windows(10 及更高版本)、Mac(macOS 10.15 及更高版本)和 Linux。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...