Files
srdb/webui/static/js/components/table-list.js
bourdon 23843493b8 重构代码结构并添加完整功能
主要改动:
- 重构目录结构:合并子目录到根目录,简化项目结构
- 添加完整的查询 API:支持复杂条件查询、字段选择、游标模式
- 实现 LSM-Tree Compaction:7层结构、Score-based策略、后台异步合并
- 添加 Web UI:基于 Lit 的现代化管理界面,支持数据浏览和 Manifest 查看
- 完善文档:添加 README.md 和 examples/webui/README.md

新增功能:
- Query Builder:链式查询 API,支持 Eq/Lt/Gt/In/Between/Contains 等操作符
- Web UI 组件:srdb-app、srdb-table-list、srdb-data-view、srdb-manifest-view 等
- 列选择持久化:自动保存到 localStorage
- 刷新按钮:一键刷新当前视图
- 主题切换:深色/浅色主题支持

代码优化:
- 使用 Go 1.24 新特性:range 7、min()、maps.Copy()、slices.Sort()
- 统一组件命名:所有 Web Components 使用 srdb-* 前缀
- CSS 优化:提取共享样式,减少重复代码
- 清理遗留代码:删除未使用的方法和样式
2025-10-08 23:04:47 +08:00

285 lines
6.5 KiB
JavaScript

import { LitElement, html, css } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js';
import { sharedStyles, cssVariables } from '../styles/shared-styles.js';
export class TableList extends LitElement {
static properties = {
tables: { type: Array },
selectedTable: { type: String },
expandedTables: { type: Set }
};
static styles = [
sharedStyles,
cssVariables,
css`
:host {
display: block;
width: 100%;
}
.table-item {
margin-bottom: 8px;
background: var(--bg-elevated);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
overflow: hidden;
transition: var(--transition);
}
.table-item:hover {
border-color: var(--border-hover);
}
.table-item.selected {
border-color: var(--primary);
box-shadow: 0 0 0 1px var(--primary);
}
.table-item.selected .table-header {
background: var(--primary-bg);
}
.table-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
cursor: pointer;
transition: var(--transition);
}
.table-header:hover {
background: var(--bg-hover);
}
.table-item.has-expanded .table-header {
border-bottom-color: var(--border-color);
}
.table-header-left {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
min-width: 0;
}
.expand-icon {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
font-size: 12px;
transition: var(--transition);
flex-shrink: 0;
border-radius: var(--radius-sm);
cursor: pointer;
color: var(--text-secondary);
}
.expand-icon:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.expand-icon.expanded {
transform: rotate(90deg);
color: var(--primary);
}
.table-name {
font-weight: 500;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.table-count {
font-size: 12px;
color: var(--text-tertiary);
white-space: nowrap;
flex-shrink: 0;
}
/* Schema 字段列表 */
.schema-fields {
display: none;
flex-direction: column;
border-top: 1px solid transparent;
transition: var(--transition);
position: relative;
}
.schema-fields.expanded {
display: block;
border-top-color: var(--border-color);
}
/* 共享的垂直线 */
.schema-fields.expanded::before {
z-index: 2;
content: "";
position: absolute;
left: 24px;
top: 0;
bottom: 24px;
width: 1px;
background: var(--border-color);
}
.field-item {
z-index: 1;
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px 8px 24px;
font-size: 12px;
transition: var(--transition);
position: relative;
}
/* 每个字段的水平线 */
.field-item::before {
content: "";
width: 8px;
height: 1px;
background: var(--border-color);
}
.field-item:hover {
background: var(--bg-hover);
}
.field-item:last-child {
padding-bottom: 12px;
}
.field-index-icon {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
font-size: 14px;
flex-shrink: 0;
}
.field-index-icon.indexed {
color: var(--success);
}
.field-index-icon.not-indexed {
color: var(--text-tertiary);
opacity: 0.5;
}
.field-name {
font-weight: 500;
color: var(--text-secondary);
flex: 1;
}
.field-type {
font-family: 'Courier New', monospace;
}
.loading {
padding: 20px;
}
.error {
text-align: center;
padding: 20px;
color: var(--danger);
}
`
];
constructor() {
super();
this.tables = [];
this.selectedTable = '';
this.expandedTables = new Set();
}
connectedCallback() {
super.connectedCallback();
this.loadTables();
}
async loadTables() {
try {
const response = await fetch('/api/tables');
if (!response.ok) throw new Error('Failed to load tables');
this.tables = await response.json();
} catch (error) {
console.error('Error loading tables:', error);
}
}
toggleExpand(tableName, event) {
event.stopPropagation();
if (this.expandedTables.has(tableName)) {
this.expandedTables.delete(tableName);
} else {
this.expandedTables.add(tableName);
}
this.requestUpdate();
}
selectTable(tableName) {
this.selectedTable = tableName;
this.dispatchEvent(new CustomEvent('table-selected', {
detail: { tableName },
bubbles: true,
composed: true
}));
}
render() {
if (this.tables.length === 0) {
return html`<div class="loading">Loading tables...</div>`;
}
return html`
${this.tables.map(table => html`
<div class="table-item ${this.expandedTables.has(table.name) ? 'has-expanded' : ''} ${this.selectedTable === table.name ? 'selected' : ''}">
<div
class="table-header"
@click=${() => this.selectTable(table.name)}
>
<div class="table-header-left">
<span
class="expand-icon ${this.expandedTables.has(table.name) ? 'expanded' : ''}"
@click=${(e) => this.toggleExpand(table.name, e)}
>
</span>
<span class="table-name">${table.name}</span>
</div>
<span class="table-count">${table.fields.length} fields</span>
</div>
<div class="schema-fields ${this.expandedTables.has(table.name) ? 'expanded' : ''}">
${table.fields.map(field => html`
<div class="field-item">
<srdb-field-icon
?indexed=${field.indexed}
class="field-index-icon"
title="${field.indexed ? 'Indexed field (fast)' : 'Not indexed (slow)'}"
></srdb-field-icon>
<span class="field-name">${field.name}</span>
<srdb-badge variant="primary" class="field-type">
${field.type}
</srdb-badge>
</div>
`)}
</div>
</div>
`)}
`;
}
}
customElements.define('srdb-table-list', TableList);