多维表格
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

307 lines
8.4 KiB

<script setup lang="ts">
import {
FormulaDataTypes,
FormulaError,
UITypes,
getUITypesForFormulaDataType,
isHiddenCol,
isVirtualCol,
substituteColumnIdWithAliasInFormula,
validateFormulaAndExtractTreeWithType,
} from 'nocodb-sdk'
import type { ColumnType, FormulaType } from 'nocodb-sdk'
const props = defineProps<{
value: any
}>()
const emit = defineEmits(['update:value'])
const uiTypesNotSupportedInFormulas = [UITypes.QrCode, UITypes.Barcode, UITypes.Button]
const vModel = useVModel(props, 'value', emit)
const { setAdditionalValidations, sqlUi, column, validateInfos } = useColumnCreateStoreOrThrow()
const { t } = useI18n()
const meta = inject(MetaInj, ref())
const { base: activeBase } = storeToRefs(useBase())
const supportedColumns = computed(
() =>
meta?.value?.columns?.filter((col) => {
if (uiTypesNotSupportedInFormulas.includes(col.uidt as UITypes)) {
return false
}
if (isHiddenCol(col, meta.value)) {
return false
}
return true
}) || [],
)
const { getMeta } = useMetas()
const validators = {
formula_raw: [
{
required: true,
validator: (_: any, formula: any) => {
return (async () => {
if (!formula?.trim()) throw new Error('Required')
try {
await validateFormulaAndExtractTreeWithType({
column: column.value,
formula,
columns: supportedColumns.value,
clientOrSqlUi: sqlUi.value,
getMeta,
})
} catch (e: any) {
if (e instanceof FormulaError && e.extra?.key) {
throw new Error(t(e.extra.key, e.extra))
}
throw new Error(e.message)
}
})()
},
},
],
}
// set default value
if ((column.value?.colOptions as any)?.formula_raw) {
vModel.value.formula_raw =
substituteColumnIdWithAliasInFormula(
(column.value?.colOptions as FormulaType)?.formula,
meta?.value?.columns as ColumnType[],
(column.value?.colOptions as any)?.formula_raw,
) || ''
}
const source = computed(() => activeBase.value?.sources?.find((s) => s.id === meta.value?.source_id))
const parsedTree = ref<any>({
dataType: FormulaDataTypes.UNKNOWN,
})
// Initialize a counter to track watcher invocations
let watcherCounter = 0
// Define the debounced async validation function
const debouncedValidate = useDebounceFn(async () => {
// Increment the counter for each invocation
watcherCounter += 1
const currentCounter = watcherCounter
try {
const parsed = await validateFormulaAndExtractTreeWithType({
formula: vModel.value.formula || vModel.value.formula_raw,
columns: (meta.value?.columns || []).slice(),
column: column.value ? { ...column.value } : undefined,
clientOrSqlUi: source.value?.type as any,
getMeta: async (modelId) => await getMeta(modelId),
})
// Update parsedTree only if this is the latest invocation
if (currentCounter === watcherCounter) {
parsedTree.value = parsed
}
} catch (e) {
// Update parsedTree only if this is the latest invocation
if (currentCounter === watcherCounter) {
parsedTree.value = {
dataType: FormulaDataTypes.UNKNOWN,
}
}
}
}, 300)
// Watch the formula inputs and call the debounced function
watch(
() => vModel.value.formula || vModel.value.formula_raw,
() => {
debouncedValidate()
},
)
// set additional validations
setAdditionalValidations({
...validators,
})
const activeKey = ref('formula')
const supportedFormulaAlias = computed(() => {
if (!parsedTree.value?.dataType) return []
try {
return getUITypesForFormulaDataType(parsedTree.value?.dataType as FormulaDataTypes).map((uidt) => {
return {
value: uidt,
label: t(`datatype.${uidt}`),
icon: h(
isVirtualCol(uidt) ? resolveComponent('SmartsheetHeaderVirtualCellIcon') : resolveComponent('SmartsheetHeaderCellIcon'),
{
columnMeta: {
uidt,
},
},
),
}
})
} catch (e) {
return []
}
})
watch(
() => vModel.value.meta?.display_type,
(value, oldValue) => {
if (oldValue === undefined && !value) {
vModel.value.meta.display_column_meta = {
meta: {},
custom: {},
}
}
},
{
immediate: true,
},
)
watch(parsedTree, (value, oldValue) => {
if (oldValue === undefined && value) {
return
}
if (value?.dataType !== oldValue?.dataType) {
vModel.value.meta.display_type = null
}
})
</script>
<template>
<div class="formula-wrapper relative">
<NcTabs v-model:activeKey="activeKey">
<a-tab-pane key="formula">
<template #tab>
<div class="tab">
<div>{{ $t('datatype.Formula') }}</div>
</div>
</template>
<div class="px-0.5">
<SmartsheetColumnFormulaInputHelper
v-model:value="vModel.formula_raw"
:error="validateInfos.formula_raw?.validateStatus === 'error'"
/>
</div>
</a-tab-pane>
<a-tab-pane key="format" :disabled="!supportedFormulaAlias?.length || !parsedTree?.dataType">
<template #tab>
<div class="tab">
<div>{{ $t('labels.formatting') }}</div>
</div>
</template>
<div class="flex flex-col px-0.5 gap-4">
<a-form-item class="mt-4" :label="$t('general.format')">
<a-select v-model:value="vModel.meta.display_type" class="w-full" :placeholder="$t('labels.selectAFormatType')">
<a-select-option v-for="option in supportedFormulaAlias" :key="option.value" :value="option.value">
<div class="flex w-full items-center gap-2 justify-between">
<div class="w-full">
<component :is="option.icon" class="w-4 h-4 !text-gray-600" />
{{ option.label }}
</div>
<component
:is="iconMap.check"
v-if="option.value === vModel.meta?.display_type"
id="nc-selected-item-icon"
class="text-primary w-4 h-4"
/>
</div>
</a-select-option>
</a-select>
</a-form-item>
<template
v-if="
[
FormulaDataTypes.NUMERIC,
FormulaDataTypes.DATE,
FormulaDataTypes.BOOLEAN,
FormulaDataTypes.STRING,
FormulaDataTypes.COND_EXP,
].includes(parsedTree?.dataType)
"
>
<SmartsheetColumnCurrencyOptions
v-if="vModel.meta.display_type === UITypes.Currency"
:value="vModel.meta.display_column_meta"
/>
<SmartsheetColumnDecimalOptions
v-else-if="vModel.meta.display_type === UITypes.Decimal"
:value="vModel.meta.display_column_meta"
/>
<SmartsheetColumnPercentOptions
v-else-if="vModel.meta.display_type === UITypes.Percent"
:value="vModel.meta.display_column_meta"
/>
<SmartsheetColumnRatingOptions
v-else-if="vModel.meta.display_type === UITypes.Rating"
:value="vModel.meta.display_column_meta"
/>
<SmartsheetColumnTimeOptions
v-else-if="vModel.meta.display_type === UITypes.Time"
:value="vModel.meta.display_column_meta"
/>
<SmartsheetColumnDateTimeOptions
v-else-if="vModel.meta.display_type === UITypes.DateTime"
:value="vModel.meta.display_column_meta"
/>
<SmartsheetColumnDateOptions
v-else-if="vModel.meta.display_type === UITypes.Date"
:value="vModel.meta.display_column_meta"
/>
<SmartsheetColumnCheckboxOptions
v-else-if="vModel.meta.display_type === UITypes.Checkbox"
:value="vModel.meta.display_column_meta"
/>
</template>
</div>
</a-tab-pane>
</NcTabs>
</div>
</template>
<style lang="scss" scoped>
:deep(.ant-tabs-nav-wrap) {
@apply !pl-0;
}
:deep(.ant-form-item-control-input) {
@apply h-full;
}
:deep(.ant-tabs-content-holder) {
@apply mt-4;
}
:deep(.ant-tabs-tab) {
@apply !pb-0 pt-1;
}
:deep(.ant-tabs-nav) {
@apply !mb-0 !pl-0;
}
:deep(.ant-tabs-tab-btn) {
@apply !mb-1;
}
.mono-font {
font-family: 'JetBrainsMono', monospace;
}
</style>