Files
tzdwindows 7 8de2b0f2fe chore(jcef): 更新缓存数据库日志文件
- 更新 shared_proto_db/metadata/000003.log 文件内容
- 更新 Site Characteristics Database/00003.log 文件内容
- 添加新的数据库条目和元数据记录
- 保持数据库文件格式的一致性
- 删除Vivid2D的内容
- 重写启动加载界面
2026-01-02 17:12:54 +08:00

481 lines
17 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>SQL Command Line</title>
<style>
:root {
--bg-color: #0c0c0c;
--text-color: #cccccc;
--caret-color: #ffffff;
--selection-bg: #264f78;
--menu-bg: #2b2d30;
--highlight-active: #0d293e;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
font-family: 'Consolas', 'Courier New', monospace;
font-size: 16px;
margin: 0;
padding: 10px;
height: 100vh;
box-sizing: border-box;
overflow-y: auto;
word-break: break-all;
}
::selection { background: var(--selection-bg); color: white; }
#terminal { width: 100%; min-height: 100%; display: flex; flex-direction: column; padding-bottom: 50px; }
#output-area { white-space: pre-wrap; cursor: text; }
.input-line { display: flex; margin-top: 5px; position: relative; }
.prompt { color: #cccccc; margin-right: 8px; font-weight: bold; user-select: none; white-space: pre; }
.continuation-prompt { color: #cccccc; margin-right: 8px; user-select: none; white-space: pre; }
input[type="text"] {
background: transparent; border: none; color: #ffffff;
font-family: inherit; font-size: inherit; flex: 1;
outline: none; padding: 0; margin: 0; caret-color: var(--caret-color);
}
.welcome-msg { margin-bottom: 20px; color: #888; }
.error-text { color: #ff5555; }
/* --- 自动补全样式 --- */
#suggestion-box {
position: absolute; display: none; background-color: #2b2d30;
border: 1px solid #5f6165; box-shadow: 0 5px 15px rgba(0,0,0,0.6);
z-index: 999; max-height: 200px; overflow-y: auto; min-width: 250px;
border-radius: 3px;
}
.suggestion-item {
padding: 5px 10px; cursor: pointer; font-size: 14px;
color: #a9b7c6; display: flex; align-items: center;
}
.suggestion-item.active { background-color: var(--highlight-active); color: #ffffff; }
.icon {
display: inline-block; width: 16px; height: 16px; margin-right: 8px;
font-size: 10px; text-align: center; line-height: 16px; border-radius: 2px;
font-weight: bold; color: #000;
}
.icon-k { background-color: #ffc66d; } /* Keyword */
.icon-t { background-color: #4a88c7; color: #fff; } /* Table */
.icon-c { background-color: #9876aa; color: #fff; } /* Column */
.detail-text { margin-left: auto; color: #666; font-size: 12px; }
</style>
</head>
<body>
<div id="terminal">
<div class="welcome-msg">
Axis SQL Client [Version 2.0.0]<br>
(c) 2024 Axis Innovators.<br>
Loaded local database mode.<br>
<br>
Commands end with ; <br>
Type 'help' for help. 'cls' to clear.<br>
</div>
<div id="output-area"></div>
<div class="input-line">
<span id="prompt-span" class="prompt">SQL&gt;</span>
<input type="text" id="cmd-input" autocomplete="off" spellcheck="false" autofocus>
<div id="suggestion-box"></div>
</div>
</div>
<script>
// --- 核心状态 ---
const outputArea = document.getElementById('output-area');
const input = document.getElementById('cmd-input');
const promptSpan = document.getElementById('prompt-span');
const suggestionBox = document.getElementById('suggestion-box');
let history = [];
let historyIndex = -1;
// 多行命令缓冲区
let commandBuffer = "";
let isMultiLineMode = false;
// 数据库元数据 (由 Java 填充)
let dbSchema = {
tables: [], // ["student", "logs"]
columns: {}, // {"student": ["id", "name"], "logs": ["id", "msg"]}
allColumns: [] // flat list of all columns for quick search
};
// 静态关键字库
const keywords = [
"SELECT", "FROM", "WHERE", "INSERT", "INTO", "VALUES", "UPDATE", "SET",
"DELETE", "CREATE", "TABLE", "DROP", "ALTER", "ORDER", "BY", "GROUP",
"LIMIT", "JOIN", "LEFT", "RIGHT", "INNER", "OUTER", "ON", "AND", "OR",
"NOT", "NULL", "AS", "DISTINCT", "COUNT", "MAX", "MIN", "AVG", "SUM"
];
// --- 1. 初始化: 加载表结构 ---
window.onload = function() {
if (window.cefQuery) {
window.cefQuery({
request: JSON.stringify({ type: "initSchema" }),
onSuccess: function(response) {
const res = JSON.parse(response);
if (res.status === "success") {
dbSchema.tables = res.tables || [];
dbSchema.columns = res.columns || {};
// 扁平化所有列名,方便通用提示
let colSet = new Set();
for (let t in dbSchema.columns) {
dbSchema.columns[t].forEach(c => colSet.add(c));
}
dbSchema.allColumns = Array.from(colSet);
console.log("Schema loaded:", dbSchema);
}
},
onFailure: function() {}
});
} else {
// 测试模式数据
dbSchema.tables = ["student", "course"];
dbSchema.columns = { "student": ["id", "name", "age"], "course": ["id", "title"] };
dbSchema.allColumns = ["id", "name", "age", "title"];
}
};
// --- 2. 输入与键盘事件 ---
input.addEventListener('keydown', function(e) {
// 如果补全框打开,接管按键
if (suggestionBox.style.display === 'block') {
if (handleSuggestionKeys(e)) return;
}
if (e.key === 'Enter') {
handleEnterKey();
}
else if (e.key === 'ArrowUp') {
handleHistory(true, e);
}
else if (e.key === 'ArrowDown') {
handleHistory(false, e);
}
});
// --- 3. 多行命令处理逻辑 (核心升级) ---
function handleEnterKey() {
const rawLine = input.value;
// 1. 如果输入为空,只是换行
if (!rawLine.trim() && !commandBuffer.trim()) {
input.value = '';
return;
}
// 2. 显示当前行
const promptText = isMultiLineMode ? " -> " : "SQL> ";
appendLine(promptText + rawLine);
// 3. 处理历史记录 (每次回车都存,方便找回片段)
if (rawLine.trim()) {
history.push(rawLine);
historyIndex = history.length;
}
// 4. 判断特殊前端命令
const upperLine = rawLine.trim().toUpperCase();
if (upperLine === 'CLS' || upperLine === 'CLEAR') {
outputArea.innerHTML = '';
resetInput();
return;
}
if (upperLine === 'EXIT') {
// 这里可以调用关闭窗口
appendLine("Bye.");
return;
}
// 5. 拼接到缓冲区
commandBuffer += rawLine + " "; // 加空格防止粘连
// 6. 检查是否以分号结尾 (真正的执行触发器)
if (rawLine.trim().endsWith(';')) {
// 移除分号并执行
const finalCmd = commandBuffer.trim().replace(/;$/, '');
processCommand(finalCmd);
resetInput(); // 重置为单行模式
} else {
// 进入多行模式
isMultiLineMode = true;
promptSpan.textContent = " -> ";
input.value = '';
// 滚动到底部
window.scrollTo(0, document.body.scrollHeight);
}
}
function resetInput() {
commandBuffer = "";
isMultiLineMode = false;
promptSpan.textContent = "SQL> ";
input.value = '';
window.scrollTo(0, document.body.scrollHeight);
}
function handleHistory(isUp, e) {
e.preventDefault();
if (isUp && historyIndex > 0) {
historyIndex--;
input.value = history[historyIndex];
} else if (!isUp && historyIndex < history.length - 1) {
historyIndex++;
input.value = history[historyIndex];
} else if (!isUp) {
historyIndex = history.length;
input.value = '';
}
}
// --- 4. 智能感知自动补全 (Context Aware) ---
let currentSuggestions = [];
let activeIndex = -1;
input.addEventListener('input', function() {
const val = this.value;
const cursorPos = this.selectionStart;
// 获取正在输入的单词
const wordInfo = getCurrentWord(val, cursorPos);
if (!wordInfo) {
hideSuggestions();
return;
}
// 分析上下文 (Context Analysis)
const contextType = analyzeContext(val, wordInfo.startPos);
// 获取建议列表
const list = getFilteredSuggestions(wordInfo.word, contextType);
if (list.length > 0) {
showSuggestions(list, wordInfo.startPos);
} else {
hideSuggestions();
}
});
/**
* 简单的上下文分析器
* 返回: 'TABLE' | 'COLUMN' | 'KEYWORD' (默认)
*/
function analyzeContext(text, cursorIndex) {
// 取光标前的文本,转大写,分割成 Token
const prefix = text.substring(0, cursorIndex).toUpperCase();
const tokens = prefix.split(/\s+/).filter(t => t.length > 0);
if (tokens.length === 0) return 'KEYWORD';
const lastToken = tokens[tokens.length - 1];
// 如果上一个词是这些,通常后面接表名
const tableTriggers = ["FROM", "JOIN", "UPDATE", "INTO", "TABLE"];
if (tableTriggers.includes(lastToken)) return 'TABLE';
// 如果上一个词是这些,通常后面接列名
const columnTriggers = ["SELECT", "WHERE", "ORDER", "BY", "GROUP", "HAVING", "SET", "ON", "AND", "OR"];
if (columnTriggers.includes(lastToken) || lastToken.endsWith(',')) return 'COLUMN';
return 'KEYWORD';
}
function getFilteredSuggestions(query, type) {
const q = query.toUpperCase();
let results = [];
// 1. 优先根据上下文添加
if (type === 'TABLE') {
dbSchema.tables.forEach(t => {
if (t.toUpperCase().startsWith(q)) results.push({ text: t, type: 't', detail: 'Table' });
});
} else if (type === 'COLUMN') {
dbSchema.allColumns.forEach(c => {
if (c.toUpperCase().startsWith(q)) results.push({ text: c, type: 'c', detail: 'Column' });
});
// 列名场景下也可能输入表名(例如 table.col
dbSchema.tables.forEach(t => {
if (t.toUpperCase().startsWith(q)) results.push({ text: t, type: 't', detail: 'Table' });
});
}
// 2. 总是添加匹配的关键字 (但在特定上下文排在后面)
keywords.forEach(k => {
if (k.startsWith(q)) results.push({ text: k, type: 'k', detail: 'Keyword' });
});
return results.slice(0, 15); // 限制显示数量
}
// --- 补全 UI 渲染逻辑 --- (复用之前的基础,增加了类型判断)
function showSuggestions(list, wordStartPos) {
currentSuggestions = list;
activeIndex = 0;
// 计算 UI 位置
const promptWidth = getTextWidth(promptSpan.textContent, "bold 16px Consolas");
const inputBefore = input.value.substring(0, wordStartPos);
const inputWidth = getTextWidth(inputBefore, "16px Consolas");
suggestionBox.style.left = (promptWidth + inputWidth) + "px";
suggestionBox.style.top = "26px";
suggestionBox.style.display = "block";
renderList();
}
function renderList() {
suggestionBox.innerHTML = '';
currentSuggestions.forEach((item, idx) => {
const div = document.createElement('div');
div.className = 'suggestion-item' + (idx === activeIndex ? ' active' : '');
let iconChar = 'K';
if (item.type === 't') iconChar = 'T';
if (item.type === 'c') iconChar = 'C';
div.innerHTML = `
<span class="icon icon-${item.type}">${iconChar}</span>
<span class="text">${item.text}</span>
<span class="detail-text">${item.detail}</span>
`;
div.onmousedown = (e) => { e.preventDefault(); applySuggestion(idx); }; // prevent blur
suggestionBox.appendChild(div);
});
// 自动滚动
const activeEl = suggestionBox.children[activeIndex];
if (activeEl) {
const boxTop = suggestionBox.scrollTop;
const boxBottom = boxTop + suggestionBox.clientHeight;
const elTop = activeEl.offsetTop;
const elBottom = elTop + activeEl.clientHeight;
if (elTop < boxTop) suggestionBox.scrollTop = elTop;
if (elBottom > boxBottom) suggestionBox.scrollTop = elBottom - suggestionBox.clientHeight;
}
}
function handleSuggestionKeys(e) {
if (e.key === 'ArrowDown') {
e.preventDefault();
activeIndex = (activeIndex + 1) % currentSuggestions.length;
renderList();
return true;
}
if (e.key === 'ArrowUp') {
e.preventDefault();
activeIndex = (activeIndex - 1 + currentSuggestions.length) % currentSuggestions.length;
renderList();
return true;
}
if (e.key === 'Tab' || e.key === 'Enter') {
e.preventDefault();
applySuggestion(activeIndex);
return true;
}
if (e.key === 'Escape') {
hideSuggestions();
return true;
}
return false;
}
function applySuggestion(index) {
const item = currentSuggestions[index];
const val = input.value;
const wordInfo = getCurrentWord(val, input.selectionStart);
if (wordInfo) {
const prefix = val.substring(0, wordInfo.startPos);
const suffix = val.substring(input.selectionStart);
const newText = prefix + item.text + " " + suffix; // 自动加个空格
input.value = newText;
const newPos = prefix.length + item.text.length + 1;
input.setSelectionRange(newPos, newPos);
}
hideSuggestions();
}
function hideSuggestions() {
suggestionBox.style.display = 'none';
activeIndex = -1;
}
// --- 工具函数 ---
function getCurrentWord(text, cursorIndex) {
const left = text.substring(0, cursorIndex);
const match = left.match(/([a-zA-Z0-9_]+)$/);
if (match) {
return { word: match[1], startPos: match.index };
}
return null;
}
function getTextWidth(text, font) {
const canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
const ctx = canvas.getContext("2d");
ctx.font = font;
return ctx.measureText(text).width;
}
// --- 后端通信与显示 (复用) ---
function processCommand(cmd) {
if (window.cefQuery) {
window.cefQuery({
request: JSON.stringify({ type: "executeCommand", command: cmd }),
onSuccess: (res) => renderResponse(JSON.parse(res)),
onFailure: (c, msg) => appendLine("Error: " + msg, true)
});
}
}
function renderResponse(res) {
if (res.status === 'error') {
appendLine(res.output, true);
} else if (res.type === 'table') {
appendLine(generateAsciiTable(res.headers, res.rows));
if (res.info) appendLine(res.info);
} else {
appendLine(res.output);
}
appendLine(""); // 空行分割
}
function appendLine(text, isError = false) {
const div = document.createElement('div');
div.textContent = text;
if (isError) div.className = 'error-text';
outputArea.appendChild(div);
}
function generateAsciiTable(headers, rows) {
if (!headers || !headers.length) return "";
const colWidths = headers.map(h => h.length);
rows.forEach(row => row.forEach((c, i) => colWidths[i] = Math.max(colWidths[i], String(c).length)));
const border = "+" + colWidths.map(w => "-".repeat(w + 2)).join("+") + "+";
const makeRow = (arr) => "|" + arr.map((c, i) => " " + String(c).padEnd(colWidths[i]) + " ").join("|") + "|";
return [border, makeRow(headers), border, ...rows.map(makeRow), border].join("\n");
}
// 点击聚焦 (不影响选中文本)
document.addEventListener('click', (e) => {
if (!window.getSelection().toString() && !suggestionBox.contains(e.target)) input.focus();
});
</script>
</body>
</html>