feat(bitable): 表格复选框、字段图标与单元格样式
- BitableGrid 添加 checkbox 选择列,seq 列改窄为 40px - 单元格高度 44px、表头高度 40px、行 hover 背景、列分隔线 - 字段类型图标加入 ColumnHeaderMenu - date 字段使用 DateDisplay,统一 YYYY-MM-DD 格式 - SelectDisplay chip 圆角与行高微调
This commit is contained in:
parent
729047433d
commit
99a512c3f7
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue