mirror of
https://github.com/nocodb/nocodb.git
synced 2026-05-01 14:46:53 +00:00
197 lines
5.9 KiB
Vue
197 lines
5.9 KiB
Vue
<script lang="ts" setup>
|
|
import { type ColumnType, isSystemColumn } from 'nocodb-sdk'
|
|
|
|
interface Props {
|
|
tableId?: string
|
|
columnId?: string
|
|
value?: string
|
|
forceLayout?: 'vertical' | 'horizontal'
|
|
filterColumn?: (column: ColumnType) => boolean
|
|
forceLoadTableFields?: boolean
|
|
disableLabel?: boolean
|
|
autoSelect?: boolean
|
|
disabled?: boolean
|
|
allowClear?: boolean
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
forceLoadTableFields: false,
|
|
disableLabel: false,
|
|
autoSelect: false,
|
|
disabled: false,
|
|
allowClear: false,
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
'update:value': [value: string | undefined]
|
|
}>()
|
|
|
|
const { t } = useI18n()
|
|
|
|
const tableStore = useTablesStore()
|
|
const { loadTableMeta } = useTablesStore()
|
|
const { activeTable } = storeToRefs(tableStore)
|
|
|
|
const modelValue = useVModel(props, 'value', emit)
|
|
|
|
const isOpenColumnSelectDropdown = ref(false)
|
|
|
|
const handleValueUpdate = (value: any) => {
|
|
modelValue.value = value
|
|
}
|
|
|
|
const columnList = computedAsync(async () => {
|
|
let fields: ColumnType[]
|
|
|
|
if (props.tableId) {
|
|
const tableMeta = await loadTableMeta(props.tableId)
|
|
fields = tableMeta?.columns || []
|
|
} else {
|
|
fields = activeTable.value?.columns || []
|
|
}
|
|
|
|
if (props.filterColumn) {
|
|
fields = fields.filter(props.filterColumn)
|
|
} else {
|
|
fields = fields.filter((f) => !isSystemColumn(f) || f.pk)
|
|
}
|
|
|
|
return fields.map((column) => {
|
|
const isDisabled = Boolean(column.system && !column.pk)
|
|
|
|
const ncItemTooltip = isDisabled ? t('tooltip.systemColumnNotEditable') : ''
|
|
|
|
return {
|
|
label: column.title || column.column_name,
|
|
value: column.id,
|
|
ncItemDisabled: isDisabled,
|
|
ncItemTooltip,
|
|
...column,
|
|
}
|
|
})
|
|
}, [])
|
|
|
|
const columnListMap = computed(() => {
|
|
if (!columnList.value || columnList.value.length === 0) return new Map()
|
|
|
|
return new Map(columnList.value.map((column) => [column.value, column]))
|
|
})
|
|
|
|
const selectedColumn = computed(() => {
|
|
if (!columnListMap.value || columnListMap.value.size === 0) return undefined
|
|
|
|
return columnListMap.value.get(modelValue.value) || undefined
|
|
})
|
|
|
|
// Watch for columnList changes and set initial value
|
|
watch(
|
|
columnList,
|
|
(newColumnList) => {
|
|
if (newColumnList && newColumnList.length > 0) {
|
|
const newColumnListMap = new Map(newColumnList.map((column) => [column.value, column]))
|
|
|
|
// Check if current value exists in the new column list
|
|
if (modelValue.value && !newColumnListMap.has(modelValue.value)) {
|
|
// Current value is not in the list, set null to clear it
|
|
modelValue.value = undefined
|
|
return
|
|
}
|
|
|
|
// Auto-select logic (only if autoSelect is enabled and no current value)
|
|
if (!modelValue.value && props.autoSelect) {
|
|
const newColumnId = props.columnId || newColumnList[0]?.value
|
|
|
|
const columnObj = newColumnListMap.get(newColumnId)
|
|
|
|
// Change column id only if it is default column selected initially and its not enabled
|
|
if (columnObj && columnObj.ncItemDisabled && columnObj.value === newColumnList[0]?.value) {
|
|
const selectedValue = newColumnList.find((column) => !column.ncItemDisabled)?.value || newColumnList[0]?.value
|
|
modelValue.value = selectedValue
|
|
} else {
|
|
modelValue.value = newColumnId
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{ immediate: true },
|
|
)
|
|
|
|
defineExpose({
|
|
modelValue,
|
|
selectedColumn,
|
|
isOpenColumnSelectDropdown,
|
|
columnList,
|
|
columnListMap,
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<a-form-item
|
|
name="columnId"
|
|
class="!mb-0 nc-column-selector"
|
|
:class="`nc-force-layout-${forceLayout}`"
|
|
:validate-status="selectedColumn?.ncItemDisabled ? 'error' : ''"
|
|
:help="selectedColumn?.ncItemDisabled ? [selectedColumn.ncItemTooltip] : []"
|
|
@click.stop
|
|
@dblclick.stop
|
|
>
|
|
<template v-if="!disableLabel" #label>
|
|
<div>
|
|
<slot name="label">{{ t('objects.column') }}</slot>
|
|
</div>
|
|
</template>
|
|
<NcListDropdown
|
|
v-model:is-open="isOpenColumnSelectDropdown"
|
|
:disabled="disabled"
|
|
:has-error="!!selectedColumn?.ncItemDisabled"
|
|
>
|
|
<div class="flex-1 flex group items-center gap-2 min-w-0">
|
|
<div v-if="selectedColumn" class="min-w-5 flex items-center justify-center">
|
|
<SmartsheetHeaderIcon :column="selectedColumn" color="text-nc-content-gray-muted" />
|
|
</div>
|
|
<NcTooltip hide-on-click class="flex-1 truncate" show-on-truncate-only>
|
|
<span v-if="selectedColumn" class="text-sm flex-1 truncate text-nc-content-gray-default">
|
|
{{ selectedColumn?.label }}
|
|
</span>
|
|
<span v-else class="text-sm flex-1 truncate text-nc-content-gray-muted">-- Select field --</span>
|
|
|
|
<template #title>
|
|
{{ selectedColumn?.label || 'Select field' }}
|
|
</template>
|
|
</NcTooltip>
|
|
|
|
<GeneralIcon
|
|
v-if="selectedColumn && allowClear"
|
|
class="hidden text-nc-content-gray-muted transition group-hover:!block h-4 w-4 cursor-pointer"
|
|
icon="ncXCircle"
|
|
@click.stop="handleValueUpdate(null)"
|
|
/>
|
|
|
|
<GeneralIcon
|
|
icon="ncChevronDown"
|
|
class="flex-none h-4 w-4 transition-transform opacity-70"
|
|
:class="{ 'transform rotate-180': isOpenColumnSelectDropdown }"
|
|
/>
|
|
</div>
|
|
<template #overlay="{ onEsc }">
|
|
<NcList
|
|
v-model:open="isOpenColumnSelectDropdown"
|
|
:value="modelValue || selectedColumn?.value || ''"
|
|
:list="columnList"
|
|
variant="medium"
|
|
class="!w-auto"
|
|
wrapper-class-name="!h-auto"
|
|
@update:value="handleValueUpdate"
|
|
@escape="onEsc"
|
|
>
|
|
<template #listItemExtraLeft="{ option }">
|
|
<div class="min-w-5 flex items-center justify-center">
|
|
<SmartsheetHeaderIcon :column="option as ColumnType" color="text-nc-content-gray-muted" />
|
|
</div>
|
|
</template>
|
|
</NcList>
|
|
</template>
|
|
</NcListDropdown>
|
|
</a-form-item>
|
|
</template>
|