fix: 顶栏文案去重与文档/连库体验更新
顶栏定为:连接 · 新建连接 · 新建查询 · 查询历史 · 快捷键; 同步 docs/落地页、对象树与 connection-types 注册表整理。
This commit is contained in:
1 parent
f8740d27f7
commit
26f459d1b8
49 files changed
+2682
-492
No files matched your search
@@ -1,8 +1,63 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { execSql } from "./api";
|
||||
import { ensureSession } from "./ObjectTree";
|
||||
import { useWorkspaceStore } from "./store";
|
||||
|
||||
type MetaRow = Record<string, unknown>;
|
||||
|
||||
type TableMeta = {
|
||||
indexes: MetaRow[];
|
||||
fks: MetaRow[];
|
||||
triggers: MetaRow[];
|
||||
};
|
||||
|
||||
const EMPTY_META: TableMeta = { indexes: [], fks: [], triggers: [] };
|
||||
|
||||
/** MySQL 协议族才走 information_schema 元数据(PG/Oracle 的系统目录另行适配) */
|
||||
function isMysqlFamily(driver?: string) {
|
||||
return ["mysql", "mariadb", "tidb", "oceanbase", "polardb", "tdsql"].includes((driver || "").toLowerCase());
|
||||
}
|
||||
|
||||
/** 标识符转义进 information_schema 字符串比较 */
|
||||
const esc = (s: string) => s.replace(/'/g, "''");
|
||||
|
||||
export function SchemaPane() {
|
||||
const schema = useWorkspaceStore((s) => s.schemaView);
|
||||
const set = useWorkspaceStore((s) => s.set);
|
||||
const conn = useWorkspaceStore((s) => s.connections.find((c) => c.id === s.activeConnectionId));
|
||||
const [meta, setMeta] = useState<TableMeta>(EMPTY_META);
|
||||
const mysql = isMysqlFamily(conn?.driver);
|
||||
|
||||
useEffect(() => {
|
||||
let alive = true;
|
||||
setMeta(EMPTY_META);
|
||||
if (!conn || !mysql || !schema || schema.loading || schema.error || !schema.table) return;
|
||||
(async () => {
|
||||
try {
|
||||
const sid = await ensureSession(conn);
|
||||
const db = esc(schema.database);
|
||||
const t = esc(schema.table);
|
||||
const run = (sqlText: string) => execSql(conn, sqlText, { database: schema.database, sessionId: sid, limit: 200 });
|
||||
const [ix, fk, tg] = await Promise.all([
|
||||
run(
|
||||
`SELECT INDEX_NAME AS name, IF(NON_UNIQUE=0,'UNIQUE','') AS uniq, GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX SEPARATOR ', ') AS cols FROM information_schema.STATISTICS WHERE TABLE_SCHEMA='${db}' AND TABLE_NAME='${t}' GROUP BY INDEX_NAME, NON_UNIQUE ORDER BY INDEX_NAME`,
|
||||
),
|
||||
run(
|
||||
`SELECT CONSTRAINT_NAME AS name, COLUMN_NAME AS col, REFERENCED_TABLE_NAME AS refTable, REFERENCED_COLUMN_NAME AS refCol FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA='${db}' AND TABLE_NAME='${t}' AND REFERENCED_TABLE_NAME IS NOT NULL`,
|
||||
),
|
||||
run(
|
||||
`SELECT TRIGGER_NAME AS name, ACTION_TIMING AS timing, EVENT_MANIPULATION AS event, EVENT_OBJECT_TABLE AS onTable FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA='${db}' AND EVENT_OBJECT_TABLE='${t}'`,
|
||||
),
|
||||
]);
|
||||
if (alive) setMeta({ indexes: ix.rows, fks: fk.rows, triggers: tg.rows });
|
||||
} catch {
|
||||
/* 元数据不可用时静默降级:结构页至少保底显示列 */
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
alive = false;
|
||||
};
|
||||
}, [conn, mysql, schema?.database, schema?.table, schema?.loading, schema?.error]);
|
||||
|
||||
if (!schema) {
|
||||
return (
|
||||
@@ -36,6 +91,8 @@ export function SchemaPane() {
|
||||
);
|
||||
}
|
||||
|
||||
const str = (v: unknown) => (v == null ? "" : String(v));
|
||||
|
||||
return (
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<div className="ops-section-label justify-between border-b">
|
||||
@@ -86,6 +143,96 @@ export function SchemaPane() {
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
|
||||
{mysql && (
|
||||
<div className="border-t" style={{ borderColor: "var(--ops-border)" }}>
|
||||
<details open className="px-3 py-1.5">
|
||||
<summary className="cursor-pointer text-[12.5px] font-medium" style={{ listStyle: "none" }}>
|
||||
索引({meta.indexes.length})
|
||||
</summary>
|
||||
{meta.indexes.length === 0 ? (
|
||||
<div className="px-1 py-1 text-[11.5px]" style={{ color: "var(--ops-muted)" }}>无索引</div>
|
||||
) : (
|
||||
<table className="ops-grid" style={{ width: "100%" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>索引名</th>
|
||||
<th style={{ width: 70 }}>唯一</th>
|
||||
<th>列</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{meta.indexes.map((r, i) => (
|
||||
<tr key={i}>
|
||||
<td className="font-mono text-[12px]">{str(r.name)}</td>
|
||||
<td>{r.uniq ? "✓" : ""}</td>
|
||||
<td className="font-mono text-[12px]" style={{ color: "var(--ops-muted)" }}>{str(r.cols)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</details>
|
||||
|
||||
<details open className="border-t px-3 py-1.5" style={{ borderColor: "var(--ops-border)" }}>
|
||||
<summary className="cursor-pointer text-[12.5px] font-medium" style={{ listStyle: "none" }}>
|
||||
外键({meta.fks.length})
|
||||
</summary>
|
||||
{meta.fks.length === 0 ? (
|
||||
<div className="px-1 py-1 text-[11.5px]" style={{ color: "var(--ops-muted)" }}>无外键</div>
|
||||
) : (
|
||||
<table className="ops-grid" style={{ width: "100%" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>约束名</th>
|
||||
<th>列</th>
|
||||
<th>引用</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{meta.fks.map((r, i) => (
|
||||
<tr key={i}>
|
||||
<td className="font-mono text-[12px]">{str(r.name)}</td>
|
||||
<td className="font-mono text-[12px]">{str(r.col)}</td>
|
||||
<td className="font-mono text-[12px]" style={{ color: "var(--ops-muted)" }}>
|
||||
{str(r.refTable)}.{str(r.refCol)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</details>
|
||||
|
||||
<details open className="border-t px-3 py-1.5" style={{ borderColor: "var(--ops-border)" }}>
|
||||
<summary className="cursor-pointer text-[12.5px] font-medium" style={{ listStyle: "none" }}>
|
||||
触发器({meta.triggers.length})
|
||||
</summary>
|
||||
{meta.triggers.length === 0 ? (
|
||||
<div className="px-1 py-1 text-[11.5px]" style={{ color: "var(--ops-muted)" }}>无触发器</div>
|
||||
) : (
|
||||
<table className="ops-grid" style={{ width: "100%" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>触发器</th>
|
||||
<th style={{ width: 80 }}>时机</th>
|
||||
<th style={{ width: 80 }}>事件</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{meta.triggers.map((r, i) => (
|
||||
<tr key={i}>
|
||||
<td className="font-mono text-[12px]">{str(r.name)}</td>
|
||||
<td>{str(r.timing)}</td>
|
||||
<td>{str(r.event)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</details>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user