feat: site全面更新 + CONTRIBUTING/CHANGELOG完善,准备v0.1.13迭代
CI / test (18.x) (push) Successful in 9m55s
CI / test (20.x) (push) Successful in 9m50s
CI / test (22.x) (push) Successful in 9m52s
CI / test (24.x) (push) Successful in 9m48s

This commit is contained in:
thzxx
2026-07-26 16:13:34 +08:00
parent 7858cf3720
commit 7a53cfd530
5 changed files with 811 additions and 265 deletions
+145 -89
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>在线演示 — MetonaSqlark</title>
<title>在线演示 — MetonaSqlark v0.1.12</title>
<style>
:root {
--bg:#0a0a0f; --surface:#0d1117; --surface2:#161b22; --border:#30363d;
@@ -13,7 +13,6 @@
}
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family:'Inter',-apple-system,system-ui,sans-serif; background:var(--bg); color:var(--text); height:100vh; overflow:hidden; }
/* Header */
header {
height:56px; display:flex; align-items:center; justify-content:space-between; padding:0 24px;
background:var(--surface); border-bottom:1px solid var(--border); z-index:10; position:relative;
@@ -27,7 +26,6 @@
.status { display:flex; align-items:center; gap:8px; font-size:0.82rem; color:var(--text2); }
.status .dot { width:7px; height:7px; border-radius:50%; background:var(--green); animation:pulse 2s infinite; }
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.3} }
/* Main Layout */
.main { display:flex; height:calc(100vh - 56px); }
.editor-panel { flex:1; display:flex; flex-direction:column; border-right:1px solid var(--border); min-width:400px; }
.result-panel { flex:1; display:flex; flex-direction:column; min-width:400px; }
@@ -40,7 +38,6 @@
font-size:0.7rem; padding:3px 10px; border-radius:999px; background:var(--surface2);
border:1px solid var(--border); text-transform:none; letter-spacing:0;
}
/* SQL Editor */
.editor-area { flex:1; display:flex; flex-direction:column; }
#sql-input {
flex:1; background:var(--surface); color:var(--text); border:none; padding:20px;
@@ -48,7 +45,7 @@
resize:none; outline:none; caret-color:var(--primary);
}
#sql-input::placeholder { color:var(--text2); opacity:0.5; }
.toolbar { display:flex; gap:8px; padding:12px 20px; border-top:1px solid var(--border); background:var(--surface2); }
.toolbar { display:flex; gap:8px; padding:12px 20px; border-top:1px solid var(--border); background:var(--surface2); flex-wrap:wrap; }
.btn {
display:inline-flex; align-items:center; gap:6px; padding:10px 20px; border-radius:8px;
font-weight:600; font-size:0.85rem; cursor:pointer; border:none; transition:.25s; font-family:inherit;
@@ -60,7 +57,6 @@
.btn-clear:hover { color:var(--text); border-color:var(--text2); }
.btn-preset { background:transparent; color:var(--accent); border:1px solid var(--accent); font-size:0.78rem; padding:6px 14px; }
.btn-preset:hover { background:rgba(6,182,212,0.12); }
/* Result */
.result-content { flex:1; overflow-y:auto; padding:20px; }
.result-table { width:100%; border-collapse:collapse; font-size:0.85rem; }
.result-table th { text-align:left; padding:10px 14px; background:var(--surface2); color:var(--accent); font-weight:600; border-bottom:2px solid var(--border); position:sticky; top:0; z-index:1; }
@@ -68,19 +64,12 @@
.result-table tr:hover td { background:rgba(99,102,241,0.04); }
.empty-state { display:flex; flex-direction:column; align-items:center; justify-content:center; height:100%; color:var(--text2); gap:12px; }
.empty-state .icon { font-size:3rem; opacity:0.3; }
/* Error */
.error-box { background:rgba(239,68,68,0.1); border:1px solid rgba(239,68,68,0.3); border-radius:10px; padding:16px; color:#f87171; font-family:'JetBrains Mono',monospace; font-size:0.82rem; margin-bottom:16px; }
.success-box { background:rgba(34,197,94,0.1); border:1px solid rgba(34,197,94,0.3); border-radius:10px; padding:12px 16px; color:#4ade80; font-size:0.85rem; margin-bottom:16px; }
/* Tabs */
.tabs { display:flex; gap:4px; padding:8px 20px 0; background:var(--surface); }
.tab { padding:8px 18px; border-radius:8px 8px 0 0; font-size:0.82rem; cursor:pointer; color:var(--text2); border:1px solid transparent; transition:.2s; }
.tab:hover { color:var(--text); }
.tab.active { color:var(--text); background:var(--bg); border-color:var(--border); border-bottom-color:var(--bg); }
/* Scrollbar */
.sql-tag { display:inline-block; background:var(--surface2); border:1px solid var(--border); border-radius:6px; padding:4px 12px; font-family:'JetBrains Mono',monospace; font-size:0.75rem; color:var(--accent); margin-bottom:8px; }
::-webkit-scrollbar { width:6px; height:6px; }
::-webkit-scrollbar-track { background:transparent; }
::-webkit-scrollbar-thumb { background:var(--border); border-radius:3px; }
/* Responsive */
@media(max-width:900px){ .main { flex-direction:column; } .editor-panel,.result-panel { min-width:auto; } .editor-panel { height:50%; } }
</style>
</head>
@@ -93,40 +82,40 @@
<a href="docs.html">文档</a>
<a href="demo.html" class="nav-active">演示</a>
</nav>
<div class="status"><span class="dot"></span> Memory 模式</div>
<div class="status"><span class="dot"></span> Memory 模式 — v0.1.12</div>
</header>
<div class="main">
<!-- Editor Panel -->
<div class="editor-panel">
<div class="tabs">
<div class="tab active" data-tab="sql">SQL 查询</div>
<div class="tab" data-tab="builder">Query Builder</div>
</div>
<div class="editor-area">
<textarea id="sql-input" placeholder="输入 SQL 语句...&#10;&#10;SELECT * FROM users WHERE age > 18&#10;INSERT INTO users VALUES ('4', 'Diana', 'diana@test.com', 28)&#10;SELECT u.name, o.amount FROM users u INNER JOIN orders o ON u.id = o.user_id">-- 欢迎使用 MetonaSqlark 在线演示 🚀
-- 已预置 users / orders 表数据,试试以下查询:
<textarea id="sql-input" placeholder="输入 SQL 语句...&#10;&#10;SELECT * FROM users;&#10;INSERT INTO users VALUES ('4', 'Diana', 'diana@test.com', 28);&#10;SELECT u.name, o.amount FROM users u INNER JOIN orders o ON u.id = o.user_id;">-- 🚀 MetonaSqlark v0.1.12 在线演示
-- 已预置 users / orders / products 表数据
-- 查看所有数据
SELECT * FROM users;
-- 条件查询 + 排序
SELECT name, age FROM users WHERE age > 25 ORDER BY age DESC;
SELECT u.name, o.amount, o.product
-- JOIN 多表关联
SELECT u.name, o.product, o.amount
FROM users u INNER JOIN orders o ON u.id = o.user_id;
-- 聚合统计
SELECT COUNT(*) as total_users, AVG(age) as avg_age FROM users;</textarea>
<div class="toolbar">
<button class="btn btn-run" onclick="runQuery()">▶ 执行 (Ctrl+Enter)</button>
<button class="btn btn-clear" onclick="clearResults()">🗑 清空</button>
<span style="flex:1"></span>
<button class="btn btn-preset" onclick="loadPreset('all')">📋 全数据</button>
<button class="btn btn-preset" onclick="loadPreset('all')">📋 全数据</button>
<button class="btn btn-preset" onclick="loadPreset('join')">🔗 JOIN</button>
<button class="btn btn-preset" onclick="loadPreset('agg')">📊 聚合</button>
<button class="btn btn-preset" onclick="loadPreset('ddl')">🏗 DDL</button>
<button class="btn btn-preset" onclick="loadPreset('crud')">✏️ CRUD</button>
</div>
</div>
</div>
<!-- Result Panel -->
<div class="result-panel">
<div class="panel-header">
<span>📊 查询结果</span>
@@ -136,7 +125,7 @@ SELECT COUNT(*) as total_users, AVG(age) as avg_age FROM users;</textarea>
<div class="empty-state">
<div class="icon"></div>
<div>执行 SQL 查询以查看结果</div>
<div style="font-size:0.8rem;opacity:0.6">Ctrl + Enter 快捷执行</div>
<div style="font-size:0.8rem;opacity:0.6">Ctrl + Enter 快捷执行 · 支持多语句</div>
</div>
</div>
</div>
@@ -144,7 +133,10 @@ SELECT COUNT(*) as total_users, AVG(age) as avg_age FROM users;</textarea>
<script src="../dist/metona-sqlark.js"></script>
<script>
const { create, MetonaSqlark } = window.MetonaSqlark || window.MeSqlark || {};
// Resolve global: try both MetonaSqlark and MeSqlark
const globalNs = window.MetonaSqlark || window.MeSqlark || {};
const { MetonaSqlark, MeSqlark, create } = globalNs;
const DBClass = MetonaSqlark || MeSqlark;
let db = null;
const resultArea = document.getElementById('result-area');
@@ -153,53 +145,68 @@ const sqlInput = document.getElementById('sql-input');
// Init database
async function initDB() {
db = new MetonaSqlark({ name: 'demo', mode: 'memory' });
await db.init();
try {
db = new DBClass({ name: 'demo', mode: 'memory' });
await db.init();
// Create tables
await db.defineTable('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string', required: true },
email: { type: 'string' },
age: { type: 'number', default: 0 },
});
await db.defineTable('orders', {
id: { type: 'string', primaryKey: true },
user_id: { type: 'string' },
product: { type: 'string' },
amount: { type: 'number' },
});
// Create tables
await db.defineTable('users', {
id: { type: 'string', primaryKey: true },
name: { type: 'string', required: true },
email: { type: 'string' },
age: { type: 'number', default: 0 },
});
await db.defineTable('orders', {
id: { type: 'string', primaryKey: true },
user_id: { type: 'string' },
product: { type: 'string' },
amount: { type: 'number' },
quantity: { type: 'number', default: 1 },
});
await db.defineTable('products', {
id: { type: 'string', primaryKey: true },
name: { type: 'string', required: true },
price: { type: 'number', default: 0 },
category: { type: 'string' },
});
// Seed data
await db.table('users').insertMany([
{ id: '1', name: 'Alice', email: 'alice@demo.com', age: 30 },
{ id: '2', name: 'Bob', email: 'bob@demo.com', age: 25 },
{ id: '3', name: 'Charlie', email: 'charlie@demo.com', age: 35 },
]);
await db.table('orders').insertMany([
{ id: 'o1', user_id: '1', product: 'Laptop', amount: 1200 },
{ id: 'o2', user_id: '1', product: 'Mouse', amount: 50 },
{ id: 'o3', user_id: '2', product: 'Keyboard', amount: 150 },
]);
// Seed data
await db.table('users').insertMany([
{ id: '1', name: 'Alice', email: 'alice@demo.com', age: 30 },
{ id: '2', name: 'Bob', email: 'bob@demo.com', age: 25 },
{ id: '3', name: 'Charlie', email: 'charlie@demo.com', age: 35 },
{ id: '4', name: 'Diana', email: 'diana@demo.com', age: 28 },
{ id: '5', name: 'Eve', email: 'eve@demo.com', age: 22 },
]);
await db.table('orders').insertMany([
{ id: 'o1', user_id: '1', product: 'Laptop', amount: 1200, quantity: 1 },
{ id: 'o2', user_id: '1', product: 'Mouse', amount: 50, quantity: 2 },
{ id: 'o3', user_id: '2', product: 'Keyboard', amount: 150, quantity: 1 },
{ id: 'o4', user_id: '3', product: 'Monitor', amount: 400, quantity: 1 },
{ id: 'o5', user_id: '3', product: 'Cable', amount: 20, quantity: 3 },
]);
await db.table('products').insertMany([
{ id: 'p1', name: 'Laptop', price: 1200, category: 'Electronics' },
{ id: 'p2', name: 'Mouse', price: 50, category: 'Accessories' },
{ id: 'p3', name: 'Keyboard', price: 150, category: 'Accessories' },
]);
} catch (e) {
renderError('数据库初始化失败: ' + e.message);
}
}
// Run SQL
async function runQuery() {
const sql = sqlInput.value.trim();
if (!sql) return;
if (!sql || !db) return;
// Handle multiple statements
const statements = sql.split(';').map(s => s.trim()).filter(s => s.length > 0);
// For display purposes, if only one statement, use it; otherwise use first
try {
let lastResult = null;
const allResults = [];
for (const stmt of statements) {
const result = await db.query(stmt);
allResults.push({ sql: stmt, result });
lastResult = result;
}
if (allResults.length === 1) {
@@ -208,7 +215,7 @@ async function runQuery() {
renderAllResults(allResults);
}
} catch (e) {
renderError(e.message);
renderError(e.message || String(e));
}
}
@@ -223,7 +230,7 @@ function renderResult({ sql, result }) {
function renderAllResults(allResults) {
let html = '';
for (const { sql, result } of allResults) {
html += `<div style="color:var(--accent);font-size:0.78rem;margin-bottom:8px;font-family:monospace;">> ${sql}</div>`;
html += `<div class="sql-tag">> ${escapeHTML(sql)}</div>`;
if (Array.isArray(result)) {
if (result.length === 0) {
html += '<div class="success-box">✅ 查询返回 0 行</div>';
@@ -231,9 +238,9 @@ function renderAllResults(allResults) {
html += buildTableHTML(result);
}
} else {
html += `<div class="success-box">✅ 执行成功</div>`;
html += `<div class="success-box">✅ 执行成功 (影响 ${result ?? 0} 行)</div>`;
}
html += '<div style="height:16px"></div>';
html += '<div style="height:12px"></div>';
}
resultArea.innerHTML = html;
updateRowCount(allResults.reduce((sum, r) => sum + (Array.isArray(r.result) ? r.result.length : 0), 0));
@@ -260,7 +267,7 @@ function buildTableHTML(rows) {
html += '<tr>';
for (const col of columns) {
const val = row[col];
html += `<td>${escapeHTML(val === null ? 'NULL' : String(val))}</td>`;
html += `<td>${formatCell(val)}</td>`;
}
html += '</tr>';
}
@@ -268,6 +275,14 @@ function buildTableHTML(rows) {
return html;
}
function formatCell(val) {
if (val === null) return '<span style="color:#64748b;font-style:italic">NULL</span>';
if (val === undefined) return '<span style="color:#64748b">-</span>';
if (typeof val === 'boolean') return `<span style="color:#fbbf24">${val}</span>`;
if (typeof val === 'number') return `<span style="color:#fbbf24">${val}</span>`;
return escapeHTML(String(val));
}
function renderError(msg) {
resultArea.innerHTML = `<div class="error-box">❌ ${escapeHTML(msg)}</div>`;
updateRowCount(0);
@@ -282,32 +297,80 @@ function clearResults() {
updateRowCount(0);
}
function updateRowCount(n) {
rowCount.textContent = `${n}`;
}
function escapeHTML(str) {
return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
function updateRowCount(n) { rowCount.textContent = `${n}`; }
function escapeHTML(str) { return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#039;'); }
// Presets
const presets = {
all: `-- 查看所有用户
all: `-- 查看所有数据
SELECT * FROM users;
-- 查看所有订单
SELECT * FROM orders;`,
SELECT * FROM orders;
SELECT * FROM products;`,
join: `-- INNER JOIN:用户 + 订单
SELECT u.name, o.product, o.amount
SELECT u.name, o.product, o.amount, o.quantity
FROM users u
INNER JOIN orders o ON u.id = o.user_id
ORDER BY o.amount DESC;`,
agg: `-- 聚合:按用户统计订单总额
ORDER BY o.amount DESC;
-- LEFT JOIN:包含没有订单的用户
SELECT u.name, o.product, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;`,
agg: `-- 聚合统计:每个用户的消费
SELECT u.name, COUNT(*) as orders, SUM(o.amount) as total_spent
FROM users u
INNER JOIN orders o ON u.id = o.user_id
GROUP BY u.name
ORDER BY total_spent DESC;`
ORDER BY total_spent DESC;
-- 全局聚合
SELECT COUNT(*) as total_users, AVG(age) as avg_age,
MIN(age) as min_age, MAX(age) as max_age
FROM users;
-- 分类统计
SELECT category, COUNT(*) as product_count, AVG(price) as avg_price
FROM products
GROUP BY category;`,
ddl: `-- 查看所有表
-- (使用 db.getTableNames() 替代)
-- 创建新表
CREATE TABLE tasks (
id STRING PRIMARY KEY,
title STRING NOT NULL,
done BOOLEAN DEFAULT false,
priority NUMBER DEFAULT 0
);
-- 插入数据
INSERT INTO tasks (id, title, done, priority) VALUES ('t1', 'Learn SQLark', false, 1);
INSERT INTO tasks VALUES ('t2', 'Write docs', true, 2);
-- 查询
SELECT * FROM tasks ORDER BY priority DESC;
-- 删除表
DROP TABLE tasks;`,
crud: `-- 增
INSERT INTO users VALUES ('9', 'Frank', 'frank@demo.com', 40);
-- 查
SELECT * FROM users WHERE name = 'Frank';
-- 改
UPDATE users SET age = 41, email = 'frank2@demo.com' WHERE id = '9';
-- 查(确认)
SELECT * FROM users WHERE id = '9';
-- 删
DELETE FROM users WHERE id = '9';
-- 确认
SELECT COUNT(*) as user_count FROM users;`
};
function loadPreset(name) {
@@ -325,19 +388,12 @@ document.addEventListener('keydown', e => {
}
});
// Tab switching
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', function() {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
this.classList.add('active');
});
});
// Boot
initDB().then(() => {
console.log('✅ MetonaSqlark demo ready');
// Auto-run initial query
setTimeout(runQuery, 200);
console.log('✅ MetonaSqlark v0.1.12 demo ready');
setTimeout(runQuery, 300);
}).catch(err => {
renderError('初始化失败: ' + err.message);
});
</script>
</body>