feat: 参数修改

dev_na
puz 2026-07-16 13:51:00 +08:00
parent 3df2e753c5
commit 85ed5e37fd
2 changed files with 79 additions and 99 deletions

View File

@ -74,22 +74,64 @@
line-height: 1.6;
}
.param-value-rich-editor {
.param-value-rich-editor-shell {
position: relative;
min-height: var(--param-value-editor-min-height);
max-height: 260px;
}
.param-value-rich-editor,
.param-value-rich-editor-highlight {
box-sizing: border-box;
min-height: var(--param-value-editor-min-height);
max-height: 260px;
padding: 6px 11px;
overflow: auto;
border: 1px solid #d9d9d9;
border-radius: 6px;
background: #fff;
color: rgba(0, 0, 0, 0.88);
font-family: 'SFMono-Regular', Consolas, Menlo, monospace;
font-size: 14px;
line-height: 1.5715;
outline: none;
white-space: pre-wrap;
word-break: break-word;
}
.param-value-rich-editor-highlight {
position: absolute;
inset: 0;
overflow: hidden;
border: 1px solid transparent;
border-radius: 6px;
background: #fff;
color: rgba(0, 0, 0, 0.88);
pointer-events: none;
}
.param-value-rich-editor-highlight .param-value-template-token {
margin: 0 2px;
vertical-align: 1px;
}
.param-value-rich-editor {
position: relative;
z-index: 1;
overflow: auto;
border: 1px solid #d9d9d9;
border-radius: 6px;
background: transparent !important;
color: transparent !important;
caret-color: rgba(0, 0, 0, 0.88);
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
-webkit-text-fill-color: transparent;
}
.param-value-rich-editor::placeholder {
color: rgba(0, 0, 0, 0.25);
-webkit-text-fill-color: rgba(0, 0, 0, 0.25);
}
.param-value-rich-editor::selection {
background: #bae0ff;
color: rgba(0, 0, 0, 0.88);
-webkit-text-fill-color: rgba(0, 0, 0, 0.88);
}
.param-value-rich-editor:hover {
@ -101,16 +143,6 @@
box-shadow: 0 0 0 2px rgba(5, 145, 255, 0.1);
}
.param-value-rich-editor:empty::before {
color: rgba(0, 0, 0, 0.25);
content: attr(data-placeholder);
}
.param-value-rich-editor .param-value-template-token {
margin: 0 2px;
vertical-align: 1px;
}
.param-boolean-segmented {
width: 176px;
padding: 2px;

View File

@ -1,5 +1,5 @@
import { Button, Col, Drawer, Form, Input, Popconfirm, Popover, Row, Segmented, Select, Space, Switch, Tag, Tooltip, Typography, message } from "antd";
import { type CSSProperties, type ReactNode, useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
import { type ChangeEvent, type CSSProperties, type ReactNode, type UIEvent, useCallback, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { DeleteOutlined, EditOutlined, InfoCircleOutlined, PlusOutlined, SearchOutlined, SettingOutlined } from "@ant-design/icons";
import { createParam, deleteParam, pageParams, updateParam } from "@/api";
@ -54,49 +54,6 @@ const renderTemplateParamSegments = (text: string) => {
return segments;
};
const readEditorText = (element: HTMLElement) => element.innerText.replace(/\r\n/g, "\n");
const getCaretTextOffset = (element: HTMLElement) => {
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0 || !selection.anchorNode || !element.contains(selection.anchorNode)) {
return null;
}
const range = selection.getRangeAt(0);
const prefixRange = range.cloneRange();
prefixRange.selectNodeContents(element);
prefixRange.setEnd(range.endContainer, range.endOffset);
return prefixRange.toString().length;
};
const restoreCaretTextOffset = (element: HTMLElement, offset: number) => {
const selection = window.getSelection();
if (!selection) return;
const range = document.createRange();
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
let remaining = offset;
let node = walker.nextNode();
while (node) {
const textLength = node.textContent?.length ?? 0;
if (remaining <= textLength) {
range.setStart(node, remaining);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
return;
}
remaining -= textLength;
node = walker.nextNode();
}
range.selectNodeContents(element);
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
};
function TemplateParamValueEditor({
value,
onChange,
@ -112,52 +69,43 @@ function TemplateParamValueEditor({
placeholder?: string;
rows?: number;
}) {
const editorRef = useRef<HTMLDivElement>(null);
const pendingCaretOffsetRef = useRef<number | null>(null);
const highlightRef = useRef<HTMLDivElement>(null);
const text = value === undefined || value === null ? "" : String(value);
const editorStyle = { "--param-value-editor-min-height": `${rows * 22 + 20}px` } as CSSProperties;
useLayoutEffect(() => {
const editor = editorRef.current;
const caretOffset = pendingCaretOffsetRef.current;
if (!editor || caretOffset === null || document.activeElement !== editor) {
return;
}
restoreCaretTextOffset(editor, caretOffset);
pendingCaretOffsetRef.current = null;
}, [text]);
const handleInput = () => {
const editor = editorRef.current;
if (!editor) return;
pendingCaretOffsetRef.current = getCaretTextOffset(editor);
const nextValue = readEditorText(editor);
const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
const nextValue = event.target.value;
onChange?.(nextValue);
onTextChange?.(nextValue);
};
const handleScroll = (event: UIEvent<HTMLTextAreaElement>) => {
if (!highlightRef.current) return;
highlightRef.current.scrollTop = event.currentTarget.scrollTop;
highlightRef.current.scrollLeft = event.currentTarget.scrollLeft;
};
return (
<div
ref={editorRef}
className="param-value-rich-editor"
contentEditable
data-placeholder={placeholder}
role="textbox"
aria-multiline="true"
spellCheck={false}
style={editorStyle}
suppressContentEditableWarning
tabIndex={0}
onFocus={onFocus}
onInput={handleInput}
onPaste={(event) => {
event.preventDefault();
document.execCommand("insertText", false, event.clipboardData.getData("text/plain"));
}}
>
{text ? renderTemplateParamSegments(text) : null}
<div className="param-value-rich-editor-shell" style={editorStyle}>
<div ref={highlightRef} className="param-value-rich-editor-highlight" aria-hidden>
{text ? (
<>
{renderTemplateParamSegments(text)}
<span className="param-value-editor-tail"> </span>
</>
) : null}
</div>
<Input.TextArea
className="param-value-rich-editor"
value={text}
placeholder={placeholder}
rows={rows}
spellCheck={false}
onFocus={onFocus}
onChange={handleChange}
onScroll={handleScroll}
/>
</div>
);
}