chore(jcef): 更新缓存数据库日志文件
- 更新 shared_proto_db/metadata/000003.log 文件内容 - 更新 Site Characteristics Database/00003.log 文件内容 - 添加新的数据库条目和元数据记录 - 保持数据库文件格式的一致性 - 删除Vivid2D的内容 - 重写启动加载界面
This commit is contained in:
1216
javascript/HtmlJarViewer.html
Normal file
1216
javascript/HtmlJarViewer.html
Normal file
File diff suppressed because it is too large
Load Diff
117
javascript/LinuxTerminal.html
Normal file
117
javascript/LinuxTerminal.html
Normal file
@@ -0,0 +1,117 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Real Linux Terminal</title>
|
||||
<!-- 引入 Xterm.js 的样式 -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" />
|
||||
<style>
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #000;
|
||||
overflow: hidden;
|
||||
height: 100%; /* 确保 html 和 body 占满全屏 */
|
||||
}
|
||||
#terminal-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="terminal-container"></div>
|
||||
|
||||
<!-- 引入 Xterm.js 核心库 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
|
||||
<!-- 引入自适应插件,让终端随窗口缩放 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></script>
|
||||
|
||||
<script>
|
||||
// 1. 初始化终端
|
||||
const term = new Terminal({
|
||||
cursorBlink: true,
|
||||
fontSize: 14,
|
||||
fontFamily: 'Consolas, "Ubuntu Mono", monospace',
|
||||
theme: {
|
||||
background: '#1e1e1e',
|
||||
foreground: '#ffffff'
|
||||
},
|
||||
// 增加右键菜单功能:粘贴
|
||||
allowProposedApi: true
|
||||
});
|
||||
|
||||
const fitAddon = new FitAddon.FitAddon();
|
||||
term.loadAddon(fitAddon);
|
||||
term.open(document.getElementById('terminal-container'));
|
||||
|
||||
// --- 修复点 1: 自动获取焦点 ---
|
||||
// 确保用户打开窗口后可以直接输入
|
||||
term.focus();
|
||||
|
||||
// --- 修复点 2: 延迟并可靠地适应窗口大小 ---
|
||||
// 使用 setTimeout 将 fit() 推迟到下一个事件循环
|
||||
// 这可以确保在DOM元素完全渲染并获得其最终尺寸后才进行计算
|
||||
function fitTerminal() {
|
||||
try {
|
||||
fitAddon.fit();
|
||||
// 你还可以在这里将新的尺寸发送给后端 PTY
|
||||
// term.cols 和 term.rows 包含了新的行列数
|
||||
} catch (e) {
|
||||
console.error("Fit addon failed:", e);
|
||||
}
|
||||
}
|
||||
setTimeout(fitTerminal, 1); // 1ms 延迟足以确保渲染完成
|
||||
|
||||
// 窗口大小改变时自动调整
|
||||
window.addEventListener('resize', fitTerminal);
|
||||
|
||||
// 2. 监听用户输入 (键盘按键)
|
||||
term.onData(data => {
|
||||
if (window.cefQuery) {
|
||||
window.cefQuery({
|
||||
request: JSON.stringify({
|
||||
type: 'terminalInput',
|
||||
data: data
|
||||
}),
|
||||
onSuccess: function() {},
|
||||
onFailure: function(errorCode, errorMessage) {
|
||||
console.error("CEF query failed:", errorCode, errorMessage);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.warn("cefQuery is not available. Input ignored.");
|
||||
}
|
||||
});
|
||||
|
||||
// --- 修复点 3: 使用 TextDecoder 优化解码逻辑 ---
|
||||
// 这是更现代、更可靠的将字节流解码为UTF-8字符串的方法
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
|
||||
/**
|
||||
* 供 Java 调用的接口:写入数据到终端
|
||||
* @param {string} base64Data - Base64 编码的原始字节流字符串
|
||||
*/
|
||||
function writeToTerminal(base64Data) {
|
||||
try {
|
||||
// 将 Base64 字符串转换为 Uint8Array 字节数组
|
||||
const binaryString = window.atob(base64Data);
|
||||
const len = binaryString.length;
|
||||
const bytes = new Uint8Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
// 使用 TextDecoder 解码为字符串并写入终端
|
||||
term.write(decoder.decode(bytes));
|
||||
} catch (e) {
|
||||
console.error("Failed to decode or write Base64 data:", e);
|
||||
// 可以在终端显示错误信息
|
||||
term.write("\r\n[Front-end Error: Failed to process data from back-end]\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化通知
|
||||
term.write('\x1B[1;3;32mConnected to Local Shell via PTY4J...\x1B[0m\r\n');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
481
javascript/SQLTerminal.html
Normal file
481
javascript/SQLTerminal.html
Normal file
@@ -0,0 +1,481 @@
|
||||
<!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></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>
|
||||
Reference in New Issue
Block a user