前端:重构 Web UI 代码结构
- 添加 Import Map 支持 Lit 和本地模块的简洁导入 - 创建统一的 API 管理模块 (common/api.js) - 重命名 styles/ 为 common/ 目录 - 修复分页时列选择被重置的问题 - 将 app.js 重命名为 main.js - 所有导入路径使用 ~ 别名映射
This commit is contained in:
@@ -11,6 +11,17 @@
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="stylesheet" href="/static/css/styles.css" />
|
||||
|
||||
<!-- Import Map for Lit and local modules -->
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"lit": "https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js",
|
||||
"lit/": "https://cdn.jsdelivr.net/gh/lit/dist@3/",
|
||||
"~/": "/static/js/"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 应用容器 -->
|
||||
@@ -20,6 +31,6 @@
|
||||
<srdb-modal-dialog></srdb-modal-dialog>
|
||||
|
||||
<!-- 加载 Lit 组件 -->
|
||||
<script type="module" src="/static/js/app.js"></script>
|
||||
<script type="module" src="/static/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
174
webui/static/js/common/api.js
Normal file
174
webui/static/js/common/api.js
Normal file
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* API 请求管理模块
|
||||
* 统一管理所有后端接口请求
|
||||
*/
|
||||
|
||||
const API_BASE = '/api';
|
||||
|
||||
/**
|
||||
* 通用请求处理函数
|
||||
* @param {string} url - 请求 URL
|
||||
* @param {RequestInit} options - fetch 选项
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
async function request(url, options = {}) {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers,
|
||||
},
|
||||
...options,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
error.status = response.status;
|
||||
error.response = response;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('API request failed:', url, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 表相关 API
|
||||
*/
|
||||
export const tableAPI = {
|
||||
/**
|
||||
* 获取所有表列表
|
||||
* @returns {Promise<Array>}
|
||||
*/
|
||||
async list() {
|
||||
return request(`${API_BASE}/tables`);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取表的 Schema
|
||||
* @param {string} tableName - 表名
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async getSchema(tableName) {
|
||||
return request(`${API_BASE}/tables/${tableName}/schema`);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取表数据(分页)
|
||||
* @param {string} tableName - 表名
|
||||
* @param {Object} params - 查询参数
|
||||
* @param {number} params.page - 页码
|
||||
* @param {number} params.pageSize - 每页大小
|
||||
* @param {string} params.select - 选择的列(逗号分隔)
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async getData(tableName, { page = 1, pageSize = 20, select = '' } = {}) {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
pageSize: pageSize.toString(),
|
||||
});
|
||||
|
||||
if (select) {
|
||||
params.append('select', select);
|
||||
}
|
||||
|
||||
return request(`${API_BASE}/tables/${tableName}/data?${params}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取单行数据详情
|
||||
* @param {string} tableName - 表名
|
||||
* @param {number} seq - 序列号
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async getRow(tableName, seq) {
|
||||
return request(`${API_BASE}/tables/${tableName}/data/${seq}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取表的 Manifest 信息
|
||||
* @param {string} tableName - 表名
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async getManifest(tableName) {
|
||||
return request(`${API_BASE}/tables/${tableName}/manifest`);
|
||||
},
|
||||
|
||||
/**
|
||||
* 插入数据
|
||||
* @param {string} tableName - 表名
|
||||
* @param {Object} data - 数据对象
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async insert(tableName, data) {
|
||||
return request(`${API_BASE}/tables/${tableName}/data`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 批量插入数据
|
||||
* @param {string} tableName - 表名
|
||||
* @param {Array<Object>} data - 数据数组
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async batchInsert(tableName, data) {
|
||||
return request(`${API_BASE}/tables/${tableName}/data/batch`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除表
|
||||
* @param {string} tableName - 表名
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async delete(tableName) {
|
||||
return request(`${API_BASE}/tables/${tableName}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取表统计信息
|
||||
* @param {string} tableName - 表名
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async getStats(tableName) {
|
||||
return request(`${API_BASE}/tables/${tableName}/stats`);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 数据库相关 API
|
||||
*/
|
||||
export const databaseAPI = {
|
||||
/**
|
||||
* 获取数据库信息
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async getInfo() {
|
||||
return request(`${API_BASE}/database/info`);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取数据库统计信息
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async getStats() {
|
||||
return request(`${API_BASE}/database/stats`);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 导出默认 API 对象
|
||||
*/
|
||||
export default {
|
||||
table: tableAPI,
|
||||
database: databaseAPI,
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { css } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js';
|
||||
import { css } from 'lit';
|
||||
|
||||
// 共享的基础样式
|
||||
export const sharedStyles = css`
|
||||
@@ -1,5 +1,5 @@
|
||||
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';
|
||||
import { LitElement, html, css } from 'lit';
|
||||
import { sharedStyles, cssVariables } from '~/common/shared-styles.js';
|
||||
|
||||
export class AppContainer extends LitElement {
|
||||
static properties = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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';
|
||||
import { LitElement, html, css } from 'lit';
|
||||
import { sharedStyles, cssVariables } from '~/common/shared-styles.js';
|
||||
|
||||
export class Badge extends LitElement {
|
||||
static properties = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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';
|
||||
import { LitElement, html, css } from 'lit';
|
||||
import { sharedStyles, cssVariables } from '~/common/shared-styles.js';
|
||||
|
||||
export class DataView extends LitElement {
|
||||
static properties = {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { LitElement, html, css, svg } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js';
|
||||
import { LitElement, html, css } from 'lit';
|
||||
|
||||
export class FieldIcon extends LitElement {
|
||||
static properties = {
|
||||
indexed: { type: Boolean }
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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';
|
||||
import { LitElement, html, css } from 'lit';
|
||||
import { sharedStyles, cssVariables } from '~/common/shared-styles.js';
|
||||
|
||||
export class ManifestView extends LitElement {
|
||||
static properties = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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';
|
||||
import { LitElement, html, css } from 'lit';
|
||||
import { sharedStyles, cssVariables } from '~/common/shared-styles.js';
|
||||
|
||||
export class ModalDialog extends LitElement {
|
||||
static properties = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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';
|
||||
import { LitElement, html, css } from 'lit';
|
||||
import { sharedStyles, cssVariables } from '~/common/shared-styles.js';
|
||||
|
||||
export class PageHeader extends LitElement {
|
||||
static properties = {
|
||||
@@ -247,11 +247,11 @@ export class PageHeader extends LitElement {
|
||||
>
|
||||
<span>Data</span>
|
||||
</button>
|
||||
<button
|
||||
<button
|
||||
class="view-tab ${this.view === 'manifest' ? 'active' : ''}"
|
||||
@click=${() => this.switchView('manifest')}
|
||||
>
|
||||
<span>Manifest / LSM-Tree</span>
|
||||
<span>Manifest / Storage Layers</span>
|
||||
</button>
|
||||
<button class="refresh-btn" @click=${this.refreshView} title="Refresh current view">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
import { LitElement, html, css } from 'lit';
|
||||
import { sharedStyles, cssVariables } from '~/common/shared-styles.js';
|
||||
import { tableAPI } from '~/common/api.js';
|
||||
|
||||
export class TableList extends LitElement {
|
||||
static properties = {
|
||||
@@ -209,9 +210,7 @@ export class TableList extends LitElement {
|
||||
|
||||
async loadTables() {
|
||||
try {
|
||||
const response = await fetch('/api/tables');
|
||||
if (!response.ok) throw new Error('Failed to load tables');
|
||||
this.tables = await response.json();
|
||||
this.tables = await tableAPI.list();
|
||||
} catch (error) {
|
||||
console.error('Error loading tables:', error);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
import { LitElement, html, css } from 'lit';
|
||||
import { sharedStyles, cssVariables } from '~/common/shared-styles.js';
|
||||
import { tableAPI } from '~/common/api.js';
|
||||
|
||||
export class TableView extends LitElement {
|
||||
static properties = {
|
||||
@@ -127,13 +128,20 @@ export class TableView extends LitElement {
|
||||
|
||||
try {
|
||||
// Load schema
|
||||
const schemaResponse = await fetch(`/api/tables/${this.tableName}/schema`);
|
||||
if (!schemaResponse.ok) throw new Error('Failed to load schema');
|
||||
this.schema = await schemaResponse.json();
|
||||
this.schema = await tableAPI.getSchema(this.tableName);
|
||||
|
||||
// Initialize selected columns (all by default)
|
||||
// Initialize selected columns from localStorage or all by default
|
||||
if (this.schema.fields) {
|
||||
this.selectedColumns = this.schema.fields.map(f => f.name);
|
||||
const saved = this.loadSelectedColumns();
|
||||
if (saved && saved.length > 0) {
|
||||
// 验证保存的列是否仍然存在于当前 schema 中
|
||||
const validColumns = saved.filter(col =>
|
||||
this.schema.fields.some(field => field.name === col)
|
||||
);
|
||||
this.selectedColumns = validColumns.length > 0 ? validColumns : this.schema.fields.map(f => f.name);
|
||||
} else {
|
||||
this.selectedColumns = this.schema.fields.map(f => f.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.view === 'data') {
|
||||
@@ -149,24 +157,28 @@ export class TableView extends LitElement {
|
||||
}
|
||||
|
||||
async loadTableData() {
|
||||
const selectParam = this.selectedColumns.join(',');
|
||||
const url = `/api/tables/${this.tableName}/data?page=${this.page}&pageSize=${this.pageSize}&select=${selectParam}`;
|
||||
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error('Failed to load table data');
|
||||
this.tableData = await response.json();
|
||||
this.tableData = await tableAPI.getData(this.tableName, {
|
||||
page: this.page,
|
||||
pageSize: this.pageSize,
|
||||
select: this.selectedColumns.join(',')
|
||||
});
|
||||
}
|
||||
|
||||
async loadManifestData() {
|
||||
const response = await fetch(`/api/tables/${this.tableName}/manifest`);
|
||||
if (!response.ok) throw new Error('Failed to load manifest data');
|
||||
this.manifestData = await response.json();
|
||||
this.manifestData = await tableAPI.getManifest(this.tableName);
|
||||
}
|
||||
|
||||
switchView(newView) {
|
||||
this.view = newView;
|
||||
}
|
||||
|
||||
loadSelectedColumns() {
|
||||
if (!this.tableName) return null;
|
||||
const key = `srdb_columns_${this.tableName}`;
|
||||
const saved = localStorage.getItem(key);
|
||||
return saved ? JSON.parse(saved) : null;
|
||||
}
|
||||
|
||||
toggleColumn(columnName) {
|
||||
const index = this.selectedColumns.indexOf(columnName);
|
||||
if (index > -1) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LitElement, html, css } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js';
|
||||
import { sharedStyles } from '../styles/shared-styles.js';
|
||||
import { LitElement, html, css } from 'lit';
|
||||
import { sharedStyles } from '~/common/shared-styles.js';
|
||||
|
||||
export class ThemeToggle extends LitElement {
|
||||
static properties = {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import './components/app.js';
|
||||
import './components/table-list.js';
|
||||
import './components/table-view.js';
|
||||
import './components/modal-dialog.js';
|
||||
import './components/theme-toggle.js';
|
||||
import './components/badge.js';
|
||||
import './components/field-icon.js';
|
||||
import './components/data-view.js';
|
||||
import './components/manifest-view.js';
|
||||
import './components/page-header.js';
|
||||
import '~/components/app.js';
|
||||
import '~/components/table-list.js';
|
||||
import '~/components/table-view.js';
|
||||
import '~/components/modal-dialog.js';
|
||||
import '~/components/theme-toggle.js';
|
||||
import '~/components/badge.js';
|
||||
import '~/components/field-icon.js';
|
||||
import '~/components/data-view.js';
|
||||
import '~/components/manifest-view.js';
|
||||
import '~/components/page-header.js';
|
||||
import { tableAPI } from '~/common/api.js';
|
||||
|
||||
class App {
|
||||
constructor() {
|
||||
@@ -78,10 +79,7 @@ class App {
|
||||
|
||||
async showRowDetail(tableName, seq) {
|
||||
try {
|
||||
const response = await fetch(`/api/tables/${tableName}/data/${seq}`);
|
||||
if (!response.ok) throw new Error('Failed to load row detail');
|
||||
|
||||
const data = await response.json();
|
||||
const data = await tableAPI.getRow(tableName, seq);
|
||||
const content = JSON.stringify(data, null, 2);
|
||||
|
||||
this.modal.title = `Row Detail - Seq: ${seq}`;
|
||||
Reference in New Issue
Block a user