数据库页面:对象资源管理器加库和表的图标,表缩进挂在库下面(左边竖线),一眼分得清层级;标签页内容改成占标签栏下面剩下的高度,不再多出 39px 撑出整页滚动条;查询补全写了 FROM / JOIN 后只列用到的表的列。设计器画布窄时操作栏折行、不显示提示文字,不再撑出横向滚动条。使用说明的数据库截图同步
CI/CD / test (push) Canceled after 0s
CI/CD / deploy (push) Canceled after 0s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-27 16:06:13 +08:00
1 parent f9a2739353
commit e177753ad6
6 files changed
+66 -20

No files matched your search

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 27 KiB

+45 -8
View File
@@ -87,12 +87,16 @@ function columnsTip(table: ExplorerTable) {
<div v-for="db in databases" :key="db.name" class="db">
<div class="row db-row" @click="toggle(db.name)" @contextmenu="databaseMenu($event, db.name)">
<span class="arrow">{{ collapsed.has(db.name) ? '▸' : '▾' }}</span>
<svg class="icon db-icon" viewBox="0 0 16 16" aria-hidden="true">
<ellipse cx="8" cy="3.5" rx="5.5" ry="2" />
<path d="M2.5 3.5v9c0 1.1 2.5 2 5.5 2s5.5-.9 5.5-2v-9M2.5 8c0 1.1 2.5 2 5.5 2s5.5-.9 5.5-2" />
</svg>
<span class="name">{{ db.name }}</span>
<span class="count">{{ db.tables.length }} 张表</span>
<button class="more" title="更多操作" @click="databaseMenu($event, db.name)">⋯</button>
</div>
<template v-if="!collapsed.has(db.name)">
<div v-if="!collapsed.has(db.name)" class="tables">
<div
v-for="table in db.tables"
:key="table.name"
@@ -101,11 +105,15 @@ function columnsTip(table: ExplorerTable) {
@click="emit('action', { kind: 'editRows', database: db.name, table })"
@contextmenu="tableMenu($event, db.name, table)"
>
<svg class="icon table-icon" viewBox="0 0 16 16" aria-hidden="true">
<rect x="2" y="3" width="12" height="10" rx="1.5" />
<path d="M2 6.5h12M6.5 6.5V13" />
</svg>
<span class="name">{{ table.name }}</span>
<button class="more" title="更多操作" @click="tableMenu($event, db.name, table)">⋯</button>
</div>
<div class="row new-table" @click="emit('action', { kind: 'newTable', database: db.name })">+ 新建表</div>
</template>
</div>
</div>
</div>
@@ -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;
}
/* ⋯ 平时浅一点,鼠标移到这一行再变深 */
+6 -3
View File
@@ -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('空格触发:只在要写表名的地方弹出', () => {
+3 -4
View File
@@ -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<string, { column: ExplorerTable['columns'][number]; tables: string[]; sort: string }>()
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 }) => ({
+10
View File
@@ -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;
+2 -5
View File
@@ -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;
}