mirror of
https://github.com/keven1024/015.git
synced 2026-05-26 07:08:02 +00:00
feat(front): enhance KvInputGroupField and NotifyConfigField components with autocomplete functionality and improved header configuration
This commit is contained in:
@@ -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)))"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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: [] },
|
||||
])
|
||||
"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user