feat(front): enhance KvInputGroupField and NotifyConfigField components with autocomplete functionality and improved header configuration

This commit is contained in:
keven1024
2026-05-01 15:39:37 +08:00
parent 1226a7948e
commit a24bf24271
3 changed files with 83 additions and 39 deletions

View File

@@ -12,7 +12,7 @@ const { value, setValue, errorMessage } = useField<string[]>(props.name, props?.
<template>
<div class="flex flex-col gap-2">
<Label v-if="label">{{ label }}</Label>
<div v-for="(item, index) in value" :key="`${index}-${item}`" class="flex flex-row gap-2 items-center">
<div v-for="(item, index) in value" class="flex flex-row gap-2 items-center">
<Input
:model-value="item"
@update:model-value="(v: string | number) => setValue(value.map((o, i) => (i === index ? String(v) : o)))"

View File

@@ -1,13 +1,43 @@
<script setup lang="ts">
import { Label } from '@/components/ui/label'
import { AutocompleteAnchor, AutocompleteContent, AutocompleteInput, AutocompleteItem, AutocompleteRoot, AutocompleteViewport } from 'reka-ui'
import type { Component } from 'vue'
import InputField from '../Field/InputField.vue'
type KvInputValueComponentConfig = [(key: string) => boolean, Component]
type KvInputConfig = {
key?: {
placeholder?: string
enum?: string[]
}
value?: {
placeholder?: string
component?: KvInputValueComponentConfig[]
default?: Component
}
}
const defaultConfig = {
key: {},
value: {
default: InputField,
},
} satisfies Required<KvInputConfig>
const props = defineProps<{
name: string
label?: string
keyPlaceholder?: string
valuePlaceholder?: string
config?: KvInputConfig
}>()
const config = computed(() => {
return {
key: { ...defaultConfig.key, ...(props.config?.key ?? {}) },
value: { ...defaultConfig.value, ...(props.config?.value ?? {}) },
}
})
const { value, setValue } = useField<[string, string][]>(props.name)
const updateKey = (index: number, nextKey: string | number) => {
@@ -15,24 +45,44 @@ const updateKey = (index: number, nextKey: string | number) => {
next[index] = [String(nextKey), next[index]?.[1] ?? '']
setValue(next)
}
const updateValue = (index: number, nextVal: string | number) => {
const next = [...(value.value ?? [])]
next[index] = [next[index]?.[0] ?? '', String(nextVal)]
setValue(next)
}
</script>
<template>
<div class="flex flex-col gap-2">
<Label v-if="label">{{ label }}</Label>
<div v-for="([key, itemValue], index) in value" :key="index" class="flex flex-row gap-2 items-center">
<Input :model-value="key" :placeholder="keyPlaceholder" @update:model-value="(v: string | number) => updateKey(index, v)" />
<Input
:model-value="String(itemValue ?? '')"
:placeholder="valuePlaceholder"
@update:model-value="(v: string | number) => updateValue(index, v)"
/>
<div v-for="([key, _], index) in value" class="flex flex-row gap-2 items-center">
<AutocompleteRoot class="basis-40 relative" :model-value="String(key ?? '')" @update:model-value="(v) => updateKey(index, v)">
<AutocompleteAnchor>
<AutocompleteInput
class="w-full placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm"
:placeholder="config.key.placeholder"
>
</AutocompleteInput>
</AutocompleteAnchor>
<AutocompleteContent
v-if="config.key?.enum"
class="bg-popover border rounded-md shadow-md z-50 w-(--reka-autocomplete-trigger-width) absolute inset-x-0"
>
<AutocompleteViewport class="p-1">
<AutocompleteItem
v-for="opt in config.key?.enum"
:key="opt"
:value="opt"
class="relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-highlighted:bg-accent data-highlighted:text-accent-foreground"
>
{{ opt }}
</AutocompleteItem>
</AutocompleteViewport>
</AutocompleteContent>
</AutocompleteRoot>
<div class="flex-1">
<component
:is="config.value.component?.find(([isMatchCom]) => isMatchCom(key))?.[1] ?? config.value.default"
:name="`${props.name}[${index}][1]`"
:placeholder="config.value.placeholder"
class="w-full"
/>
</div>
<Button
type="button"
variant="ghost"

View File

@@ -110,29 +110,23 @@ const expandedAdvanced = ref<Set<number>>(new Set())
<KvInputField
:name="`notify_webhooks.${index}.headers`"
:label="t('page.shareOptions.notify.webhookHeaders')"
:key-placeholder="t('page.shareOptions.notify.webhookHeaderKey')"
:value-placeholder="t('page.shareOptions.notify.webhookHeaderValue')"
:config="{
key: {
placeholder: t('page.shareOptions.notify.webhookHeaderKey'),
enum: ['Content-Type', 'User-Agent', 'Authorization', 'Accept', 'Content-Length'],
},
value: {
placeholder: t('page.shareOptions.notify.webhookHeaderValue'),
component: [
[
(key: string) => key === 'Content-Type',
({ ...props }) =>
h(SelectField, { ...props, options: [{ value: 'text/plain' }, { value: 'application/json' }] }),
],
],
},
}"
/>
<div v-if="((values.notify_webhooks as WebhookItem[]) || [])[index]?.method === 'POST'" class="flex flex-col gap-3">
<Label class="w-20 pt-2">{{ t('page.shareOptions.notify.webhookBody') }}</Label>
<div class="flex flex-1 flex-col gap-2">
<SelectField
:name="`notify_webhooks.${index}.bodyType`"
:placeholder="t('page.shareOptions.notify.webhookBodyType')"
:label="t('page.shareOptions.notify.webhookBodyType')"
:options="[
{ label: t('page.shareOptions.notify.webhookBodyTypeNone'), value: 'none' },
{ label: t('page.shareOptions.notify.webhookBodyTypeFormData'), value: 'form-data' },
{ label: t('page.shareOptions.notify.webhookBodyTypeRaw'), value: 'raw' },
]"
/>
<TextareaField
v-if="['form-data', 'raw'].includes(((values.notify_webhooks as WebhookItem[]) || [])[index]?.bodyType || '')"
:name="`notify_webhooks.${index}.body`"
:placeholder="t('page.shareOptions.notify.webhookBody')"
/>
</div>
</div>
</div>
</div>
<div class="flex justify-start">
@@ -142,7 +136,7 @@ const expandedAdvanced = ref<Set<number>>(new Set())
@click="
setFieldValue('notify_webhooks', [
...((values.notify_webhooks as WebhookItem[]) || []),
{ url: '', method: 'POST', headers: {}, bodyType: 'none', body: '' },
{ url: '', method: 'POST', headers: [] },
])
"
>