mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-30 02:58:05 +08:00
parent
b74112ac30
commit
01d9f4d0c1
@ -360,6 +360,8 @@
|
||||
&-input {
|
||||
flex: 1;
|
||||
margin-right: #{px2rem(10px)};
|
||||
padding-right: 0;
|
||||
max-width: calc(100% - #{px2rem(42px)});
|
||||
}
|
||||
|
||||
&-action {
|
||||
@ -384,6 +386,9 @@
|
||||
|
||||
&-ResultBox {
|
||||
padding-right: 10px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
span.c-field {
|
||||
background: #007bff;
|
||||
padding: 3px 5px;
|
||||
|
@ -129,7 +129,7 @@ export class FormulaEditor extends React.Component<
|
||||
static highlightValue(
|
||||
value: string,
|
||||
variables: Array<VariableItem>,
|
||||
functions: Array<FuncGroup>
|
||||
evalMode: boolean = true
|
||||
) {
|
||||
if (!Array.isArray(variables) || !variables.length || !value) {
|
||||
return;
|
||||
@ -139,10 +139,12 @@ export class FormulaEditor extends React.Component<
|
||||
[propname: string]: string;
|
||||
} = {};
|
||||
|
||||
eachTree(
|
||||
variables,
|
||||
item => item.value && (varMap[item.value] = item.label)
|
||||
);
|
||||
eachTree(variables, item => {
|
||||
if (item.value) {
|
||||
const key = evalMode ? item.value : '${' + item.value + '}';
|
||||
varMap[key] = item.label;
|
||||
}
|
||||
});
|
||||
const vars = Object.keys(varMap)
|
||||
.filter(item => item)
|
||||
.sort((a, b) => b.length - a.length);
|
||||
@ -159,7 +161,7 @@ export class FormulaEditor extends React.Component<
|
||||
let from = 0;
|
||||
let idx = -1;
|
||||
while (~(idx = content.indexOf(v, from))) {
|
||||
html = content.replace(v, `<span class="c-field">${varMap[v]}</span>`);
|
||||
html = html.replace(v, `<span class="c-field">${varMap[v]}</span>`);
|
||||
from = idx + v.length;
|
||||
}
|
||||
});
|
||||
|
@ -3,19 +3,20 @@ import React from 'react';
|
||||
import {FormulaEditor, FormulaEditorProps} from './Editor';
|
||||
import {autobind, noop} from '../../utils/helper';
|
||||
import {generateIcon} from '../../utils/icon';
|
||||
import PickerContainer from '../PickerContainer';
|
||||
import Editor from './Editor';
|
||||
import ResultBox from '../ResultBox';
|
||||
import Button from '../Button';
|
||||
import {Icon} from '../icons';
|
||||
import Modal from '../Modal';
|
||||
import {themeable} from '../../theme';
|
||||
import {localeable} from '../../locale';
|
||||
|
||||
import type {SchemaIcon} from '../../Schema';
|
||||
import {
|
||||
resolveVariableAndFilter,
|
||||
isPureVariable
|
||||
} from '../../utils/tpl-builtin';
|
||||
import {toast} from '../../components';
|
||||
import {parse, Evaluator} from 'amis-formula';
|
||||
|
||||
export interface FormulaPickerProps extends FormulaEditorProps {
|
||||
// 新的属性?
|
||||
@ -95,9 +96,31 @@ export interface FormulaPickerProps extends FormulaEditorProps {
|
||||
data?: any;
|
||||
}
|
||||
|
||||
export class FormulaPicker extends React.Component<FormulaPickerProps> {
|
||||
export interface FormulaPickerState {
|
||||
isOpened: boolean;
|
||||
value: string;
|
||||
editorValue: string;
|
||||
isError: boolean | string;
|
||||
}
|
||||
|
||||
export class FormulaPicker extends React.Component<
|
||||
FormulaPickerProps,
|
||||
FormulaPickerState
|
||||
> {
|
||||
static defaultProps = {
|
||||
evalMode: true
|
||||
};
|
||||
|
||||
state: FormulaPickerState = {
|
||||
isOpened: false,
|
||||
value: this.props.value,
|
||||
editorValue: this.props.value,
|
||||
isError: false
|
||||
};
|
||||
|
||||
@autobind
|
||||
handleConfirm(value: any) {
|
||||
handleConfirm() {
|
||||
const value = this.state.value;
|
||||
this.props.onChange?.(value);
|
||||
}
|
||||
|
||||
@ -116,10 +139,94 @@ export class FormulaPicker extends React.Component<FormulaPickerProps> {
|
||||
);
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleInputChange(value: string) {
|
||||
const validate = this.validate(value);
|
||||
if (validate === true) {
|
||||
this.setState(
|
||||
{
|
||||
value,
|
||||
isError: false
|
||||
},
|
||||
() => this.handleConfirm()
|
||||
);
|
||||
} else {
|
||||
this.setState({isError: validate});
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleEditorChange(value: string) {
|
||||
this.setState({
|
||||
editorValue: value
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleEditorConfirm() {
|
||||
const {translate: __} = this.props;
|
||||
const value = this.state.editorValue;
|
||||
const validate = this.validate(value, true);
|
||||
|
||||
if (validate === true) {
|
||||
this.setState({value}, () => {
|
||||
this.close(undefined, () => this.handleConfirm());
|
||||
});
|
||||
} else {
|
||||
this.setState({isError: validate});
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleClick() {
|
||||
this.setState({
|
||||
editorValue: this.props.value,
|
||||
isOpened: true
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
close(e?: any, callback?: () => void) {
|
||||
this.setState(
|
||||
{
|
||||
isOpened: false,
|
||||
isError: false
|
||||
},
|
||||
() => {
|
||||
if (callback) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@autobind
|
||||
validate(value: string, remind?: boolean) {
|
||||
const {translate: __} = this.props;
|
||||
|
||||
try {
|
||||
const ast = parse(value, {
|
||||
evalMode: this.props.evalMode,
|
||||
allowFilter: false
|
||||
});
|
||||
|
||||
new Evaluator({}).evalute(ast);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
const [, position] = /\s(\d+:\d+)$/.exec(e.message) || [];
|
||||
remind &&
|
||||
toast.error(
|
||||
__('FormulaEditor.invalidData', {position: position || '-'})
|
||||
);
|
||||
return position;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
classnames: cx,
|
||||
value,
|
||||
translate: __,
|
||||
disabled,
|
||||
allowInput,
|
||||
@ -139,6 +246,8 @@ export class FormulaPicker extends React.Component<FormulaPickerProps> {
|
||||
functions,
|
||||
...rest
|
||||
} = this.props;
|
||||
const {isOpened, value, editorValue, isError} = this.state;
|
||||
|
||||
if (isPureVariable(variables)) {
|
||||
// 如果 variables 是 ${xxx} 这种形式,将其处理成实际的值
|
||||
variables = resolveVariableAndFilter(variables, this.props.data, '| raw');
|
||||
@ -151,98 +260,110 @@ export class FormulaPicker extends React.Component<FormulaPickerProps> {
|
||||
const iconElement = generateIcon(cx, icon, 'Icon');
|
||||
|
||||
return (
|
||||
<PickerContainer
|
||||
title={__(title || 'FormulaEditor.title')}
|
||||
headerClassName="font-bold"
|
||||
bodyRender={({onClose, value, onChange}) => {
|
||||
return (
|
||||
<>
|
||||
<div className={cx('FormulaPicker', className)}>
|
||||
{mode === 'button' ? (
|
||||
<Button
|
||||
className={cx('FormulaPicker-action', 'w-full')}
|
||||
level={level}
|
||||
size={btnSize}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
{iconElement ? (
|
||||
React.cloneElement(iconElement, {
|
||||
className: cx(
|
||||
iconElement?.props?.className ?? '',
|
||||
'FormulaPicker-icon',
|
||||
{
|
||||
['is-filled']: !!value
|
||||
}
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<Icon
|
||||
icon="function"
|
||||
className={cx('FormulaPicker-icon', 'icon', {
|
||||
['is-filled']: !!value
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
<span className={cx('FormulaPicker-label')}>
|
||||
{__(btnLabel || 'FormulaEditor.btnLabel')}
|
||||
</span>
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<ResultBox
|
||||
className={cx(
|
||||
'FormulaPicker-input',
|
||||
isOpened ? 'is-active' : '',
|
||||
!!isError ? 'is-error' : ''
|
||||
)}
|
||||
allowInput={allowInput}
|
||||
clearable={clearable}
|
||||
value={value}
|
||||
result={
|
||||
allowInput
|
||||
? void 0
|
||||
: FormulaEditor.highlightValue(
|
||||
value,
|
||||
variables,
|
||||
this.props.evalMode
|
||||
)
|
||||
}
|
||||
itemRender={this.renderFormulaValue}
|
||||
onResultChange={noop}
|
||||
onChange={this.handleInputChange}
|
||||
disabled={disabled}
|
||||
borderMode={borderMode}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
|
||||
<Button
|
||||
className={cx('FormulaPicker-action')}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<Icon
|
||||
icon="function"
|
||||
className={cx('FormulaPicker-icon', 'icon', {
|
||||
['is-filled']: !!value
|
||||
})}
|
||||
/>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{!!isError ? (
|
||||
<ul className={cx('Form-feedback')}>
|
||||
<li>{__('FormulaEditor.invalidData', {position: isError})}</li>
|
||||
</ul>
|
||||
) : null}
|
||||
<Modal
|
||||
size="md"
|
||||
closeOnEsc
|
||||
show={this.state.isOpened}
|
||||
onHide={this.close}
|
||||
>
|
||||
<Modal.Header onClose={this.close} className="font-bold">
|
||||
{__(title || 'FormulaEditor.title')}
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Editor
|
||||
{...rest}
|
||||
variables={variables}
|
||||
functions={functions}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
value={editorValue}
|
||||
onChange={this.handleEditorChange}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
value={value}
|
||||
onConfirm={this.handleConfirm}
|
||||
size={'md'}
|
||||
>
|
||||
{({onClick, isOpened}) => (
|
||||
<div className={cx('FormulaPicker', className)}>
|
||||
{mode === 'button' ? (
|
||||
<Button
|
||||
className={cx('FormulaPicker-action', 'w-full')}
|
||||
level={level}
|
||||
size={btnSize}
|
||||
onClick={onClick}
|
||||
>
|
||||
{iconElement ? (
|
||||
React.cloneElement(iconElement, {
|
||||
className: cx(
|
||||
iconElement?.props?.className ?? '',
|
||||
'FormulaPicker-icon',
|
||||
{
|
||||
['is-filled']: !!value
|
||||
}
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<Icon
|
||||
icon="function"
|
||||
className={cx('FormulaPicker-icon', 'icon', {
|
||||
['is-filled']: !!value
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
<span className={cx('FormulaPicker-label')}>
|
||||
{__(btnLabel || 'FormulaEditor.btnLabel')}
|
||||
</span>
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<ResultBox
|
||||
className={cx(
|
||||
'FormulaPicker-input',
|
||||
isOpened ? 'is-active' : ''
|
||||
)}
|
||||
allowInput={allowInput}
|
||||
clearable={clearable}
|
||||
value={value}
|
||||
result={
|
||||
allowInput
|
||||
? void 0
|
||||
: FormulaEditor.highlightValue(
|
||||
value,
|
||||
variables,
|
||||
functions
|
||||
)
|
||||
}
|
||||
itemRender={this.renderFormulaValue}
|
||||
onResultChange={noop}
|
||||
onChange={this.handleConfirm}
|
||||
disabled={disabled}
|
||||
borderMode={borderMode}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
|
||||
<Button
|
||||
className={cx('FormulaPicker-action')}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Icon
|
||||
icon="function"
|
||||
className={cx('FormulaPicker-icon', 'icon', {
|
||||
['is-filled']: !!value
|
||||
})}
|
||||
/>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</PickerContainer>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button onClick={this.close}>{__('cancel')}</Button>
|
||||
<Button onClick={this.handleEditorConfirm} level="primary">
|
||||
{__('confirm')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -39,30 +39,37 @@ export class FormulaPlugin {
|
||||
}
|
||||
|
||||
insertContent(value: any, type: 'variable' | 'func') {
|
||||
const {evalMode} = this.getProps();
|
||||
|
||||
const from = this.editor.getCursor();
|
||||
if (type === 'variable') {
|
||||
this.editor.replaceSelection(value.key);
|
||||
const key = evalMode ? value.key : '${' + value.key + '}';
|
||||
this.editor.replaceSelection(key);
|
||||
var to = this.editor.getCursor();
|
||||
|
||||
this.markText(from, to, value.name, 'cm-field');
|
||||
} else if (type === 'func') {
|
||||
// todo 支持 snippet,目前是不支持的
|
||||
|
||||
this.editor.replaceSelection(`${value}()`);
|
||||
const key = evalMode ? `${value}()` : '${' + value + '()}';
|
||||
this.editor.replaceSelection(key);
|
||||
var to = this.editor.getCursor();
|
||||
this.markText(
|
||||
from,
|
||||
{
|
||||
line: to.line,
|
||||
ch: to.ch - 2
|
||||
},
|
||||
value,
|
||||
'cm-func'
|
||||
);
|
||||
|
||||
// todo 模板模式下 ${XXX()} 高亮处理
|
||||
evalMode &&
|
||||
this.markText(
|
||||
from,
|
||||
{
|
||||
line: to.line,
|
||||
ch: to.ch - 2
|
||||
},
|
||||
value,
|
||||
'cm-func'
|
||||
);
|
||||
|
||||
this.editor.setCursor({
|
||||
line: to.line,
|
||||
ch: to.ch - 1
|
||||
ch: evalMode ? to.ch - 1 : to.ch - 2
|
||||
});
|
||||
} else if (typeof value === 'string') {
|
||||
this.editor.replaceSelection(value);
|
||||
@ -90,15 +97,17 @@ export class FormulaPlugin {
|
||||
if (!Array.isArray(variables) || !variables.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {evalMode} = this.getProps();
|
||||
const varMap: {
|
||||
[propname: string]: string;
|
||||
} = {};
|
||||
|
||||
eachTree(
|
||||
variables,
|
||||
item => item.value && (varMap[item.value] = item.label)
|
||||
);
|
||||
eachTree(variables, item => {
|
||||
if (item.value) {
|
||||
const key = evalMode ? item.value : '${' + item.value + '}';
|
||||
varMap[key] = item.label;
|
||||
}
|
||||
});
|
||||
const vars = Object.keys(varMap).sort((a, b) => b.length - a.length);
|
||||
|
||||
const editor = this.editor;
|
||||
|
@ -301,6 +301,7 @@ register('de-DE', {
|
||||
'FormulaEditor.title': 'Formel Editor',
|
||||
'FormulaEditor.variable': 'Variable',
|
||||
'FormulaEditor.function': 'Funktion',
|
||||
'FormulaEditor.invalidData': 'Überprüfungsfehler, position in {{position}}',
|
||||
'pullRefresh.pullingText': 'Zum Aktualisieren nach unten ziehen...',
|
||||
'pullRefresh.loosingText': 'Zum Aktualisieren freigeben...',
|
||||
'pullRefresh.loadingText': 'Laden...',
|
||||
|
@ -303,6 +303,7 @@ register('en-US', {
|
||||
'FormulaEditor.title': 'Formula Editor',
|
||||
'FormulaEditor.variable': 'Variable',
|
||||
'FormulaEditor.function': 'Function',
|
||||
'FormulaEditor.invalidData': 'invalid data, position in {{position}}',
|
||||
'pullRefresh.pullingText': 'Pull down to refresh...',
|
||||
'pullRefresh.loosingText': 'Release to refresh...',
|
||||
'pullRefresh.loadingText': 'Loading...',
|
||||
|
@ -310,6 +310,7 @@ register('zh-CN', {
|
||||
'FormulaEditor.title': '公式编辑器',
|
||||
'FormulaEditor.variable': '变量',
|
||||
'FormulaEditor.function': '函数',
|
||||
'FormulaEditor.invalidData': '公式值校验错误,错误的位置是 {{position}}',
|
||||
'pullRefresh.pullingText': '下拉即可刷新...',
|
||||
'pullRefresh.loosingText': '释放即可刷新...',
|
||||
'pullRefresh.loadingText': '加载中...',
|
||||
|
Loading…
Reference in New Issue
Block a user