最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用HTML+JavaScript實現(xiàn)SQL智能補全功能

 更新時間:2026年03月30日 09:20:37   作者:bjzhang75  
現(xiàn)代數(shù)據(jù)庫管理工具中,SQL 智能補全功能已成為提升開發(fā)效率的重要特性,它能夠根據(jù)上下文環(huán)境自動提示關(guān)鍵詞、表名、字段名等信息,大大減少了手動輸入錯誤和查詢文檔的時間,本文將介紹如何使用HTML、CSS和JavaScript實現(xiàn)SQL智能補全功能,需要的朋友可以參考下

一、SQL 智能補全功能

現(xiàn)代數(shù)據(jù)庫管理工具中,SQL 智能補全功能已成為提升開發(fā)效率的重要特性。它能夠根據(jù)上下文環(huán)境自動提示關(guān)鍵詞、表名、字段名等信息,大大減少了手動輸入錯誤和查詢文檔的時間。這種智能化的代碼輔助功能不僅提高了編寫 SQL 的準確性,還能幫助開發(fā)者快速回憶表結(jié)構(gòu)和語法規(guī)范。本文將介紹如何使用 HTML、CSS 和 JavaScript 實現(xiàn) SQL 智能補全功能。

二、效果演示

這個 SQL 智能補全功能提供了一個直觀的編輯環(huán)境,當(dāng)用戶在文本框中輸入 SQL 語句時,系統(tǒng)會根據(jù)上下文智能推薦關(guān)鍵詞、表名和字段名。例如,當(dāng)用戶在 SELECT 關(guān)鍵詞后輸入時,系統(tǒng)會推薦可用的字段名;在 FROM 后面則會推薦數(shù)據(jù)庫中的表名。補全建議會以下拉菜單的形式顯示,用戶可以通過鍵盤方向鍵導(dǎo)航,按 Enter 或 Tab 鍵確認選擇,也可以點擊鼠標直接選擇。

三、系統(tǒng)分析

1.頁面結(jié)構(gòu)

頁面主要包括以下幾個區(qū)域:

1.1 SQL 編輯器區(qū)域

這是主要的輸入?yún)^(qū)域,用戶在這里編寫 SQL 語句。

<div class="sql-editor-wrapper">
    <textarea id="sqlEditor" placeholder="輸入SQL語句,體驗智能補全功能..."></textarea>
    <div class="autocomplete-dropdown" id="autocompleteDropdown"></div>
</div>

1.2 信息面板區(qū)域

底部的信息面板提供了使用說明和快捷鍵提示。

<div class="info-panel">
    支持:關(guān)鍵詞、表名、字段名智能補全 | 快捷鍵:↑↓選擇,Enter/Tab確認,Esc取消 | 表:user, order
</div>

2.核心功能實現(xiàn)

2.1 SQL 詞匯表構(gòu)建

智能補全功能的基礎(chǔ)是完整的詞匯表,包括 SQL 關(guān)鍵詞和預(yù)定義的表結(jié)構(gòu)。代碼首先定義了常用 SQL 關(guān)鍵詞數(shù)組 SQL_KEYWORDS,包含 SELECT、FROM、WHERE 等標準 SQL 語句關(guān)鍵詞。同時,通過 TABLES 對象定義了兩個示例表(user 和 order)及其字段,這些數(shù)據(jù)構(gòu)成了智能補全的數(shù)據(jù)基礎(chǔ)。為了提高查詢效率,代碼創(chuàng)建了緩存數(shù)據(jù)結(jié)構(gòu):TABLE_NAMES 存儲所有表名,ALL_COLUMNS 是所有字段的集合,而 COLUMNS_BY_TABLE 則建立了表到其字段的映射關(guān)系。

// SQL關(guān)鍵詞
const SQL_KEYWORDS = [
  'SELECT', 'FROM', 'WHERE', 'INSERT', 'UPDATE', 'DELETE', 'SET',
  'JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'INNER JOIN', 'OUTER JOIN',
  // ... 其他關(guān)鍵詞
];

// 表結(jié)構(gòu)定義
const TABLES = {
  user: { columns: ['id', 'username', 'email', 'password', 'created_at', 'updated_at', 'status', 'nickname', 'avatar'] },
  order: { columns: ['id', 'user_id', 'order_no', 'amount', 'status', 'created_at', 'pay_time', 'remark', 'payment_method'] }
};

// 緩存數(shù)據(jù)
const TABLE_NAMES = Object.keys(TABLES);
const ALL_COLUMNS = new Set();
const COLUMNS_BY_TABLE = {};
TABLE_NAMES.forEach(table => {
  COLUMNS_BY_TABLE[table] = new Set(TABLES[table].columns);
  TABLES[table].columns.forEach(col => ALL_COLUMNS.add(col));
});

2.2 令牌提取與上下文分析

智能補全的關(guān)鍵在于準確識別用戶當(dāng)前正在輸入的內(nèi)容。
getTokenContext 函數(shù)負責(zé)從編輯器中提取光標前的文本,并使用正則表達式匹配當(dāng)前正在輸入的令牌(token)。這個函數(shù)返回令牌本身、令牌起始位置和光標位置等信息,為后續(xù)的上下文分析提供基礎(chǔ)。
getContext 函數(shù)進一步分析當(dāng)前的 SQL 上下文,確定用戶可能需要的補全類型。它通過檢查光標前的文本模式來判斷當(dāng)前處于什么 SQL 語句部分:如果前面是 FROM、JOIN 或 INTO,則認為是表名上下文;如果是 ON,則可能是列名且涉及表別名;如果是 WHERE、ORDER BY 等,則是列名上下文;如果檢測到表別名(如 “t.” 形式),則提供該表的字段建議。

function getTokenContext() {
  const cursorPos = editor.selectionStart;
  const text = editor.value;
  const beforeCursor = text.substring(0, cursorPos);
  const tokenMatch = beforeCursor.match(/[\w.]*$/);
  const token = tokenMatch ? tokenMatch[0] : '';
  const tokenStart = cursorPos - token.length;
  return { token, tokenStart, cursorPos, text };
}
function getContext(text, cursorPos, tokenStart) {
  const beforeToken = text.substring(0, tokenStart).toUpperCase().trim();

  if (/FROM\s*$/.test(beforeToken) || /JOIN\s*$/.test(beforeToken) || /INTO\s*$/.test(beforeToken)) {
    return { type: 'table', prefix: '' };
  }

  if (/ON\s*$/.test(beforeToken)) {
    return { type: 'column', prefix: '', tableContext: true };
  }

  if (/(WHERE|ORDER BY|GROUP BY|HAVING|SELECT|SET)\s*$/.test(beforeToken)) {
    return { type: 'column', prefix: '' };
  }

  const aliasMatch = text.substring(Math.max(0, tokenStart - 50), tokenStart).match(/(\w+)\.$/);
  if (aliasMatch) {
    const alias = aliasMatch[1].toLowerCase();
    if (COLUMNS_BY_TABLE[alias]) {
      return { type: 'column', prefix: '', table: alias };
    }
  }

  return { type: 'mixed', prefix: '' };
}

2.3 智能建議算法

getSuggestions 函數(shù)實現(xiàn)了建議算法,它根據(jù)上下文類型返回相應(yīng)的補全選項。算法首先定義了 calculateScore 函數(shù)來計算匹配度分數(shù),該函數(shù)考慮了多種因素:完全匹配得分最高,前綴匹配次之,包含匹配再次,最后根據(jù)字符串長度進行微調(diào)?;谏舷挛念愋停惴ǚ謩e處理表名、字段名和混合類型的建議。對于表名上下文,只返回匹配的表名;對于字段名上下文,如果沒有指定表則返回所有字段,如果有指定表則只返回該表的字段;對于混合上下文,則同時考慮關(guān)鍵詞、表名和字段名。所有建議按分數(shù)排序并限制數(shù)量。

function getSuggestions(token, context) {
  const suggestions = [];
  const tokenLower = token.toLowerCase();
  const cleanToken = token.replace(/[`"]/g, '');
  const cleanTokenLower = cleanToken.toLowerCase();

  function calculateScore(text, search, typePriority) {
    let score = typePriority * 100;
    const textLower = text.toLowerCase();
    const searchLower = search.toLowerCase();

    if (textLower === searchLower) {
      score += 1000;
    } else if (textLower.startsWith(searchLower)) {
      score += 500;
    } else if (textLower.includes(searchLower)) {
      score += 200;
    }

    score -= text.length * 0.1;
    return score;
  }

  if (context.type === 'table') {
    TABLE_NAMES.forEach(table => {
      if (table.toLowerCase().includes(cleanTokenLower)) {
        const score = calculateScore(table, cleanToken, 10);
        suggestions.push({ text: table, type: 'table', score });
      }
    });
  } else if (context.type === 'column') {
    // ... 字段上下文,搜索字段名
  } else {
    // ... 混合上下文,搜索關(guān)鍵詞、表名和字段名
  }

  return suggestions.sort((a, b) => b.score - a.score).slice(0, 12);
}

2.4 下拉框定位與渲染

為了提供良好的用戶體驗,下拉框需要精確地出現(xiàn)在光標附近。

calculateDropdownPosition 函數(shù)計算下拉框的絕對位置,它首先通過 textMeasurer 元素測量當(dāng)前行的文本寬度,然后計算光標所在的行列位置,從而確定下拉框的左上角坐標。

renderDropdown 函數(shù)負責(zé)創(chuàng)建和顯示下拉框。它首先清空現(xiàn)有內(nèi)容,然后為每個建議創(chuàng)建 autocomplete-item 元素,包含建議文本和類型標識。每個項目都綁定點擊事件和鼠標懸停事件,使用戶可以通過鼠標或鍵盤選擇建議。

function calculateDropdownPosition(tokenStart) {
  const text = editor.value;
  const beforeToken = text.substring(0, tokenStart);
  const lines = beforeToken.split('\n');
  const currentLineText = lines[lines.length - 1];

  const editorRect = editor.getBoundingClientRect();
  const editorStyle = window.getComputedStyle(editor);

  const paddingLeft = parseFloat(editorStyle.paddingLeft);
  const paddingTop = parseFloat(editorStyle.paddingTop);
  const paddingRight = parseFloat(editorStyle.paddingRight);
  const lineHeight = parseFloat(editorStyle.lineHeight) || 21;

  textMeasurer.textContent = currentLineText;
  const textWidth = textMeasurer.offsetWidth;

  const left = paddingLeft + textWidth;
  const top = paddingTop + (lines.length - 1) * lineHeight;

  const dropdownHeight = Math.min(200, dropdown.scrollHeight || 150);
  const dropdownWidth = 220;

  const spaceBelow = editorRect.height - top;
  const displayAbove = spaceBelow < dropdownHeight + 10;

  const adjustedLeft = Math.min(left, editorRect.width - dropdownWidth - paddingRight - 5);

  return {
    left: adjustedLeft,
    top: displayAbove ? top - dropdownHeight - 5 : top + lineHeight,
    displayAbove
  };
}
function renderDropdown(suggestions, tokenStart) {
  if (suggestions.length === 0) {
    hideDropdown();
    return;
  }

  currentSuggestions = suggestions;
  selectedIndex = -1;

  dropdown.innerHTML = '';
  suggestions.forEach((suggestion, index) => {
    const item = document.createElement('div');
    item.className = 'autocomplete-item';
    item.setAttribute('data-index', index);

    const text = suggestion.displayText || suggestion.text;
    item.innerHTML = `<span>${text}</span><span class="autocomplete-type">${suggestion.type}</span>`;

    item.addEventListener('click', () => selectSuggestion(index));
    item.addEventListener('mouseenter', () => setSelectedIndex(index));

    dropdown.appendChild(item);
  });

  const position = calculateDropdownPosition(tokenStart);
  dropdown.style.left = position.left + 'px';
  dropdown.style.top = position.top + 'px';
  dropdown.style.display = 'block';
}

2.5 交互控制與事件處理

智能補全功能的交互體驗依賴于完善的事件處理機制。本項目中實現(xiàn)了多種事件監(jiān)聽器來處理用戶輸入、鍵盤導(dǎo)航等。
input 事件在用戶輸入時觸發(fā)建議生成。為了避免頻繁更新影響性能,代碼使用防抖技術(shù),延遲 80 毫秒執(zhí)行建議生成邏輯。如果輸入的令牌長度大于等于 1,則根據(jù)上下文獲取建議并渲染下拉框;否則隱藏下拉框。
keydown 事件處理鍵盤導(dǎo)航:方向鍵用于在建議項間移動選擇,Enter 和 Tab 鍵確認選擇,Esc 鍵取消建議。

editor.addEventListener('input', () => {
  if (compositionInProgress) return;

  clearTimeout(inputDebounceTimer);
  inputDebounceTimer = setTimeout(() => {
    const { token, tokenStart, cursorPos } = getTokenContext();

    if (token.length >= 1) {
      const context = getContext(editor.value, cursorPos, tokenStart);
      const suggestions = getSuggestions(token, context);
      renderDropdown(suggestions, tokenStart);
    } else {
      hideDropdown();
    }
  }, 80);
});

editor.addEventListener('keydown', (e) => {
  if (compositionInProgress) return;

  if (dropdown.style.display === 'block') {
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      setSelectedIndex(Math.min(selectedIndex + 1, currentSuggestions.length - 1));
      return;
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      setSelectedIndex(Math.max(selectedIndex - 1, -1));
      return;
    } else if (e.key === 'Enter' || e.key === 'Tab') {
      if (currentSuggestions.length > 0) {
        e.preventDefault();
        selectSuggestion(Math.max(selectedIndex, 0));
        return;
      }
    } else if (e.key === 'Escape') {
      e.preventDefault();
      hideDropdown();
      return;
    }
  }

  if ([' ', '(', ')', ',', ';', '.', '[', ']'].includes(e.key)) {
    setTimeout(() => {
      const { token } = getTokenContext();
      if (token.length < 1) {
        hideDropdown();
      }
    }, 0);
  }
});

四、擴展建議

  • 添加數(shù)據(jù)庫連接功能,動態(tài)獲取真實表結(jié)構(gòu)信息
  • 實現(xiàn)語法高亮,提升代碼可讀性和編輯體驗
  • 增加一鍵美化功能,自動調(diào)整縮進、換行使代碼結(jié)構(gòu)清晰
  • 增加歷史記錄功能,方便用戶查看之前的查詢語句
  • 支持更多數(shù)據(jù)庫方言,適應(yīng)不同的數(shù)據(jù)庫系統(tǒng)

五、完整代碼

  • 源碼地址
    git地址:https://gitee.com/ironpro/hjdemo/blob/master/sql-complete/index.html
  • 完整代碼
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>SQL智能補全功能</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    body {
      padding: 20px;
      background: #f5f5f5;
    }
    .container {
      max-width: 1200px;
      margin: 0 auto;
      background: #fff;
      border: 1px solid #e0e0e0;
      border-radius: 4px;
      overflow: hidden;
      box-shadow: 0 2px 4px rgba(0,0,0,0.05);
    }
    .header {
      background: #fafafa;
      border-bottom: 1px solid #e0e0e0;
      padding: 12px 15px;
      font-size: 14px;
      color: #333;
      font-weight: 500;
    }
    .sql-editor-wrapper {
      position: relative;
    }
    #sqlEditor {
      width: 100%;
      min-height: 400px;
      border: none;
      outline: none;
      padding: 15px;
      font-size: 14px;
      line-height: 1.5;
      resize: vertical;
      background: #fff;
      color: #333;
      tab-size: 4;
    }
    .autocomplete-dropdown {
      position: absolute;
      background: #fff;
      border: 1px solid #d0d0d0;
      border-radius: 3px;
      box-shadow: 0 4px 12px rgba(0,0,0,0.1);
      max-height: 200px;
      overflow-y: auto;
      display: none;
      z-index: 1000;
      min-width: 220px;
      font-size: 13px;
      font-family: inherit;
    }
    .autocomplete-item {
      padding: 7px 12px;
      cursor: pointer;
      display: flex;
      justify-content: space-between;
      align-items: center;
      color: #333;
      border-bottom: 1px solid #f5f5f5;
      transition: background 0.1s;
    }
    .autocomplete-item:last-child {
      border-bottom: none;
    }
    .autocomplete-item:hover,
    .autocomplete-item.active {
      background: #f0f0f0;
    }
    .autocomplete-type {
      font-size: 11px;
      color: #666;
      background: #e8e8e8;
      padding: 2px 6px;
      border-radius: 2px;
      margin-left: 10px;
      text-transform: uppercase;
    }
    .info-panel {
      background: #fafafa;
      border-top: 1px solid #e0e0e0;
      padding: 12px 15px;
      font-size: 12px;
      color: #666;
    }
  </style>
</head>
<body>
<div class="container">
  <div class="header">SQL 編輯器 - 智能補全</div>
  <div class="sql-editor-wrapper">
    <textarea id="sqlEditor" placeholder="輸入SQL語句,體驗智能補全功能..."></textarea>
    <div class="autocomplete-dropdown" id="autocompleteDropdown"></div>
  </div>
  <div class="info-panel">
    支持:關(guān)鍵詞、表名、字段名智能補全 | 快捷鍵:↑↓選擇,Enter/Tab確認,Esc取消 | 表:user, order
  </div>
</div>
<script>
  // SQL關(guān)鍵詞
  const SQL_KEYWORDS = [
    'SELECT', 'FROM', 'WHERE', 'INSERT', 'UPDATE', 'DELETE', 'SET',
    'JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'INNER JOIN', 'OUTER JOIN',
    'ON', 'AS', 'DISTINCT', 'GROUP BY', 'ORDER BY', 'HAVING',
    'LIMIT', 'OFFSET', 'UNION', 'ALL', 'EXISTS', 'IN', 'NOT IN',
    'BETWEEN', 'LIKE', 'ILIKE', 'AND', 'OR', 'NOT', 'NULL',
    'IS', 'COUNT', 'SUM', 'AVG', 'MAX', 'MIN', 'CREATE', 'TABLE',
    'INDEX', 'PRIMARY KEY', 'FOREIGN KEY', 'REFERENCES', 'ALTER',
    'DROP', 'VIEW', 'TRIGGER', 'PROCEDURE', 'FUNCTION', 'DATABASE',
    'SCHEMA', 'INT', 'VARCHAR', 'TEXT', 'DATE', 'DATETIME', 'BOOLEAN',
    'DECIMAL', 'FLOAT', 'DOUBLE', 'CHAR', 'BIGINT', 'SMALLINT'
  ];
  // 表結(jié)構(gòu)定義
  const TABLES = {
    user: { columns: ['id', 'username', 'email', 'password', 'created_at', 'updated_at', 'status', 'nickname', 'avatar'] },
    order: { columns: ['id', 'user_id', 'order_no', 'amount', 'status', 'created_at', 'pay_time', 'remark', 'payment_method'] }
  };
  // 緩存數(shù)據(jù)
  const TABLE_NAMES = Object.keys(TABLES);
  const ALL_COLUMNS = new Set();
  const COLUMNS_BY_TABLE = {};
  TABLE_NAMES.forEach(table => {
    COLUMNS_BY_TABLE[table] = new Set(TABLES[table].columns);
    TABLES[table].columns.forEach(col => ALL_COLUMNS.add(col));
  });
  // DOM元素
  const editor = document.getElementById('sqlEditor');
  const dropdown = document.getElementById('autocompleteDropdown');
  const wrapper = document.querySelector('.sql-editor-wrapper');
  // 狀態(tài)變量
  let currentSuggestions = [];
  let selectedIndex = -1;
  let compositionInProgress = false;
  let inputDebounceTimer;
  // 創(chuàng)建一個隱藏的元素來測量文本寬度
  const textMeasurer = document.createElement('span');
  textMeasurer.style.position = 'absolute';
  textMeasurer.style.visibility = 'hidden';
  textMeasurer.style.whiteSpace = 'pre';
  textMeasurer.style.font = getComputedStyle(editor).font;
  document.body.appendChild(textMeasurer);
  // 獲取當(dāng)前token和位置
  function getTokenContext() {
    const cursorPos = editor.selectionStart;
    const text = editor.value;
    const beforeCursor = text.substring(0, cursorPos);
    const tokenMatch = beforeCursor.match(/[\w.]*$/);
    const token = tokenMatch ? tokenMatch[0] : '';
    const tokenStart = cursorPos - token.length;
    return { token, tokenStart, cursorPos, text };
  }
  // 智能上下文分析
  function getContext(text, cursorPos, tokenStart) {
    const beforeToken = text.substring(0, tokenStart).toUpperCase().trim();
    if (/FROM\s*$/.test(beforeToken) || /JOIN\s*$/.test(beforeToken) || /INTO\s*$/.test(beforeToken)) {
      return { type: 'table', prefix: '' };
    }
    if (/ON\s*$/.test(beforeToken)) {
      return { type: 'column', prefix: '', tableContext: true };
    }
    if (/(WHERE|ORDER BY|GROUP BY|HAVING|SELECT|SET)\s*$/.test(beforeToken)) {
      return { type: 'column', prefix: '' };
    }
    const aliasMatch = text.substring(Math.max(0, tokenStart - 50), tokenStart).match(/(\w+)\.$/);
    if (aliasMatch) {
      const alias = aliasMatch[1].toLowerCase();
      if (COLUMNS_BY_TABLE[alias]) {
        return { type: 'column', prefix: '', table: alias };
      }
    }
    return { type: 'mixed', prefix: '' };
  }
  // 獲取補全建議
  function getSuggestions(token, context) {
    const suggestions = [];
    const tokenLower = token.toLowerCase();
    const cleanToken = token.replace(/[`"]/g, '');
    const cleanTokenLower = cleanToken.toLowerCase();
    function calculateScore(text, search, typePriority) {
      let score = typePriority * 100;
      const textLower = text.toLowerCase();
      const searchLower = search.toLowerCase();
      if (textLower === searchLower) {
        score += 1000;
      } else if (textLower.startsWith(searchLower)) {
        score += 500;
      } else if (textLower.includes(searchLower)) {
        score += 200;
      }
      score -= text.length * 0.1;
      return score;
    }
    if (context.type === 'table') {
      TABLE_NAMES.forEach(table => {
        if (table.toLowerCase().includes(cleanTokenLower)) {
          const score = calculateScore(table, cleanToken, 10);
          suggestions.push({ text: table, type: 'table', score });
        }
      });
    } else if (context.type === 'column') {
      if (context.table) {
        TABLES[context.table].columns.forEach(col => {
          if (col.toLowerCase().includes(cleanTokenLower)) {
            const score = calculateScore(col, cleanToken, 20);
            suggestions.push({
              text: col,
              type: 'column',
              displayText: col,
              score
            });
          }
        });
      } else {
        ALL_COLUMNS.forEach(col => {
          if (col.toLowerCase().includes(cleanTokenLower)) {
            const score = calculateScore(col, cleanToken, 8);
            suggestions.push({
              text: col,
              type: 'column',
              score
            });
          }
        });
        TABLE_NAMES.forEach(table => {
          TABLES[table].columns.forEach(col => {
            if (col.toLowerCase().includes(cleanTokenLower)) {
              const score = calculateScore(col, cleanToken, 3);
              suggestions.push({
                text: `${table}.${col}`,
                type: 'column',
                displayText: `${table}.${col}`,
                score
              });
            }
          });
        });
      }
    } else {
      SQL_KEYWORDS.forEach(keyword => {
        if (keyword.toLowerCase().includes(tokenLower)) {
          const score = calculateScore(keyword, token, 5);
          suggestions.push({ text: keyword, type: 'keyword', score });
        }
      });
      TABLE_NAMES.forEach(table => {
        if (table.toLowerCase().includes(tokenLower)) {
          const score = calculateScore(table, token, 12);
          suggestions.push({ text: table, type: 'table', score });
        }
      });
      ALL_COLUMNS.forEach(col => {
        if (col.toLowerCase().includes(tokenLower)) {
          const score = calculateScore(col, token, 7);
          suggestions.push({
            text: col,
            type: 'column',
            score
          });
        }
      });
    }
    return suggestions.sort((a, b) => b.score - a.score).slice(0, 12);
  }
  // 計算下拉框位置
  function calculateDropdownPosition(tokenStart) {
    const text = editor.value;
    const beforeToken = text.substring(0, tokenStart);
    const lines = beforeToken.split('\n');
    const currentLineText = lines[lines.length - 1];
    const editorRect = editor.getBoundingClientRect();
    const editorStyle = window.getComputedStyle(editor);
    const paddingLeft = parseFloat(editorStyle.paddingLeft);
    const paddingTop = parseFloat(editorStyle.paddingTop);
    const paddingRight = parseFloat(editorStyle.paddingRight);
    const lineHeight = parseFloat(editorStyle.lineHeight) || 21;
    textMeasurer.textContent = currentLineText;
    const textWidth = textMeasurer.offsetWidth;
    const left = paddingLeft + textWidth;
    const top = paddingTop + (lines.length - 1) * lineHeight;
    const dropdownHeight = Math.min(200, dropdown.scrollHeight || 150);
    const dropdownWidth = 220;
    const spaceBelow = editorRect.height - top;
    const displayAbove = spaceBelow < dropdownHeight + 10;
    const adjustedLeft = Math.min(left, editorRect.width - dropdownWidth - paddingRight - 5);
    return {
      left: adjustedLeft,
      top: displayAbove ? top - dropdownHeight - 5 : top + lineHeight,
      displayAbove
    };
  }
  // 渲染下拉框
  function renderDropdown(suggestions, tokenStart) {
    if (suggestions.length === 0) {
      hideDropdown();
      return;
    }
    currentSuggestions = suggestions;
    selectedIndex = -1;
    dropdown.innerHTML = '';
    suggestions.forEach((suggestion, index) => {
      const item = document.createElement('div');
      item.className = 'autocomplete-item';
      item.setAttribute('data-index', index);
      const text = suggestion.displayText || suggestion.text;
      item.innerHTML = `<span>${text}</span><span class="autocomplete-type">${suggestion.type}</span>`;
      item.addEventListener('click', () => selectSuggestion(index));
      item.addEventListener('mouseenter', () => setSelectedIndex(index));
      dropdown.appendChild(item);
    });
    const position = calculateDropdownPosition(tokenStart);
    dropdown.style.left = position.left + 'px';
    dropdown.style.top = position.top + 'px';
    dropdown.style.display = 'block';
  }
  // 選擇建議
  function selectSuggestion(index) {
    if (index < 0 || index >= currentSuggestions.length) return;
    const suggestion = currentSuggestions[index];
    const { token, tokenStart, cursorPos } = getTokenContext();
    const beforeToken = editor.value.substring(0, tokenStart);
    const afterToken = editor.value.substring(cursorPos);
    let insertText = suggestion.text;
    editor.value = beforeToken + insertText + afterToken;
    const newCursorPos = tokenStart + insertText.length;
    editor.setSelectionRange(newCursorPos, newCursorPos);
    hideDropdown();
    editor.focus();
  }
  // 隱藏下拉框
  function hideDropdown() {
    dropdown.style.display = 'none';
    currentSuggestions = [];
    selectedIndex = -1;
  }
  // 設(shè)置選中索引
  function setSelectedIndex(index) {
    const items = dropdown.querySelectorAll('.autocomplete-item');
    items.forEach(item => item.classList.remove('active'));
    if (index >= 0 && index < items.length) {
      items[index].classList.add('active');
      items[index].scrollIntoView({ block: 'nearest' });
    }
    selectedIndex = index;
  }
  // 鍵盤事件處理
  editor.addEventListener('keydown', (e) => {
    if (compositionInProgress) return;
    if (dropdown.style.display === 'block') {
      if (e.key === 'ArrowDown') {
        e.preventDefault();
        setSelectedIndex(Math.min(selectedIndex + 1, currentSuggestions.length - 1));
        return;
      } else if (e.key === 'ArrowUp') {
        e.preventDefault();
        setSelectedIndex(Math.max(selectedIndex - 1, -1));
        return;
      } else if (e.key === 'Enter' || e.key === 'Tab') {
        if (currentSuggestions.length > 0) {
          e.preventDefault();
          selectSuggestion(Math.max(selectedIndex, 0));
          return;
        }
      } else if (e.key === 'Escape') {
        e.preventDefault();
        hideDropdown();
        return;
      }
    }
    if ([' ', '(', ')', ',', ';', '.', '[', ']'].includes(e.key)) {
      setTimeout(() => {
        const { token } = getTokenContext();
        if (token.length < 1) {
          hideDropdown();
        }
      }, 0);
    }
  });
  // 輸入事件處理
  editor.addEventListener('input', () => {
    if (compositionInProgress) return;
    clearTimeout(inputDebounceTimer);
    inputDebounceTimer = setTimeout(() => {
      const { token, tokenStart, cursorPos } = getTokenContext();
      if (token.length >= 1) {
        const context = getContext(editor.value, cursorPos, tokenStart);
        const suggestions = getSuggestions(token, context);
        renderDropdown(suggestions, tokenStart);
      } else {
        hideDropdown();
      }
    }, 80);
  });
  // 中文輸入法處理
  editor.addEventListener('compositionstart', () => {
    compositionInProgress = true;
    hideDropdown();
  });
  editor.addEventListener('compositionend', () => {
    compositionInProgress = false;
    editor.dispatchEvent(new Event('input'));
  });
  // 點擊外部隱藏下拉框
  document.addEventListener('mousedown', (e) => {
    if (!e.target.closest('.sql-editor-wrapper')) {
      hideDropdown();
    }
  });
  // 其他事件監(jiān)聽
  editor.addEventListener('scroll', hideDropdown);
  window.addEventListener('resize', hideDropdown);
  editor.addEventListener('paste', () => {
    setTimeout(() => editor.dispatchEvent(new Event('input')), 0);
  });
  editor.addEventListener('blur', () => {
    setTimeout(() => {
      if (!document.activeElement.closest('.autocomplete-dropdown')) {
        hideDropdown();
      }
    }, 200);
  });
</script>
</body>
</html>

以上就是使用HTML+JavaScript實現(xiàn)SQL智能補全功能的詳細內(nèi)容,更多關(guān)于JavaScript SQL智能補全的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論

三门县| 博罗县| 和政县| 广丰县| 武定县| 靖宇县| 柳江县| 南川市| 尖扎县| 翼城县| 农安县| 阜城县| 绥化市| 磐石市| 东丽区| 精河县| 水富县| 朝阳县| 治多县| 小金县| 镇雄县| 保亭| 高淳县| 山东| 英德市| 三门峡市| 罗山县| 三都| 光山县| 阿克陶县| 淮滨县| 南和县| 阿拉善盟| 喜德县| 黄平县| 潜江市| 且末县| 溧阳市| 元氏县| 镇赉县| 芜湖市|