feat: 更新 table 与 WebUI 静态资源

This commit is contained in:
2025-10-12 06:15:21 +08:00
parent c7cb1ae6c6
commit fc7eeb3696
4 changed files with 277 additions and 33 deletions

View File

@@ -1,5 +1,6 @@
import { LitElement, html, css } from 'lit';
import { sharedStyles, cssVariables } from '~/common/shared-styles.js';
import domAlign from 'dom-align';
export class DataView extends LitElement {
static properties = {
@@ -127,11 +128,7 @@ export class DataView extends LitElement {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.data-table td:hover {
white-space: normal;
word-break: break-all;
cursor: pointer;
}
.data-table tr:hover {
@@ -167,6 +164,113 @@ export class DataView extends LitElement {
this.tableData = null;
this.selectedColumns = [];
this.loading = false;
this.hidePopoverTimeout = null;
this.popoverElement = null;
this.themeObserver = null;
}
connectedCallback() {
super.connectedCallback();
// 创建 popover 元素并添加到 body
this.createPopoverElement();
}
disconnectedCallback() {
super.disconnectedCallback();
// 移除 popover 元素
this.removePopoverElement();
}
createPopoverElement() {
if (this.popoverElement) return;
this.popoverElement = document.createElement('div');
this.popoverElement.className = 'srdb-cell-popover';
// 从 CSS 变量中获取主题颜色
this.updatePopoverTheme();
// 添加滚动条样式(使用 CSS 变量)
if (!document.getElementById('srdb-popover-scrollbar-style')) {
const style = document.createElement('style');
style.id = 'srdb-popover-scrollbar-style';
style.textContent = `
.srdb-cell-popover::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.srdb-cell-popover::-webkit-scrollbar-track {
background: var(--bg-surface);
border-radius: 4px;
}
.srdb-cell-popover::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 4px;
}
.srdb-cell-popover::-webkit-scrollbar-thumb:hover {
background: var(--border-hover);
}
`;
document.head.appendChild(style);
}
this.popoverElement.addEventListener('mouseenter', () => this.keepPopover());
this.popoverElement.addEventListener('mouseleave', () => this.hidePopover());
document.body.appendChild(this.popoverElement);
// 监听主题变化
this.themeObserver = new MutationObserver(() => {
this.updatePopoverTheme();
});
this.themeObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-theme']
});
}
updatePopoverTheme() {
if (!this.popoverElement) return;
const rootStyles = getComputedStyle(document.documentElement);
const bgElevated = rootStyles.getPropertyValue('--bg-elevated').trim();
const textPrimary = rootStyles.getPropertyValue('--text-primary').trim();
const borderColor = rootStyles.getPropertyValue('--border-color').trim();
const shadowMd = rootStyles.getPropertyValue('--shadow-md').trim();
const radiusMd = rootStyles.getPropertyValue('--radius-md').trim();
this.popoverElement.style.cssText = `
position: fixed;
z-index: 9999;
background: ${bgElevated};
border: 1px solid ${borderColor};
border-radius: ${radiusMd};
box-shadow: ${shadowMd};
padding: 12px;
max-width: 500px;
max-height: 400px;
overflow: auto;
font-size: 13px;
color: ${textPrimary};
white-space: pre-wrap;
word-break: break-word;
font-family: 'Courier New', monospace;
opacity: 0;
transition: opacity 0.15s ease-in-out, background 0.3s ease, color 0.3s ease, border-color 0.3s ease;
display: none;
pointer-events: auto;
`;
}
removePopoverElement() {
if (this.popoverElement) {
this.popoverElement.remove();
this.popoverElement = null;
}
if (this.themeObserver) {
this.themeObserver.disconnect();
this.themeObserver = null;
}
}
updated(changedProperties) {
@@ -244,6 +348,97 @@ export class DataView extends LitElement {
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
formatValue(value) {
// 处理 null 和 undefined
if (value === null) return 'null';
if (value === undefined) return 'undefined';
// 处理 Object 和 Array 类型
if (typeof value === 'object') {
try {
return JSON.stringify(value);
} catch (e) {
return '[Object]';
}
}
// 其他类型直接返回
return value;
}
showPopover(event, value, col) {
if (!this.popoverElement) return;
// 清除之前的隐藏定时器
if (this.hidePopoverTimeout) {
clearTimeout(this.hidePopoverTimeout);
this.hidePopoverTimeout = null;
}
// 格式化值
let content = col === '_time' ? this.formatTime(value) : this.formatValue(value);
// 对于 JSON 对象/数组,格式化显示
if (typeof value === 'object' && value !== null) {
try {
content = JSON.stringify(value, null, 2);
} catch (e) {
content = String(content);
}
} else {
content = String(content);
}
// 只在内容较长时显示 popover
if (content.length < 50) {
return;
}
// 更新 popover 内容
this.popoverElement.textContent = content;
this.popoverElement.style.display = 'block';
// 使用 dom-align 进行智能定位
// 减小间隙到 2px方便鼠标移入
domAlign(this.popoverElement, event.target, {
points: ['tl', 'tr'], // popover左上角 对齐到 单元格右上角
offset: [2, 0], // 右侧间距仅2px便于鼠标移入
overflow: { adjustX: true, adjustY: true }
});
// 使用 setTimeout 确保 dom-align 完成后再显示
setTimeout(() => {
if (this.popoverElement) {
this.popoverElement.style.opacity = '1';
}
}, 10);
}
hidePopover() {
if (!this.popoverElement) return;
// 延迟隐藏,给用户足够时间移动鼠标到 popover300ms
this.hidePopoverTimeout = setTimeout(() => {
if (this.popoverElement) {
this.popoverElement.style.opacity = '0';
// 等待动画完成后再隐藏
setTimeout(() => {
if (this.popoverElement) {
this.popoverElement.style.display = 'none';
}
}, 150);
}
}, 300);
}
keepPopover() {
// 鼠标进入 popover 时,取消隐藏
if (this.hidePopoverTimeout) {
clearTimeout(this.hidePopoverTimeout);
this.hidePopoverTimeout = null;
}
}
getColumns() {
let columns = [];
@@ -274,9 +469,9 @@ export class DataView extends LitElement {
return html`
${this.renderSchemaSection()}
<h3>Data (${this.formatCount(this.tableData.totalRows)} rows)</h3>
${this.tableData.data.length === 0 ? html`
<div class="empty"><p>No data available</p></div>
` : html`
@@ -292,13 +487,16 @@ export class DataView extends LitElement {
${this.tableData.data.map(row => html`
<tr>
${columns.map(col => html`
<td>
${col === '_time' ? this.formatTime(row[col]) : row[col]}
<td
@mouseenter=${(e) => this.showPopover(e, row[col], col)}
@mouseleave=${() => this.hidePopover()}
>
${col === '_time' ? this.formatTime(row[col]) : this.formatValue(row[col])}
${row[col + '_truncated'] ? html`<span class="truncated-icon">✂️</span>` : ''}
</td>
`)}
<td style="text-align: center;">
<button
<button
class="row-detail-btn"
@click=${() => this.showRowDetail(row._seq)}
>