feat: 更新 table 与 WebUI 静态资源
This commit is contained in:
@@ -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;
|
||||
|
||||
// 延迟隐藏,给用户足够时间移动鼠标到 popover(300ms)
|
||||
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)}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user