feat(bitable): 表格复选框、字段图标与单元格样式

- BitableGrid 添加 checkbox 选择列,seq 列改窄为 40px
- 单元格高度 44px、表头高度 40px、行 hover 背景、列分隔线
- 字段类型图标加入 ColumnHeaderMenu
- date 字段使用 DateDisplay,统一 YYYY-MM-DD 格式
- SelectDisplay chip 圆角与行高微调
This commit is contained in:
Chiguyong 2026-07-06 18:46:21 +08:00
parent 729047433d
commit 99a512c3f7
4 changed files with 68 additions and 9 deletions

View File

@ -35,6 +35,8 @@
:height="groupingEnabled ? 'auto' : height"
:loading="loading"
:row-config="rowConfig"
:cell-config="{ height: 44 }"
:header-config="{ height: 40 }"
:column-config="{ resizable: true }"
:virtual-y-config="{ enabled: !groupingEnabled, gt: 60 }"
:virtual-x-config="{ enabled: true, gt: 20 }"
@ -46,6 +48,7 @@
}"
@edit-closed="onEditClosed"
@cell-click="onCellClick"
@cell-dblclick="onCellDblclick"
>
<template #empty>
<a-empty :description="emptyText" />
@ -120,6 +123,14 @@
:multiple="f.field_type === 'multiselect'"
/>
</template>
<!-- Date display slots -->
<template
v-for="f in dateFields"
:key="`cell_date_${f.id}`"
#[`cell_date_${f.id}`]="{ row }"
>
<DateDisplay :value="(row[f.id] as string | null | undefined)" />
</template>
<!-- Add-field column header -->
<template #header__add_field>
<div class="bitable-grid-scope__add-col" @click="emit('add-field')">
@ -160,6 +171,7 @@ import ImageCell from './ImageCell.vue'
import ColumnHeaderMenu from './ColumnHeaderMenu.vue'
import SelectCellEditor from './SelectCellEditor.vue'
import SelectDisplay from './SelectDisplay.vue'
import DateDisplay from './DateDisplay.vue'
import InlineFieldConfigurator from './InlineFieldConfigurator.vue'
import RecordDetailDrawer from './RecordDetailDrawer.vue'
import type { ISelectOption } from './SelectCellEditor.vue'
@ -354,6 +366,15 @@ const onCellClick: VxeGridEvents.CellClick = (params) => {
store.openRecordDetail(recordId)
}
// U3: double-clicking any data cell also opens the record detail drawer
// (Twenty-style row affordance; seq column remains the primary indicator).
const onCellDblclick: VxeGridEvents.CellDblclick = (params) => {
const { row } = params
const recordId = (row as GridRow)._recordId
if (!recordId) return
store.openRecordDetail(recordId)
}
function onDrawerRetry(recordId: string): void {
store.fetchRecordDetail(recordId)
}
@ -389,6 +410,9 @@ const selectFields = computed(() =>
),
)
// Fields that use date display slots
const dateFields = computed(() => props.fields.filter((f) => f.field_type === 'date'))
// Map records to grid rows with a stable _rowId (record.id)
const rows = computed<GridRow[]>(() =>
props.records.map((r) => ({
@ -401,11 +425,18 @@ const rows = computed<GridRow[]>(() =>
// Build vxe-grid columns from field definitions
const gridColumns = computed<GridColumn[]>(() => {
const cols: GridColumn[] = [
{
type: 'checkbox',
width: 40,
fixed: 'left',
resizable: false,
},
{
type: 'seq',
width: 56,
width: 40,
fixed: 'left',
title: '#',
resizable: false,
},
]
@ -472,6 +503,10 @@ function buildColumn(f: IBitableField): GridColumn {
enabled: true,
name: 'VxeDatePicker',
},
slots: {
...base.slots,
default: `cell_date_${f.id}`,
},
}
case 'select':
case 'multiselect':
@ -561,6 +596,29 @@ defineExpose({
cursor: pointer;
}
/* Twenty-style row hover + header separator */
.bitable-grid-scope :deep(.vxe-body--row:hover) {
background-color: var(--bitable-color-bg-tertiary);
}
.bitable-grid-scope :deep(.vxe-header--row) {
height: 40px;
}
.bitable-grid-scope :deep(.vxe-header--column) {
border-bottom: 2px solid var(--bitable-color-border-hover);
}
.bitable-grid-scope :deep(.vxe-body--column),
.bitable-grid-scope :deep(.vxe-header--column) {
border-right: 1px solid var(--bitable-color-border);
}
.bitable-grid-scope :deep(.vxe-body--column:last-child),
.bitable-grid-scope :deep(.vxe-header--column:last-child) {
border-right: 0;
}
.bitable-grid-scope__add-col {
display: flex;
align-items: center;

View File

@ -10,6 +10,7 @@
@keydown.enter.prevent="open = true"
@keydown.space.prevent="open = true"
>
<FieldTypeIcon :type="field.field_type" />
<span class="column-header-menu__title">{{ field.name }}</span>
<DownOutlined class="column-header-menu__arrow" />
</div>
@ -43,6 +44,7 @@ import {
AppstoreOutlined,
} from '@ant-design/icons-vue'
import type { IBitableField } from '@/api/bitable'
import FieldTypeIcon from './FieldTypeIcon.vue'
const props = defineProps<{
field: IBitableField

View File

@ -13,11 +13,10 @@ const formatted = computed(() => {
if (!props.value) return ''
const d = new Date(props.value)
if (Number.isNaN(d.getTime())) return String(props.value)
return d.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
})
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day}`
})
</script>

View File

@ -108,11 +108,11 @@ function chipStyle(key: ColorKey): Record<string, string> {
.select-display__chip {
display: inline-flex;
align-items: center;
padding: 0 var(--bitable-spacing-sm);
border-radius: var(--bitable-radius-sm);
padding: 0 6px;
border-radius: var(--bitable-radius-md);
border: 1px solid;
font-size: var(--bitable-font-xs);
line-height: 1.5;
line-height: 1.4;
white-space: nowrap;
}
</style>