diff --git a/web/public/help-img/db.webp b/web/public/help-img/db.webp index 7f5e795..642cf8b 100644 Binary files a/web/public/help-img/db.webp and b/web/public/help-img/db.webp differ diff --git a/web/src/database/ObjectExplorer.vue b/web/src/database/ObjectExplorer.vue index d52f8f8..d571788 100644 --- a/web/src/database/ObjectExplorer.vue +++ b/web/src/database/ObjectExplorer.vue @@ -87,12 +87,16 @@ function columnsTip(table: ExplorerTable) {
{{ collapsed.has(db.name) ? '▸' : '▾' }} + {{ db.name }} {{ db.tables.length }} 张表
- +
@@ -171,7 +179,7 @@ function columnsTip(table: ExplorerTable) { } .db + .db { - margin-top: 4px; + margin-top: 8px; } .row { @@ -206,18 +214,47 @@ function columnsTip(table: ExplorerTable) { font-weight: 600; } +.icon { + flex: none; + width: 16px; + height: 16px; + fill: none; + stroke-width: 1.3; + stroke-linecap: round; + stroke-linejoin: round; +} + +.db-icon { + stroke: #2563eb; +} + +.db-icon ellipse { + fill: #dbeafe; +} + +.table-icon { + stroke: #6b7280; +} + +.table-icon rect { + fill: #f9fafb; +} + +/* 表挂在库下面:往里缩进,左边一条竖线对齐库图标 */ +.tables { + margin-left: 33px; + padding-left: 6px; + border-left: 1px solid #e5e7eb; +} + .count { font-size: 12px; color: #9ca3af; } -.table-row, -.new-table { - padding-left: 26px; -} - .new-table { color: #2563eb; + padding-left: 30px; } /* ⋯ 平时浅一点,鼠标移到这一行再变深 */ diff --git a/web/src/database/completion.test.ts b/web/src/database/completion.test.ts index afbf8b8..4761c6b 100644 --- a/web/src/database/completion.test.ts +++ b/web/src/database/completion.test.ts @@ -44,9 +44,12 @@ describe('查询窗口的自动补全', () => { expect(sorted('SELECT * FROM 用户表, |')[0]).toBe('CampusEnvironment') }) - test('语句里用到的表的列排在其他列前面', () => { - const columns = sorted('SELECT | FROM 用户表').filter((label) => ['姓名', 'light'].includes(label)) - expect(columns).toEqual(['姓名', 'light']) + test('写了表名后只列用到的表的列,还没写时列所有表的列', () => { + expect(labels('SELECT | FROM 用户表')).toContain('姓名') + expect(labels('SELECT | FROM 用户表')).not.toContain('light') + expect(labels('SELECT | FROM 用户表 u JOIN CampusEnvironment e ON u.ID = e.ID')).toContain('light') + expect(labels('SELECT |')).toContain('light') + expect(labels('SELECT |')).toContain('姓名') }) test('空格触发:只在要写表名的地方弹出', () => { diff --git a/web/src/database/completion.ts b/web/src/database/completion.ts index 2b46c6f..e3aa211 100644 --- a/web/src/database/completion.ts +++ b/web/src/database/completion.ts @@ -324,17 +324,16 @@ function tableSuggestions(tables: ExplorerTable[], bracketOpen: boolean, rank: s return tables.map((table) => ({ label: table.name, kind: 'table', insert: nameText(table.name, bracketOpen), detail: '表', sort: rank + table.name })) } -/** 所有表的列(同名的列只列一次);语句里用到的表的列排在前面 */ +/** 查询窗口里用到的表(FROM / JOIN 等后面写了的)的列;还没写表名时列所有表的列。同名的列只列一次 */ function columnSuggestions(tables: ExplorerTable[], refs: TableRef[], bracketOpen: boolean): SqlSuggestion[] { const used = new Set(refs.map((r) => r.table)) - const ordered = [...tables.filter((t) => used.has(t)), ...tables.filter((t) => !used.has(t))] const columns = new Map() - for (const table of ordered) { + for (const table of used.size > 0 ? tables.filter((t) => used.has(t)) : tables) { for (const column of table.columns) { const key = column.name.toUpperCase() const existing = columns.get(key) if (existing) existing.tables.push(table.name) - else columns.set(key, { column, tables: [table.name], sort: `${used.has(table) ? 1 : 2}${pad(columns.size)}` }) + else columns.set(key, { column, tables: [table.name], sort: `1${pad(columns.size)}` }) } } return [...columns.values()].map(({ column, tables, sort }) => ({ diff --git a/web/src/designer/FormDesigner.vue b/web/src/designer/FormDesigner.vue index 913a6cb..c921020 100644 --- a/web/src/designer/FormDesigner.vue +++ b/web/src/designer/FormDesigner.vue @@ -677,7 +677,9 @@ const noop = () => {} } .actions { + container-type: inline-size; display: flex; + flex-wrap: wrap; align-items: center; gap: 6px; font-size: 12px; @@ -713,6 +715,8 @@ const noop = () => {} } .tips { + /* 提示文字不占位置:画布窄时不显示(只留作空白把缩放推到右边),按钮放不下再折到第二行,不撑出横向滚动条 */ + flex: 1 1 0; min-width: 0; margin-left: 8px; overflow: hidden; @@ -721,6 +725,12 @@ const noop = () => {} color: #7a838e; } +@container (max-width: 560px) { + .tips { + visibility: hidden; + } +} + .window { align-self: flex-start; border: 1px solid #9aa3ad; diff --git a/web/src/pages/DatabasePage.vue b/web/src/pages/DatabasePage.vue index f26e4d5..45dcfd7 100644 --- a/web/src/pages/DatabasePage.vue +++ b/web/src/pages/DatabasePage.vue @@ -296,13 +296,10 @@ onMounted(refresh) flex-direction: column; } -.tabs :deep(.n-tabs-pane-wrapper) { +/* 标签页内容直接是 .tabs 的子元素(display-directive="show"):占标签栏下面剩下的高度,不能用 height: 100%,否则多出标签栏的高度,整页出滚动条 */ +.tabs :deep(.n-tab-pane) { flex: 1; min-height: 0; -} - -.tabs :deep(.n-tab-pane) { - height: 100%; padding: 0; background: #fff; }