多维表格
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.
 
 
 
 
 
 

455 lines
10 KiB

<script setup lang="ts">
import { ButtonActionsType, type ButtonType, type ColumnType } from 'nocodb-sdk'
import type { Ref } from 'vue'
const column = inject(ColumnInj) as Ref<
ColumnType & {
colOptions: ButtonType
}
>
const cellValue = inject(CellValueInj, ref())
const { currentRow } = useSmartsheetRowStoreOrThrow()
const { generateRows, generatingRows, generatingColumnRows, generatingColumns, aiIntegrations } = useNocoAi()
const isFieldAiIntegrationAvailable = computed(() => {
if (column.value?.colOptions?.type !== ButtonActionsType.Ai) return true
const fkIntegrationId = column.value?.colOptions?.fk_integration_id
if (!fkIntegrationId) return false
return ncIsArrayIncludes(aiIntegrations.value, fkIntegrationId, 'id')
})
const meta = inject(MetaInj, ref())
const isGrid = inject(IsGridInj, ref(false))
const isExpandedForm = inject(IsExpandedFormOpenInj, ref(false))
const { isUIAllowed } = useRoles()
const isPublic = inject(IsPublicInj, ref(false))
const { $api } = useNuxtApp()
const rowId = computed(() => {
return extractPkFromRow(currentRow.value?.row, meta.value!.columns!)
})
const isLoading = ref(false)
const pk = computed(() => {
if (!meta.value?.columns) return
return extractPkFromRow(currentRow.value?.row, meta.value.columns)
})
const generate = async () => {
if (!meta?.value?.id || !meta.value.columns || !column?.value?.id) return
if (!pk.value) return
const outputColumnIds =
ncIsString(column.value.colOptions?.output_column_ids) && column.value.colOptions.output_column_ids.split(',').length > 0
? column.value.colOptions.output_column_ids.split(',')
: []
const outputColumns = outputColumnIds.map((id) => meta.value?.columnsById[id])
generatingRows.value.push(pk.value)
generatingColumnRows.value.push(column.value.id)
generatingColumns.value.push(...(outputColumnIds ?? []))
const res = await generateRows(meta.value.id, column.value.id, [pk.value])
if (res?.length) {
const resRow = res[0]
if (outputColumnIds) {
for (const col of outputColumns) {
if (col && currentRow.value.row) {
currentRow.value.row[col.title!] = resRow[col.title!]
}
}
}
}
generatingRows.value = generatingRows.value.filter((v) => v !== pk.value)
generatingColumnRows.value = generatingColumnRows.value.filter((v) => v !== column.value.id)
generatingColumns.value = generatingColumns.value.filter((v) => !outputColumnIds?.includes(v))
}
const triggerAction = async () => {
const colOptions = column.value.colOptions
if (colOptions.type === ButtonActionsType.Webhook) {
try {
isLoading.value = true
await $api.dbTableWebhook.trigger(cellValue.value?.fk_webhook_id, rowId!.value)
} catch (e) {
console.log(e)
message.error(await extractSdkResponseErrorMsg(e))
} finally {
isLoading.value = false
}
} else if (colOptions.type === ButtonActionsType.Ai) {
await generate()
}
}
const componentProps = computed(() => {
if (column.value.colOptions.type === ButtonActionsType.Url) {
let url = `${cellValue.value?.url}`
url = /^(https?|ftp|mailto|file):\/\//.test(url) ? url : url.trim() ? `https://${url}` : ''
// if url params not encoded, encode them using encodeURI
try {
url = decodeURI(url) === url ? encodeURI(url) : url
} catch {
url = encodeURI(url)
}
return {
href: url,
target: '_blank',
...(column.value?.colOptions.error ? { disabled: true } : {}),
}
} else if (column.value.colOptions.type === ButtonActionsType.Webhook) {
return {
disabled:
isPublic.value ||
!isUIAllowed('hookTrigger') ||
isLoading.value ||
!column.value.colOptions.fk_webhook_id ||
!cellValue.value?.fk_webhook_id,
}
} else if (column.value.colOptions.type === ButtonActionsType.Ai) {
return {
disabled:
!isFieldAiIntegrationAvailable.value ||
isLoading.value ||
(pk.value &&
generatingRows.value.includes(pk.value) &&
column.value?.id &&
generatingColumnRows.value.includes(column.value.id)),
}
}
})
</script>
<template>
<div
:class="{
'justify-center': isGrid && !isExpandedForm,
}"
class="w-full flex items-center"
>
<NcTooltip :disabled="isFieldAiIntegrationAvailable" class="flex">
<template #title>
{{ aiIntegrations.length ? $t('tooltip.aiIntegrationReConfigure') : $t('tooltip.aiIntegrationAddAndReConfigure') }}
</template>
<component
:is="column.colOptions.type === ButtonActionsType.Url ? 'a' : 'button'"
v-bind="componentProps"
data-testid="nc-button-cell"
:class="[
`${column.colOptions.color ?? 'brand'} ${column.colOptions.theme ?? 'solid'}`,
{ '!w-6': !column.colOptions.label },
]"
class="nc-cell-button nc-button-cell-link btn-cell-colors truncate flex items-center h-6"
@click="triggerAction"
>
<GeneralLoader
v-if="isLoading || (pk && generatingRows.includes(pk) && column?.id && generatingColumnRows.includes(column.id))"
:class="{
solid: column.colOptions.theme === 'solid',
text: column.colOptions.theme === 'text',
light: column.colOptions.theme === 'light',
}"
class="flex btn-cell-colors !bg-transparent w-4 h-4"
size="medium"
/>
<GeneralIcon v-else-if="column.colOptions.icon" :icon="column.colOptions.icon" class="!w-4 min-w-4 min-h-4 !h-4" />
<NcTooltip v-if="column.colOptions.label" class="!truncate" show-on-truncate-only>
<span class="text-[13px] truncate font-medium">
{{ column.colOptions.label }}
</span>
<template #title>
{{ column.colOptions.label }}
</template>
</NcTooltip>
</component>
</NcTooltip>
</div>
</template>
<style lang="scss">
.nc-data-cell {
&:has(.nc-virtual-cell-button) {
@apply !border-none;
box-shadow: none !important;
&:focus-within:not(.nc-readonly-div-data-cell):not(.nc-system-field) {
box-shadow: none !important;
}
}
.nc-cell-attachment {
@apply !border-none;
}
}
.nc-button-cell-link {
@apply !no-underline;
}
</style>
<style scoped lang="scss">
.nc-cell-button {
@apply rounded-lg px-2 flex items-center gap-2 transition-all justify-center;
&:not([class*='text']) {
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.06), 0px 5px 3px -2px rgba(0, 0, 0, 0.02);
}
&:focus-within {
@apply outline-none ring-0;
box-shadow: 0px 0px 0px 2px #fff, 0px 0px 0px 4px #3069fe;
}
&[disabled] {
@apply !bg-gray-100 text-gray-400;
}
}
.btn-cell-colors {
&.solid {
@apply text-white;
&.brand {
@apply bg-brand-500 hover:bg-brand-600;
.nc-loader {
@apply !text-brand-500;
}
}
&.red {
@apply bg-red-600 hover:bg-red-700;
.nc-loader {
@apply !text-red-600;
}
}
&.green {
@apply bg-green-600 hover:bg-green-700;
.nc-loader {
@apply !text-green-600;
}
}
&.maroon {
@apply bg-maroon-600 hover:bg-maroon-700;
.nc-loader {
@apply !text-maroon-600;
}
}
&.blue {
@apply bg-blue-600 hover:bg-blue-700;
.nc-loader {
@apply !text-blue-600;
}
}
&.orange {
@apply bg-orange-600 hover:bg-orange-700;
.nc-loader {
@apply !text-orange-600;
}
}
&.pink {
@apply bg-pink-600 hover:bg-pink-700;
.nc-loader {
@apply !text-pink-600;
}
}
&.purple {
@apply bg-purple-500 hover:bg-purple-700;
.nc-loader {
@apply !text-purple-600;
}
}
&.yellow {
@apply bg-yellow-600 hover:bg-yellow-700;
.nc-loader {
@apply !text-yellow-600;
}
}
&.gray {
@apply bg-gray-600 hover:bg-gray-700;
.nc-loader {
@apply !text-gray-600;
}
}
}
&.light {
@apply text-gray-700;
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.06), 0px 5px 3px -2px rgba(0, 0, 0, 0.02);
&.brand {
@apply bg-brand-100 hover:bg-brand-200;
.nc-loader {
@apply !text-brand-500;
}
}
&.red {
@apply bg-red-100 hover:bg-red-200;
.nc-loader {
@apply !text-red-600;
}
}
&.green {
@apply bg-green-100 hover:bg-green-200;
.nc-loader {
@apply !text-green-600;
}
}
&.maroon {
@apply bg-maroon-100 hover:bg-maroon-200;
.nc-loader {
@apply !text-maroon-600;
}
}
&.blue {
@apply bg-blue-100 hover:bg-blue-200;
.nc-loader {
@apply !text-blue-600;
}
}
&.orange {
@apply bg-orange-100 hover:bg-orange-200;
.nc-loader {
@apply !text-orange-600;
}
}
&.pink {
@apply bg-pink-100 hover:bg-pink-200;
.nc-loader {
@apply !text-pink-600;
}
}
&.purple {
@apply bg-purple-100 hover:bg-purple-200;
.nc-loader {
@apply !text-purple-600;
}
}
&.yellow {
@apply bg-yellow-100 hover:bg-yellow-200;
.nc-loader {
@apply !text-yellow-600;
}
}
&.gray {
@apply bg-gray-100 hover:bg-gray-200;
.nc-loader {
@apply !text-gray-600;
}
}
}
&.text {
&:hover {
@apply bg-gray-200;
}
&:focus {
box-shadow: 0px 0px 0px 2px #fff, 0px 0px 0px 4px #3069fe;
}
&.brand {
@apply text-brand-500;
.nc-loader {
@apply !text-brand-500;
}
}
&.red {
@apply text-red-600;
.nc-loader {
@apply !text-red-600;
}
}
&.green {
@apply text-green-600;
.nc-loader {
@apply !text-green-600;
}
}
&.maroon {
@apply text-maroon-600;
.nc-loader {
@apply !text-maroon-600;
}
}
&.blue {
@apply text-blue-600;
.nc-loader {
@apply !text-blue-600;
}
}
&.orange {
@apply text-orange-600;
.nc-loader {
@apply !text-orange-600;
}
}
&.pink {
@apply text-pink-600;
.nc-loader {
@apply !text-pink-600;
}
}
&.purple {
@apply text-purple-500;
.nc-loader {
@apply !text-purple-600;
}
}
&.yellow {
@apply text-yellow-600;
.nc-loader {
@apply !text-yellow-600;
}
}
&.gray {
@apply text-gray-600;
.nc-loader {
@apply !text-gray-600;
}
}
}
}
</style>