feat: 优化工作流参数编辑与告警交互
- 新增 ParamTokenEditor,支持参数选择插入、token 高亮、整段删除与光标避让 - 参数候选改为动态监测,未映射参数可选择并在下拉与输入框顶部告警 - 接入知识库/搜索引擎/LLM/动态代码/HTTP Body 及 SQL、查询数据自定义节点 - 优化 Http 节点布局并补充参数解析工具与单测
This commit is contained in:
@@ -0,0 +1,748 @@
|
||||
<script lang="ts">
|
||||
import { tick } from 'svelte';
|
||||
import {Button, FloatingTrigger} from '../base';
|
||||
import type {Parameter} from '#types';
|
||||
import {
|
||||
escapeHtml,
|
||||
flattenParameterCandidates,
|
||||
findBackspaceTokenRange,
|
||||
findTokenRangeAtCursor,
|
||||
insertTextAtCursor,
|
||||
parseTokenParts,
|
||||
splitTokenDisplay
|
||||
} from '../utils/paramToken';
|
||||
|
||||
const {
|
||||
mode = 'textarea',
|
||||
value = '',
|
||||
parameters = [],
|
||||
rows = 3,
|
||||
placeholder = '',
|
||||
disabled = false,
|
||||
class: className = '',
|
||||
style = '',
|
||||
...rest
|
||||
}: {
|
||||
mode?: 'input' | 'textarea';
|
||||
value?: string;
|
||||
parameters?: Parameter[];
|
||||
rows?: number;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
class?: string;
|
||||
style?: string;
|
||||
[key: string]: any;
|
||||
} = $props();
|
||||
|
||||
let inputEl = $state<HTMLInputElement | null>(null);
|
||||
let textareaEl = $state<HTMLTextAreaElement | null>(null);
|
||||
let highlightEl = $state<HTMLDivElement | null>(null);
|
||||
let triggerObject: any;
|
||||
let isFocused = $state(false);
|
||||
let isComposing = $state(false);
|
||||
let localValue = $state((value || '') as string);
|
||||
|
||||
$effect(() => {
|
||||
const nextValue = (value || '') as string;
|
||||
if (!isFocused && nextValue !== localValue) {
|
||||
localValue = nextValue;
|
||||
}
|
||||
});
|
||||
|
||||
const paramCandidates = $derived(flattenParameterCandidates(parameters));
|
||||
const paramNames = $derived(paramCandidates.map((item) => item.name));
|
||||
const unresolvedParamSet = $derived.by(() => {
|
||||
return new Set(paramCandidates.filter((item) => !item.resolved).map((item) => item.name));
|
||||
});
|
||||
const hasParams = $derived(paramCandidates.length > 0);
|
||||
const tokenParts = $derived(parseTokenParts(localValue, paramNames));
|
||||
const unresolvedTokensInEditor = $derived.by(() => {
|
||||
const keySet = new Set<string>();
|
||||
for (const part of tokenParts) {
|
||||
if (part.type === 'token' && part.valid && unresolvedParamSet.has(part.key)) {
|
||||
keySet.add(part.key);
|
||||
}
|
||||
}
|
||||
return Array.from(keySet);
|
||||
});
|
||||
const undefinedTokensInEditor = $derived.by(() => {
|
||||
const keySet = new Set<string>();
|
||||
for (const part of tokenParts) {
|
||||
if (part.type === 'token' && !part.valid) {
|
||||
keySet.add(part.key);
|
||||
}
|
||||
}
|
||||
return Array.from(keySet);
|
||||
});
|
||||
const showInlineHint = $derived(unresolvedTokensInEditor.length > 0 || undefinedTokensInEditor.length > 0);
|
||||
const highlightedHtml = $derived.by(() => {
|
||||
return tokenParts
|
||||
.map((part) => {
|
||||
if (part.type === 'text') {
|
||||
return escapeHtml(part.text);
|
||||
}
|
||||
const cssClass = !part.valid
|
||||
? 'param-token-invalid'
|
||||
: unresolvedParamSet.has(part.key)
|
||||
? 'param-token-warning'
|
||||
: 'param-token-valid';
|
||||
const display = splitTokenDisplay(part.text, part.key);
|
||||
return `<span class="param-token-token"><span class="param-token-chip ${cssClass}"><span class="param-token-hidden">${escapeHtml(display.hiddenPrefix)}</span><span class="param-token-chip-text">${escapeHtml(display.visibleText)}</span><span class="param-token-hidden">${escapeHtml(display.hiddenSuffix)}</span></span></span>`;
|
||||
})
|
||||
.join('');
|
||||
});
|
||||
|
||||
const getEditorElement = () => {
|
||||
return mode === 'input' ? inputEl : textareaEl;
|
||||
};
|
||||
|
||||
const emitInput = (event?: Event) => {
|
||||
if (event) {
|
||||
rest.oninput?.(event);
|
||||
return;
|
||||
}
|
||||
rest.oninput?.({
|
||||
target: {
|
||||
value: localValue
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const emitChange = (event?: Event) => {
|
||||
if (event) {
|
||||
rest.onchange?.(event);
|
||||
return;
|
||||
}
|
||||
rest.onchange?.({
|
||||
target: {
|
||||
value: localValue
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const syncScroll = () => {
|
||||
const el = getEditorElement();
|
||||
if (!el || !highlightEl) {
|
||||
return;
|
||||
}
|
||||
highlightEl.scrollTop = el.scrollTop;
|
||||
highlightEl.scrollLeft = el.scrollLeft;
|
||||
};
|
||||
|
||||
const handleInput = (event: Event) => {
|
||||
localValue = ((event.target as HTMLInputElement | HTMLTextAreaElement).value || '') as string;
|
||||
syncScroll();
|
||||
emitInput(event);
|
||||
};
|
||||
|
||||
const handleChange = (event: Event) => {
|
||||
localValue = ((event.target as HTMLInputElement | HTMLTextAreaElement).value || '') as string;
|
||||
emitChange(event);
|
||||
};
|
||||
|
||||
const insertParam = async (paramName: string) => {
|
||||
const editorEl = getEditorElement();
|
||||
const result = insertTextAtCursor(
|
||||
localValue,
|
||||
`{{${paramName}}}`,
|
||||
editorEl?.selectionStart,
|
||||
editorEl?.selectionEnd
|
||||
);
|
||||
localValue = result.value;
|
||||
emitInput();
|
||||
emitChange();
|
||||
|
||||
await tick();
|
||||
const newEditorEl = getEditorElement();
|
||||
if (newEditorEl) {
|
||||
newEditorEl.focus();
|
||||
newEditorEl.setSelectionRange(result.cursor, result.cursor);
|
||||
}
|
||||
syncScroll();
|
||||
triggerObject?.hide?.();
|
||||
};
|
||||
|
||||
const handleFocus = (event: Event) => {
|
||||
isFocused = true;
|
||||
rest.onfocus?.(event);
|
||||
};
|
||||
|
||||
const handleBlur = (event: Event) => {
|
||||
isFocused = false;
|
||||
rest.onblur?.(event);
|
||||
};
|
||||
|
||||
const handleCompositionStart = (event: Event) => {
|
||||
isComposing = true;
|
||||
rest.oncompositionstart?.(event);
|
||||
};
|
||||
|
||||
const handleCompositionEnd = (event: Event) => {
|
||||
isComposing = false;
|
||||
rest.oncompositionend?.(event);
|
||||
};
|
||||
|
||||
const setCaretPosition = (position: number) => {
|
||||
const editorEl = getEditorElement();
|
||||
if (!editorEl) {
|
||||
return;
|
||||
}
|
||||
editorEl.setSelectionRange(position, position);
|
||||
};
|
||||
|
||||
const normalizeCaretAsync = () => {
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
moveCaretOutOfToken();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const moveCaretOutOfToken = (prefer?: 'start' | 'end') => {
|
||||
const editorEl = getEditorElement();
|
||||
if (!editorEl) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const selectionStart = editorEl.selectionStart ?? 0;
|
||||
const selectionEnd = editorEl.selectionEnd ?? 0;
|
||||
if (selectionStart !== selectionEnd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tokenRange = findTokenRangeAtCursor(localValue, selectionStart, {
|
||||
includeStart: false,
|
||||
includeEnd: false
|
||||
});
|
||||
if (!tokenRange) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const target =
|
||||
prefer === 'start'
|
||||
? tokenRange.start
|
||||
: prefer === 'end'
|
||||
? tokenRange.end
|
||||
: selectionStart - tokenRange.start <= tokenRange.end - selectionStart
|
||||
? tokenRange.start
|
||||
: tokenRange.end;
|
||||
editorEl.setSelectionRange(target, target);
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleKeydown = async (event: KeyboardEvent) => {
|
||||
if (disabled || isComposing) {
|
||||
rest.onkeydown?.(event);
|
||||
return;
|
||||
}
|
||||
|
||||
const target = event.target as HTMLInputElement | HTMLTextAreaElement;
|
||||
const selectionStart = target.selectionStart ?? 0;
|
||||
const selectionEnd = target.selectionEnd ?? 0;
|
||||
|
||||
if (selectionStart !== selectionEnd) {
|
||||
rest.onkeydown?.(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowLeft') {
|
||||
const leftToken = findTokenRangeAtCursor(localValue, selectionStart, {
|
||||
includeStart: false,
|
||||
includeEnd: true
|
||||
});
|
||||
if (leftToken && leftToken.end === selectionStart) {
|
||||
event.preventDefault();
|
||||
setCaretPosition(leftToken.start);
|
||||
rest.onkeydown?.(event);
|
||||
return;
|
||||
}
|
||||
if (moveCaretOutOfToken('start')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
rest.onkeydown?.(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowRight') {
|
||||
const rightToken = findTokenRangeAtCursor(localValue, selectionStart, {
|
||||
includeStart: true,
|
||||
includeEnd: false
|
||||
});
|
||||
if (rightToken && rightToken.start === selectionStart) {
|
||||
event.preventDefault();
|
||||
setCaretPosition(rightToken.end);
|
||||
rest.onkeydown?.(event);
|
||||
return;
|
||||
}
|
||||
if (moveCaretOutOfToken('end')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
rest.onkeydown?.(event);
|
||||
return;
|
||||
}
|
||||
|
||||
const tokenRangeInside = findTokenRangeAtCursor(localValue, selectionStart, {
|
||||
includeStart: false,
|
||||
includeEnd: false
|
||||
});
|
||||
if (tokenRangeInside && event.key !== 'Backspace' && event.key !== 'Delete') {
|
||||
event.preventDefault();
|
||||
moveCaretOutOfToken();
|
||||
rest.onkeydown?.(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'Delete') {
|
||||
const deleteRange = findTokenRangeAtCursor(localValue, selectionStart, {
|
||||
includeStart: true,
|
||||
includeEnd: false
|
||||
});
|
||||
if (deleteRange) {
|
||||
event.preventDefault();
|
||||
const result = insertTextAtCursor(localValue, '', deleteRange.start, deleteRange.end);
|
||||
localValue = result.value;
|
||||
emitInput();
|
||||
emitChange();
|
||||
|
||||
await tick();
|
||||
const editorEl = getEditorElement();
|
||||
if (editorEl) {
|
||||
editorEl.focus();
|
||||
editorEl.setSelectionRange(deleteRange.start, deleteRange.start);
|
||||
}
|
||||
syncScroll();
|
||||
rest.onkeydown?.(event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.key !== 'Backspace') {
|
||||
rest.onkeydown?.(event);
|
||||
return;
|
||||
}
|
||||
|
||||
const tokenRange = findBackspaceTokenRange(localValue, selectionStart);
|
||||
if (!tokenRange) {
|
||||
rest.onkeydown?.(event);
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
const result = insertTextAtCursor(localValue, '', tokenRange.start, tokenRange.end);
|
||||
localValue = result.value;
|
||||
emitInput();
|
||||
emitChange();
|
||||
|
||||
await tick();
|
||||
const editorEl = getEditorElement();
|
||||
if (editorEl) {
|
||||
editorEl.focus();
|
||||
editorEl.setSelectionRange(tokenRange.start, tokenRange.start);
|
||||
}
|
||||
syncScroll();
|
||||
rest.onkeydown?.(event);
|
||||
};
|
||||
|
||||
const handleSelect = (event: Event) => {
|
||||
normalizeCaretAsync();
|
||||
rest.onselect?.(event);
|
||||
};
|
||||
|
||||
const handleMouseUp = (event: MouseEvent) => {
|
||||
normalizeCaretAsync();
|
||||
rest.onmouseup?.(event);
|
||||
};
|
||||
|
||||
const handleKeyup = (event: KeyboardEvent) => {
|
||||
if (event.key === 'ArrowLeft' || event.key === 'ArrowRight' || event.key === 'Home' || event.key === 'End') {
|
||||
normalizeCaretAsync();
|
||||
}
|
||||
rest.onkeyup?.(event);
|
||||
};
|
||||
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
normalizeCaretAsync();
|
||||
rest.onclick?.(event);
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="param-token-editor {className}" style={style}>
|
||||
{#if showInlineHint}
|
||||
<div class="param-token-inline-hint">
|
||||
{#if unresolvedTokensInEditor.length > 0}
|
||||
<div class="hint-item hint-unresolved">
|
||||
<span class="hint-item-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 2C6.486 2 2 6.486 2 12S6.486 22 12 22 22 17.514 22 12 17.514 2 12 2zm0 15a1.25 1.25 0 1 1 0-2.5A1.25 1.25 0 0 1 12 17zm1-4h-2V7h2v6z"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<span>
|
||||
未映射参数:{unresolvedTokensInEditor.join('、')}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if undefinedTokensInEditor.length > 0}
|
||||
<div class="hint-item hint-undefined">
|
||||
<span class="hint-item-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 2C6.486 2 2 6.486 2 12S6.486 22 12 22 22 17.514 22 12 17.514 2 12 2zm0 15a1.25 1.25 0 1 1 0-2.5A1.25 1.25 0 0 1 12 17zm1-4h-2V7h2v6z"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<span>
|
||||
未定义参数:{undefinedTokensInEditor.join('、')}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="param-token-editor-inner">
|
||||
<div
|
||||
class="param-token-editor-highlight {mode === 'input' ? 'single-line' : 'multi-line'}"
|
||||
bind:this={highlightEl}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{@html highlightedHtml}
|
||||
</div>
|
||||
|
||||
{#if mode === 'input'}
|
||||
<input
|
||||
{...rest}
|
||||
bind:this={inputEl}
|
||||
type="text"
|
||||
class="tf-input param-token-input {rest.class}"
|
||||
placeholder={placeholder}
|
||||
value={localValue}
|
||||
disabled={disabled}
|
||||
spellcheck="false"
|
||||
onfocus={handleFocus}
|
||||
onblur={handleBlur}
|
||||
oninput={handleInput}
|
||||
onchange={handleChange}
|
||||
onscroll={syncScroll}
|
||||
onkeydown={handleKeydown}
|
||||
onselect={handleSelect}
|
||||
onmouseup={handleMouseUp}
|
||||
onclick={handleClick}
|
||||
onkeyup={handleKeyup}
|
||||
oncompositionstart={handleCompositionStart}
|
||||
oncompositionend={handleCompositionEnd}
|
||||
/>
|
||||
{:else}
|
||||
<textarea
|
||||
{...rest}
|
||||
bind:this={textareaEl}
|
||||
class="tf-textarea param-token-textarea {rest.class}"
|
||||
placeholder={placeholder}
|
||||
value={localValue}
|
||||
rows={rows}
|
||||
disabled={disabled}
|
||||
spellcheck="false"
|
||||
onfocus={handleFocus}
|
||||
onblur={handleBlur}
|
||||
oninput={handleInput}
|
||||
onchange={handleChange}
|
||||
onscroll={syncScroll}
|
||||
onkeydown={handleKeydown}
|
||||
onselect={handleSelect}
|
||||
onmouseup={handleMouseUp}
|
||||
onclick={handleClick}
|
||||
onkeyup={handleKeyup}
|
||||
oncompositionstart={handleCompositionStart}
|
||||
oncompositionend={handleCompositionEnd}
|
||||
></textarea>
|
||||
{/if}
|
||||
|
||||
<div class="param-token-action">
|
||||
<FloatingTrigger placement="bottom-end" bind:this={triggerObject}>
|
||||
<Button
|
||||
class="param-token-button"
|
||||
disabled={!hasParams || disabled}
|
||||
title={hasParams ? '选择参数' : '请先在输入参数中定义参数'}
|
||||
>
|
||||
<span>{"{x}"}</span>
|
||||
</Button>
|
||||
{#snippet floating()}
|
||||
<div class="param-token-panel nowheel">
|
||||
{#if hasParams}
|
||||
{#each paramCandidates as candidate}
|
||||
<button
|
||||
class="param-token-item {candidate.resolved ? '' : 'unresolved'}"
|
||||
title={candidate.resolved ? candidate.name : `${candidate.name}(未配置引用值)`}
|
||||
onclick={() => {
|
||||
insertParam(candidate.name);
|
||||
}}
|
||||
>
|
||||
<span>{candidate.name}</span>
|
||||
{#if !candidate.resolved}
|
||||
<span
|
||||
class="param-token-item-warn"
|
||||
title="该参数尚未映射引用值,运行时可能为空"
|
||||
aria-label="未映射参数"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 2 1 21h22L12 2zm0 5.2 6.4 11.8H5.6L12 7.2zm-1 3.3v4.6h2v-4.6h-2zm0 5.8v1.9h2v-1.9h-2z"></path>
|
||||
</svg>
|
||||
</span>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="param-token-empty">请先在输入参数中定义参数</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
</FloatingTrigger>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="less">
|
||||
.param-token-editor {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.param-token-inline-hint {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
margin-bottom: 6px;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.hint-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 5px 8px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.hint-item-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
flex-shrink: 0;
|
||||
|
||||
svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.hint-item.hint-unresolved {
|
||||
background: #fff8e6;
|
||||
border-color: #f6d99a;
|
||||
color: #8a5a00;
|
||||
|
||||
.hint-item-icon {
|
||||
color: #d97706;
|
||||
}
|
||||
}
|
||||
|
||||
.hint-item.hint-undefined {
|
||||
background: #fff1f1;
|
||||
border-color: #f5c7c4;
|
||||
color: #b42318;
|
||||
|
||||
.hint-item-icon {
|
||||
color: #d92d20;
|
||||
}
|
||||
}
|
||||
|
||||
.param-token-editor-inner {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: inherit;
|
||||
letter-spacing: inherit;
|
||||
}
|
||||
|
||||
.param-token-editor-highlight {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 5px;
|
||||
padding: 5px 8px;
|
||||
box-sizing: border-box;
|
||||
color: #333;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: 1.6;
|
||||
letter-spacing: inherit;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.param-token-editor-highlight.single-line {
|
||||
white-space: pre;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.param-token-editor-highlight.multi-line {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.param-token-input,
|
||||
.param-token-textarea {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background: transparent;
|
||||
color: transparent;
|
||||
caret-color: #333;
|
||||
padding-right: 36px;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: 1.6;
|
||||
letter-spacing: inherit;
|
||||
}
|
||||
|
||||
.param-token-input::placeholder,
|
||||
.param-token-textarea::placeholder {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.param-token-textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.param-token-action {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
:global(.param-token-button) {
|
||||
height: 20px;
|
||||
min-width: 20px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0 4px;
|
||||
font-size: 10px;
|
||||
border-radius: 4px;
|
||||
color: #666;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
:global(.param-token-button:hover) {
|
||||
background: #f0f3ff;
|
||||
color: #2563eb;
|
||||
border-color: #d3defd;
|
||||
}
|
||||
|
||||
.param-token-panel {
|
||||
min-width: 200px;
|
||||
max-width: 320px;
|
||||
max-height: 220px;
|
||||
overflow: auto;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.param-token-item {
|
||||
border: none;
|
||||
background: #fff;
|
||||
padding: 6px 8px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 1.3;
|
||||
color: #333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.param-token-item:hover {
|
||||
background: #f5f7ff;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.param-token-item.unresolved {
|
||||
background: #fff8e6;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.param-token-item-warn {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #d97706;
|
||||
flex-shrink: 0;
|
||||
|
||||
svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.param-token-empty {
|
||||
padding: 8px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:global(.param-token-hidden) {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
:global(.param-token-token) {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
:global(.param-token-chip) {
|
||||
display: inline-block;
|
||||
vertical-align: baseline;
|
||||
line-height: inherit;
|
||||
white-space: nowrap;
|
||||
border-radius: 4px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
box-decoration-break: clone;
|
||||
-webkit-box-decoration-break: clone;
|
||||
}
|
||||
|
||||
:global(.param-token-chip-text) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:global(.param-token-valid) {
|
||||
background: #eaf2ff;
|
||||
color: #1e4ed8;
|
||||
box-shadow: inset 0 0 0 1px #c6d8ff;
|
||||
}
|
||||
|
||||
:global(.param-token-invalid) {
|
||||
background: #fff1f1;
|
||||
color: #b42318;
|
||||
box-shadow: inset 0 0 0 1px #f5c7c4;
|
||||
}
|
||||
|
||||
:global(.param-token-warning) {
|
||||
background: #fff8e6;
|
||||
color: #92400e;
|
||||
box-shadow: inset 0 0 0 1px #f7d79e;
|
||||
}
|
||||
</style>
|
||||
@@ -42,7 +42,7 @@
|
||||
[key]: value
|
||||
};
|
||||
return {
|
||||
[dataKeyName]: parameters
|
||||
[dataKeyName]: [...parameters]
|
||||
};
|
||||
});
|
||||
};
|
||||
@@ -172,4 +172,3 @@
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user