feat: 界面优化
parent
77879f4e0e
commit
7671f94fcd
|
|
@ -119,21 +119,23 @@
|
|||
}
|
||||
|
||||
.meetings-card-scroll .ant-list-items {
|
||||
flex: 1 1 auto;
|
||||
min-height: 100%;
|
||||
flex: 0 0 auto;
|
||||
min-height: 0;
|
||||
height: auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--meeting-card-columns), minmax(0, 1fr));
|
||||
grid-auto-rows: minmax(var(--meeting-card-grid-min-height), 1fr);
|
||||
grid-auto-rows: var(--meeting-card-grid-min-height);
|
||||
gap: var(--meeting-card-grid-gap);
|
||||
align-items: stretch;
|
||||
align-content: stretch;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.meetings-card-scroll--full-page .ant-list-items {
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
grid-template-rows: repeat(var(--meeting-card-rows), minmax(var(--meeting-card-grid-min-height), 1fr));
|
||||
align-content: stretch;
|
||||
}
|
||||
|
||||
.meeting-card-list-item.ant-list-item {
|
||||
|
|
|
|||
|
|
@ -111,6 +111,23 @@
|
|||
min-width: 0;
|
||||
}
|
||||
|
||||
.prompt-template-editor-header {
|
||||
align-items: center;
|
||||
margin: 24px 0 20px;
|
||||
}
|
||||
|
||||
.prompt-template-editor-header__col {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 25px;
|
||||
}
|
||||
|
||||
.prompt-template-editor-title {
|
||||
min-width: 0;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.prompt-template-editor {
|
||||
height: calc(100vh - 400px);
|
||||
}
|
||||
|
|
@ -142,10 +159,25 @@
|
|||
}
|
||||
|
||||
.prompt-template-editor__preview-meta {
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
max-width: min(420px, 40vw);
|
||||
}
|
||||
|
||||
.prompt-template-editor__preview-meta .ant-tag {
|
||||
max-width: 100%;
|
||||
margin-inline-end: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.prompt-template-editor__preview-meta {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.prompt-template-editor {
|
||||
height: auto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import {
|
|||
App,
|
||||
Button,
|
||||
Col,
|
||||
Divider,
|
||||
Empty,
|
||||
Form,
|
||||
Input,
|
||||
|
|
@ -562,7 +561,25 @@ const PromptTemplates: React.FC = () => {
|
|||
</Col>
|
||||
</Row>
|
||||
|
||||
<Divider orientation="left">提示词编辑器 (Markdown 实时预览)</Divider>
|
||||
<Row gutter={[12, 16]} className="prompt-template-editor-header">
|
||||
<Col xs={24} xl={12} className="prompt-template-editor-header__col">
|
||||
<div className="prompt-template-editor-title">
|
||||
提示词编辑器 (Markdown 实时预览)
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={24} xl={12} className="prompt-template-editor-header__col">
|
||||
<div className="prompt-template-editor__preview-meta">
|
||||
{selectedHotWordGroupId ? (
|
||||
<Tag color="blue">
|
||||
绑定热词组:
|
||||
{groupOptions.find((item) => item.id === selectedHotWordGroupId)?.groupName || '已选择'}
|
||||
</Tag>
|
||||
) : (
|
||||
<Tag>未绑定热词组</Tag>
|
||||
)}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[12, 16]} className="prompt-template-editor">
|
||||
<Col xs={24} xl={12} className="prompt-template-editor__col">
|
||||
<Form.Item name="promptContent" noStyle rules={[{ required: true }]}>
|
||||
|
|
@ -574,16 +591,6 @@ const PromptTemplates: React.FC = () => {
|
|||
</Form.Item>
|
||||
</Col>
|
||||
<Col xs={24} xl={12} className="prompt-template-editor__preview-col">
|
||||
<div className="prompt-template-editor__preview-meta">
|
||||
{selectedHotWordGroupId ? (
|
||||
<Tag color="blue">
|
||||
绑定热词组:
|
||||
{groupOptions.find((item) => item.id === selectedHotWordGroupId)?.groupName || '已选择'}
|
||||
</Tag>
|
||||
) : (
|
||||
<Tag>未绑定热词组</Tag>
|
||||
)}
|
||||
</div>
|
||||
<div className="prompt-template-editor__preview markdown-preview"><ReactMarkdown>{previewContent}</ReactMarkdown></div>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
|||
|
|
@ -76,6 +76,12 @@
|
|||
background: #f9fafe;
|
||||
}
|
||||
|
||||
.screen-saver-page .screen-saver-table-thumb .ant-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.screen-saver-page .screen-saver-table-thumb img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
|
@ -83,6 +89,19 @@
|
|||
object-fit: cover;
|
||||
}
|
||||
|
||||
.screen-saver-page .screen-saver-table-thumb__image {
|
||||
cursor: zoom-in;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.screen-saver-page .screen-saver-table-thumb:hover .screen-saver-table-thumb__image {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
.screen-saver-page .screen-saver-table-thumb .ant-image-mask {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.screen-saver-page .screen-saver-table-thumb__empty {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
|
@ -124,15 +143,33 @@
|
|||
max-width: 420px;
|
||||
margin: 0 auto;
|
||||
aspect-ratio: 8 / 5;
|
||||
display: block;
|
||||
padding: 0;
|
||||
border-radius: 4px;
|
||||
background: #f5f6fa;
|
||||
border: 1px solid #e6e6e6;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.screen-saver-drawer .screen-saver-preview-stage::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.screen-saver-drawer .screen-saver-preview-stage:hover,
|
||||
.screen-saver-drawer .screen-saver-preview-stage:focus-visible {
|
||||
border-color: #3c70f5;
|
||||
}
|
||||
|
||||
.screen-saver-drawer .screen-saver-preview-stage:focus-visible {
|
||||
outline: 2px solid rgba(60, 112, 245, 0.28);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.screen-saver-drawer .screen-saver-preview-stage:disabled {
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
.screen-saver-drawer .screen-saver-preview-stage img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
|
@ -147,6 +184,32 @@
|
|||
color: #8c9bb3;
|
||||
}
|
||||
|
||||
.screen-saver-drawer .screen-saver-preview-stage__action {
|
||||
position: absolute;
|
||||
inset: auto 12px 12px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
min-height: 32px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
background: rgba(16, 35, 63, 0.74);
|
||||
color: #ffffff;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
opacity: 0;
|
||||
transform: translateY(4px);
|
||||
transition: opacity 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.screen-saver-drawer .screen-saver-preview-stage:hover .screen-saver-preview-stage__action,
|
||||
.screen-saver-drawer .screen-saver-preview-stage:focus-visible .screen-saver-preview-stage__action,
|
||||
.screen-saver-drawer .screen-saver-preview-stage__empty + .screen-saver-preview-stage__action {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.screen-saver-crop-modal .ant-modal-content {
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
Drawer,
|
||||
Empty,
|
||||
Form,
|
||||
Image as AntImage,
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
|
|
@ -618,7 +619,12 @@ export default function ScreenSaverManagement() {
|
|||
render: (_, record) => (
|
||||
<div className="screen-saver-table-thumb">
|
||||
{record.imageUrl ? (
|
||||
<img src={record.imageUrl} alt={record.name} />
|
||||
<AntImage
|
||||
src={record.imageUrl}
|
||||
alt={record.name}
|
||||
className="screen-saver-table-thumb__image"
|
||||
preview={{ mask: "查看大图" }}
|
||||
/>
|
||||
) : (
|
||||
<div className="screen-saver-table-thumb__empty">
|
||||
<PictureOutlined style={{ color: '#ccc', fontSize: 20 }} />
|
||||
|
|
@ -876,24 +882,30 @@ export default function ScreenSaverManagement() {
|
|||
<Title level={5} style={{ margin: 0 }}>屏保成片预览</Title>
|
||||
<Text type="secondary">固定 8:5 构图,根据设定尺寸导出。上传后后端只做校验与存储。</Text>
|
||||
</div>
|
||||
<Upload {...uploadProps}>
|
||||
<Button type="primary" icon={<UploadOutlined />} loading={uploading}>
|
||||
选择图片并裁剪
|
||||
</Button>
|
||||
</Upload>
|
||||
</Space>
|
||||
<div className="screen-saver-preview-stage">
|
||||
{currentImageUrl ? (
|
||||
<img src={currentImageUrl} alt="屏保预览" />
|
||||
) : (
|
||||
<div className="screen-saver-preview-stage__empty">
|
||||
<Space direction="vertical" align="center" size={10}>
|
||||
<ScissorOutlined style={{ fontSize: 26 }} />
|
||||
<span>请选择图片并完成裁剪后上传</span>
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Upload {...uploadProps}>
|
||||
<button
|
||||
type="button"
|
||||
className="screen-saver-preview-stage"
|
||||
disabled={uploading}
|
||||
aria-label={currentImageUrl ? "更换屏保图片并裁剪" : "选择屏保图片并裁剪"}
|
||||
>
|
||||
{currentImageUrl ? (
|
||||
<img src={currentImageUrl} alt="屏保预览" />
|
||||
) : (
|
||||
<span className="screen-saver-preview-stage__empty">
|
||||
<Space direction="vertical" align="center" size={10}>
|
||||
<ScissorOutlined style={{ fontSize: 26 }} />
|
||||
<span>请选择图片并完成裁剪后上传</span>
|
||||
</Space>
|
||||
</span>
|
||||
)}
|
||||
<span className="screen-saver-preview-stage__action" aria-hidden="true">
|
||||
<UploadOutlined />
|
||||
<span>{currentImageUrl ? "更换图片并裁剪" : "选择图片并裁剪"}</span>
|
||||
</span>
|
||||
</button>
|
||||
</Upload>
|
||||
<Space wrap>
|
||||
<span className="screen-saver-preview-pill">输出规格 {currentWidth} × {currentHeight}</span>
|
||||
<span className="screen-saver-preview-pill">当前作用域 {currentScopeType === "USER" ? "用户级" : "平台级"}</span>
|
||||
|
|
@ -924,7 +936,7 @@ export default function ScreenSaverManagement() {
|
|||
</Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item name="imageUrl" label="图片地址" rules={[{ required: true, message: "请先上传裁剪后的屏保图片" }]}>
|
||||
<Form.Item name="imageUrl" hidden rules={[{ required: true, message: "请先上传裁剪后的屏保图片" }]}>
|
||||
<Input placeholder="上传完成后自动回填" readOnly />
|
||||
</Form.Item>
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,85 @@
|
|||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.param-value-template-preview {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: default;
|
||||
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.param-value-template-token {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
margin: 0 2px;
|
||||
padding: 0 5px;
|
||||
border: 1px solid #91caff;
|
||||
border-radius: 4px;
|
||||
background: #e6f4ff;
|
||||
color: #1677ff;
|
||||
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.param-value-template-popover {
|
||||
max-width: min(720px, 70vw);
|
||||
}
|
||||
|
||||
.param-value-template-popover__content {
|
||||
max-width: min(680px, 66vw);
|
||||
max-height: 360px;
|
||||
margin: 0 !important;
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
user-select: text;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.param-value-rich-editor {
|
||||
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;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.param-value-rich-editor:hover {
|
||||
border-color: #4096ff;
|
||||
}
|
||||
|
||||
.param-value-rich-editor:focus {
|
||||
border-color: #1677ff;
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Button, Col, Drawer, Form, Input, Popconfirm, Row, Segmented, Select, Space, Switch, Tag, Tooltip, Typography, message } from "antd";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
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 { useTranslation } from "react-i18next";
|
||||
import { DeleteOutlined, EditOutlined, InfoCircleOutlined, PlusOutlined, SearchOutlined, SettingOutlined } from "@ant-design/icons";
|
||||
import { createParam, deleteParam, pageParams, updateParam } from "@/api";
|
||||
|
|
@ -14,12 +14,176 @@ import LongTextPreview from "@/components/shared/LongTextPreview";
|
|||
import type { SysParamQuery, SysParamVO } from "@/types";
|
||||
import "./index.less";
|
||||
|
||||
const { Text } = Typography;
|
||||
const { Paragraph, Text } = Typography;
|
||||
const TEMPLATE_PARAM_PATTERN = /\{\{[^{}\s]+\}\}/g;
|
||||
|
||||
/** Normalize paramType to title-case for case-insensitive comparison */
|
||||
const isType = (actual: string | undefined, expected: string) =>
|
||||
(actual || "").toLowerCase() === expected.toLowerCase();
|
||||
|
||||
const hasTemplateParam = (text: string) => {
|
||||
TEMPLATE_PARAM_PATTERN.lastIndex = 0;
|
||||
const matched = TEMPLATE_PARAM_PATTERN.test(text);
|
||||
TEMPLATE_PARAM_PATTERN.lastIndex = 0;
|
||||
return matched;
|
||||
};
|
||||
|
||||
const renderTemplateParamSegments = (text: string) => {
|
||||
const segments: ReactNode[] = [];
|
||||
let lastIndex = 0;
|
||||
|
||||
TEMPLATE_PARAM_PATTERN.lastIndex = 0;
|
||||
text.replace(TEMPLATE_PARAM_PATTERN, (match: string, offset: number) => {
|
||||
if (offset > lastIndex) {
|
||||
segments.push(text.slice(lastIndex, offset));
|
||||
}
|
||||
segments.push(
|
||||
<span className="param-value-template-token" key={`${match}-${offset}`}>
|
||||
{match}
|
||||
</span>
|
||||
);
|
||||
lastIndex = offset + match.length;
|
||||
return match;
|
||||
});
|
||||
TEMPLATE_PARAM_PATTERN.lastIndex = 0;
|
||||
|
||||
if (lastIndex < text.length) {
|
||||
segments.push(text.slice(lastIndex));
|
||||
}
|
||||
|
||||
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,
|
||||
onFocus,
|
||||
onTextChange,
|
||||
placeholder,
|
||||
rows = 8
|
||||
}: {
|
||||
value?: string | number | null;
|
||||
onChange?: (value: string) => void;
|
||||
onFocus?: () => void;
|
||||
onTextChange?: (value: string) => void;
|
||||
placeholder?: string;
|
||||
rows?: number;
|
||||
}) {
|
||||
const editorRef = useRef<HTMLDivElement>(null);
|
||||
const pendingCaretOffsetRef = useRef<number | null>(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);
|
||||
onChange?.(nextValue);
|
||||
onTextChange?.(nextValue);
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
function TemplateParamValuePreview({ value }: { value?: string | number | null }) {
|
||||
const text = value === undefined || value === null || value === "" ? "-" : String(value);
|
||||
|
||||
if (!hasTemplateParam(text)) {
|
||||
return <LongTextPreview value={value} code />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover
|
||||
overlayClassName="param-value-template-popover"
|
||||
placement="topLeft"
|
||||
content={
|
||||
<Paragraph className="param-value-template-popover__content" copyable={{ text }}>
|
||||
{renderTemplateParamSegments(text)}
|
||||
</Paragraph>
|
||||
}
|
||||
>
|
||||
<span className="param-value-template-preview">{renderTemplateParamSegments(text)}</span>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SysParams() {
|
||||
const { t } = useTranslation();
|
||||
const { can } = usePermission();
|
||||
|
|
@ -173,7 +337,7 @@ export default function SysParams() {
|
|||
title: t("sysParams.paramValue"),
|
||||
dataIndex: "paramValue",
|
||||
key: "paramValue",
|
||||
width: 360,
|
||||
width: 520,
|
||||
ellipsis: { showTitle: false },
|
||||
render: (text: string, record: SysParamVO) => {
|
||||
const type = record.paramType;
|
||||
|
|
@ -184,10 +348,10 @@ export default function SysParams() {
|
|||
}
|
||||
// JSON: 等宽字体,以纯字符串显示
|
||||
if (isType(type, "JSON")) {
|
||||
return <LongTextPreview value={text} code />;
|
||||
return <TemplateParamValuePreview value={text} />;
|
||||
}
|
||||
// 数字和字符串:等宽代码文本,悬浮查看完整内容。
|
||||
return <LongTextPreview value={text} code />;
|
||||
return <TemplateParamValuePreview value={text} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -271,7 +435,7 @@ export default function SysParams() {
|
|||
columns={columns}
|
||||
dataSource={data}
|
||||
loading={loading}
|
||||
scroll={{ x: "max(100%, 1350px)", y: "100%" }}
|
||||
scroll={{ x: "max(100%, 1510px)", y: "100%" }}
|
||||
pagination={false}
|
||||
/>
|
||||
</DataListPanel>
|
||||
|
|
@ -338,16 +502,14 @@ export default function SysParams() {
|
|||
]}
|
||||
/>
|
||||
) : isType(paramType, "JSON") ? (
|
||||
<Input.TextArea
|
||||
<TemplateParamValueEditor
|
||||
rows={6}
|
||||
style={{ fontFamily: "'SFMono-Regular', Consolas, Menlo, monospace" }}
|
||||
placeholder={t("sysParamsExt.jsonPlaceholder")}
|
||||
onFocus={() => {
|
||||
const v = drawerForm.getFieldValue("paramValue");
|
||||
if (!v) drawerForm.setFieldValue("paramValue", JSON.stringify({ key: "value" }));
|
||||
}}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value;
|
||||
onTextChange={(v) => {
|
||||
if (!v) {
|
||||
setJsonError(null);
|
||||
return;
|
||||
|
|
@ -361,7 +523,7 @@ export default function SysParams() {
|
|||
}}
|
||||
/>
|
||||
) : (
|
||||
<Input.TextArea rows={3} placeholder={t("sysParamsExt.paramValuePlaceholder")} />
|
||||
<TemplateParamValueEditor rows={8} placeholder={t("sysParamsExt.paramValuePlaceholder")} />
|
||||
)}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
|
|
|
|||
Loading…
Reference in New Issue