mirror of
https://github.com/NetEase/tango.git
synced 2026-09-03 06:33:51 +08:00
fix: remove some useless setters
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
"@typescript-eslint/no-unused-vars": "warn",
|
||||
"import/no-cycle": "off",
|
||||
"no-nested-ternary": "off",
|
||||
"no-useless-return":"off"
|
||||
"no-useless-return":"off",
|
||||
"no-param-reassign": "off",
|
||||
"prefer-destructuring": "off"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,4 +12,4 @@ export * from './sidebar';
|
||||
export * from './toolbar';
|
||||
export * from './selection-menu';
|
||||
|
||||
export { register as registerSetter } from '@music163/tango-setting-form';
|
||||
export { register as registerSetter, FormItemComponentProps, FormItemCreateOptionsType } from '@music163/tango-setting-form';
|
||||
|
||||
@@ -9,23 +9,27 @@ import { DndQuery, useDnd } from '../dnd';
|
||||
import { Navigator } from './navigator';
|
||||
import { SelectionToolsProps } from '../simulator/selection';
|
||||
|
||||
type SandboxEventHandlerConfig = {
|
||||
interface ISandboxEventHandlerConfig {
|
||||
sandboxQuery?: DndQuery;
|
||||
sandboxType?: 'design' | 'preview';
|
||||
isActive: boolean;
|
||||
[x: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
export type SandboxProps = Omit<CodeSandboxProps, 'files' | 'eventHandlers' | 'onMessage'> & {
|
||||
isPreview?: boolean;
|
||||
selectionTools?: SelectionToolsProps['actions'];
|
||||
builtinSelectionMenuMap?: SelectionToolsProps['builtinActionMap'];
|
||||
/**
|
||||
* tangoConfigJson 处理器
|
||||
*/
|
||||
configFormatter?: IMergeTangoConfigJsonConfig['formatter'];
|
||||
sandboxType?: 'design' | 'preview';
|
||||
mode?: 'single' | 'combined';
|
||||
injectScript?: string;
|
||||
onViewChange?: (data: any, config?: SandboxEventHandlerConfig) => void;
|
||||
onMessage?: (data: any, config?: SandboxEventHandlerConfig) => void;
|
||||
onLoad?: (config?: SandboxEventHandlerConfig) => void;
|
||||
onViewChange?: (data: any, config?: ISandboxEventHandlerConfig) => void;
|
||||
onMessage?: (data: any, config?: ISandboxEventHandlerConfig) => void;
|
||||
onLoad?: (config?: ISandboxEventHandlerConfig) => void;
|
||||
};
|
||||
|
||||
export type CombinedSandboxRef = {
|
||||
@@ -37,6 +41,7 @@ const LANDING_PAGE_PATH = '/__background_landing_page__';
|
||||
|
||||
function useSandbox({
|
||||
isPreview: isPreviewProp,
|
||||
configFormatter,
|
||||
onViewChange,
|
||||
onMessage: onMessageProp,
|
||||
onLoad: onLoadProp,
|
||||
@@ -70,12 +75,12 @@ function useSandbox({
|
||||
let files = Array.from(workspace.files.keys()).reduce((prev, filename) => {
|
||||
let code = workspace.getFile(filename).code;
|
||||
if (filename === '/tango.config.json') {
|
||||
code = mergeTangoConfigJson(code, isPreview, { injectScript });
|
||||
code = mergeTangoConfigJson(code, { isPreview, injectScript, formatter: configFormatter });
|
||||
}
|
||||
prev[filename] = { code };
|
||||
return prev;
|
||||
}, {});
|
||||
files = normalizeFiles(files, workspace.entry);
|
||||
files = fixSandboxFiles(files, workspace.entry);
|
||||
|
||||
const onMessage = (data: any) => onMessageProp && onMessageProp(data, getSandboxConfig());
|
||||
const onLoad = () => onLoadProp && onLoadProp(getSandboxConfig());
|
||||
@@ -188,7 +193,7 @@ export const CombinedSandbox = observer(
|
||||
const activeSandbox = useRef<string>();
|
||||
const [startRoute, setStartRoute] = useState(workspace.activeRoute);
|
||||
|
||||
const onViewChange = (data: any, config: SandboxEventHandlerConfig) => {
|
||||
const onViewChange = (data: any, config: ISandboxEventHandlerConfig) => {
|
||||
if (config.isActive) {
|
||||
const curPath = data?.pathname + data?.search;
|
||||
const isSandboxChanged = config.sandboxType !== activeSandbox.current;
|
||||
@@ -296,7 +301,7 @@ export const Sandbox = observer(
|
||||
: combinedSandboxRef.current?.designSandbox;
|
||||
}
|
||||
|
||||
const onViewChange = (data: any, { isActive }: SandboxEventHandlerConfig) => {
|
||||
const onViewChange = (data: any, { isActive }: ISandboxEventHandlerConfig) => {
|
||||
if (isActive) {
|
||||
navigatorRef.current.changeRelativeUrl(data?.pathname + data?.search);
|
||||
}
|
||||
@@ -380,7 +385,7 @@ export const Sandbox = observer(
|
||||
);
|
||||
|
||||
// 兼容 tango.config.json,转成 sandbox.config.json
|
||||
function normalizeFiles(files: object, entry = '/src/index.js') {
|
||||
function fixSandboxFiles(files: Record<string, { code: string }>, entry = '/src/index.js') {
|
||||
if (files['/tango.config.json']) {
|
||||
const tangConfigJsonStr = files['/tango.config.json'].code;
|
||||
const tangConfigJson = JSON.parse(tangConfigJsonStr);
|
||||
@@ -388,6 +393,7 @@ function normalizeFiles(files: object, entry = '/src/index.js') {
|
||||
code: JSON.stringify(tangConfigJson.sandbox, null, 2),
|
||||
};
|
||||
}
|
||||
|
||||
if (!files['/index.html']) {
|
||||
files['/index.html'] = {
|
||||
code: `
|
||||
@@ -407,28 +413,30 @@ function normalizeFiles(files: object, entry = '/src/index.js') {
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
function mergeTangoConfigJson(code: string, isPreview: boolean, config?: { [x: string]: any }) {
|
||||
interface IMergeTangoConfigJsonConfig {
|
||||
isPreview?: boolean;
|
||||
injectScript?: string;
|
||||
formatter?: (json: object) => object;
|
||||
}
|
||||
|
||||
function mergeTangoConfigJson(
|
||||
code: string,
|
||||
{ isPreview, injectScript, formatter }: IMergeTangoConfigJsonConfig = {},
|
||||
) {
|
||||
let json;
|
||||
try {
|
||||
json = JSON.parse(code);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
logger.error('Json parse failed!', err);
|
||||
return code;
|
||||
}
|
||||
|
||||
const ox = getValue(json, 'dataSource.ox');
|
||||
const userJs = getValue(json, 'sandbox.evaluateJavaScript') || '';
|
||||
let mergedUserJs = userJs;
|
||||
const { injectScript } = config || {};
|
||||
|
||||
if (ox) {
|
||||
// TIP: 自动拼装 __tango_ox__ 注入到沙箱中
|
||||
mergedUserJs = `window.__tango_ox__=${JSON.stringify(ox)};${mergedUserJs}`;
|
||||
}
|
||||
if (injectScript) {
|
||||
mergedUserJs = `${mergedUserJs};${injectScript}`;
|
||||
}
|
||||
@@ -437,15 +445,6 @@ function mergeTangoConfigJson(code: string, isPreview: boolean, config?: { [x: s
|
||||
setValue(json, 'sandbox.evaluateJavaScript', mergedUserJs);
|
||||
}
|
||||
|
||||
const i18n = getValue(json, 'i18n');
|
||||
if (i18n) {
|
||||
// TIP: 合并 i18n 配置到沙箱配置中
|
||||
setValue(json, 'sandbox.i18n', {
|
||||
id: i18n.appId,
|
||||
preModule: i18n.preModule,
|
||||
});
|
||||
}
|
||||
|
||||
// 合并 packages 内的信息至 sandbox
|
||||
const packages = getValue(json, 'packages');
|
||||
const externals = getValue(json, 'sandbox.externals') || {};
|
||||
@@ -492,6 +491,8 @@ function mergeTangoConfigJson(code: string, isPreview: boolean, config?: { [x: s
|
||||
setValue(json, 'sandbox.externals', externals);
|
||||
setValue(json, 'sandbox.externalResources', [...new Set(externalResources)]);
|
||||
|
||||
json = formatter?.(json);
|
||||
|
||||
return JSON.stringify(json);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
clone,
|
||||
ComponentPropType,
|
||||
isVariableString,
|
||||
logger,
|
||||
SetterOnChangeDetailType,
|
||||
useBoolean,
|
||||
} from '@music163/tango-helpers';
|
||||
@@ -241,7 +242,7 @@ export function SettingFormItem(props: FormItemProps) {
|
||||
*/
|
||||
export function register(options: FormItemCreateOptionsType) {
|
||||
if (SETTERS_DICT[options.name]) {
|
||||
throw Error(`Duplicate setter name: <${options.name}>`);
|
||||
logger.log(`Internal setter override: <${options.name}>`);
|
||||
}
|
||||
SETTERS_DICT[options.name] = createFormItem(options);
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import React from 'react';
|
||||
import { css } from 'styled-components';
|
||||
import { Box } from 'coral-system';
|
||||
import { Select } from 'antd';
|
||||
// import { IconPro } from '@ant-design/icons';
|
||||
import { FormItemComponentProps } from '../form-item';
|
||||
|
||||
const acStyle = css`
|
||||
.ant-select {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
const ALL_ICON_NAMES: string[] = [];
|
||||
|
||||
/**
|
||||
* Icon 组件设置器
|
||||
* onChange 返回 `{<Icon type="iconName" />}`
|
||||
*/
|
||||
export function IconSetter({ onChange, ...rest }: FormItemComponentProps) {
|
||||
const handleChange = (val: string) => {
|
||||
onChange &&
|
||||
onChange(val, {
|
||||
relatedImports: ['Icon'],
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Box css={acStyle}>
|
||||
<Select showSearch allowClear placeholder="请选择图标" onChange={handleChange} {...rest}>
|
||||
{ALL_ICON_NAMES.map((icon) => {
|
||||
const key = `{<Icon type="${icon}" />}`;
|
||||
return (
|
||||
<Select.Option key={key}>
|
||||
<Box display="inline-block" mr="m">
|
||||
{/* <IconPro type={icon as any} size="14px" /> */}
|
||||
</Box>
|
||||
{icon}
|
||||
</Select.Option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Empty, Modal, Pagination, Radio } from 'antd';
|
||||
import { Box, css, Grid, GridItem, Text } from 'coral-system';
|
||||
import { isFunction, useBoolean } from '@music163/tango-helpers';
|
||||
import { FormItemComponentProps } from '../form-item';
|
||||
import { useFormVariable } from '../context';
|
||||
|
||||
const wrapperStyle = css`
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 150px;
|
||||
}
|
||||
`;
|
||||
|
||||
export function ImageSetter({ value, onChange }: FormItemComponentProps) {
|
||||
const [visible, { on, off }] = useBoolean();
|
||||
const label = value ? '更新图片' : '上传图片';
|
||||
const { remoteServices } = useFormVariable();
|
||||
return (
|
||||
<Box css={wrapperStyle}>
|
||||
<Box border="solid" borderColor="line.normal" textAlign="center" mb="m">
|
||||
{value ? <img src={value} alt="preview image" /> : null}
|
||||
</Box>
|
||||
<Button block onClick={on}>
|
||||
{label}
|
||||
</Button>
|
||||
<Modal title={label} visible={visible} onCancel={off} width="60%">
|
||||
<ImageCenter
|
||||
imageService={remoteServices?.ImageService}
|
||||
onSelect={(url) => {
|
||||
onChange(url);
|
||||
off();
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const categories = [
|
||||
{ label: '我的素材', value: 'listMy' },
|
||||
{ label: '我的收藏', value: 'listFav' },
|
||||
{ label: '公共素材', value: 'listPub' },
|
||||
];
|
||||
|
||||
interface ImageCenterProps {
|
||||
onSelect?: ImageListProps['onSelect'];
|
||||
imageService?: Record<string, (...args: any[]) => Promise<any>>;
|
||||
}
|
||||
|
||||
const pageSize = 20;
|
||||
|
||||
function ImageCenter({ imageService, onSelect }: ImageCenterProps) {
|
||||
const [cate, setCate] = useState('listMy');
|
||||
const [page, setPage] = useState(1);
|
||||
const [data, setData] = useState<any>({});
|
||||
useEffect(() => {
|
||||
imageService?.[cate]?.({ limit: pageSize, offset: (page - 1) * pageSize }).then((res: any) => {
|
||||
setData(res || {});
|
||||
});
|
||||
}, [imageService, cate, page]);
|
||||
return (
|
||||
<Box>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Radio.Group
|
||||
value={cate}
|
||||
onChange={(e) => {
|
||||
setCate(e.target.value);
|
||||
}}
|
||||
options={categories}
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
href="https://music-fn.hz.netease.com/s/music-deer-web/user/image/myupload"
|
||||
target="_blank"
|
||||
>
|
||||
我要上传
|
||||
</Button>
|
||||
</Box>
|
||||
{data.list?.length ? (
|
||||
<ImageList dataSource={data.list} onSelect={onSelect} />
|
||||
) : (
|
||||
<Empty description="没有数据或数据服务请求出错" />
|
||||
)}
|
||||
{data.count > pageSize ? (
|
||||
<Pagination current={page} total={data.count} pageSize={pageSize} onChange={setPage} />
|
||||
) : null}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const imageListWrapper = css`
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 120px;
|
||||
}
|
||||
|
||||
button {
|
||||
border-color: transparent;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--tango-colors-brand);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface ImageListProps {
|
||||
dataSource?: any[];
|
||||
onSelect?: (url: string) => void;
|
||||
}
|
||||
|
||||
function ImageList({ dataSource = [], onSelect }: ImageListProps) {
|
||||
return (
|
||||
<Grid my="m" columns={4} gap="12px" height="400px" overflow="auto" css={imageListWrapper}>
|
||||
{dataSource.map((item) => (
|
||||
<GridItem
|
||||
key={item.id}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
bg="gray.20"
|
||||
>
|
||||
<Box
|
||||
as="button"
|
||||
display="inline-flex"
|
||||
flexDirection="column"
|
||||
alignItems="center"
|
||||
gap="m"
|
||||
bg="transparent"
|
||||
onClick={() => {
|
||||
isFunction(onSelect) && onSelect(item.url);
|
||||
}}
|
||||
>
|
||||
<img src={item.url} />
|
||||
<Text fontSize="12px" color="text.note">
|
||||
{item.name}
|
||||
</Text>
|
||||
</Box>
|
||||
</GridItem>
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import { PickerSetter } from './picker-setter';
|
||||
import { ChoiceSetter } from './choice-setter';
|
||||
import { FormItemCreateOptionsType } from '../form-item';
|
||||
import { ListSetter } from './list-setter';
|
||||
import { IconSetter } from './icon-setter';
|
||||
import { ColumnSetter } from './column-setter';
|
||||
import { NumberSetter, SliderSetter } from './number-setter';
|
||||
import { ExpressionSetter, expressionValueValidate, jsonValueValidate } from './expression-setter';
|
||||
@@ -25,16 +24,8 @@ import {
|
||||
FlexDirectionSetter,
|
||||
} from './style-setter';
|
||||
import { CssSetter } from './css-setter';
|
||||
import { ListenerSetter } from './listener-setter';
|
||||
import { RuleSetter } from './rule-setter';
|
||||
import { ModelSetter } from './model-setter';
|
||||
import {
|
||||
RenderSetter,
|
||||
TableCellSetter,
|
||||
TableExpandableSetter,
|
||||
XToolbarSetter,
|
||||
} from './render-props-setter';
|
||||
import { ImageSetter } from './image-setter';
|
||||
import { RenderSetter, TableCellSetter, TableExpandableSetter } from './render-props-setter';
|
||||
import { RouterSetter } from './router-setter';
|
||||
import { BoolSetter } from './bool-setter';
|
||||
|
||||
@@ -122,10 +113,6 @@ export const INTERNAL_SETTERS: FormItemCreateOptionsType[] = [
|
||||
name: 'choiceSetter',
|
||||
component: ChoiceSetter,
|
||||
},
|
||||
{
|
||||
name: 'iconSetter',
|
||||
component: IconSetter,
|
||||
},
|
||||
{
|
||||
name: 'optionSetter',
|
||||
component: OptionSetter,
|
||||
@@ -181,14 +168,6 @@ export const INTERNAL_SETTERS: FormItemCreateOptionsType[] = [
|
||||
name: 'columnSetter',
|
||||
component: ColumnSetter,
|
||||
},
|
||||
{
|
||||
name: 'listenerSetter',
|
||||
component: ListenerSetter,
|
||||
},
|
||||
{
|
||||
name: 'ruleSetter',
|
||||
component: RuleSetter,
|
||||
},
|
||||
{
|
||||
name: 'renderPropsSetter',
|
||||
component: RenderSetter,
|
||||
@@ -201,14 +180,6 @@ export const INTERNAL_SETTERS: FormItemCreateOptionsType[] = [
|
||||
name: 'tableExpandableSetter',
|
||||
component: TableExpandableSetter,
|
||||
},
|
||||
{
|
||||
name: 'xtoolbarSetter',
|
||||
component: XToolbarSetter,
|
||||
},
|
||||
{
|
||||
name: 'imageSetter',
|
||||
component: ImageSetter,
|
||||
},
|
||||
{
|
||||
name: 'routerSetter',
|
||||
component: RouterSetter,
|
||||
|
||||
@@ -1,203 +0,0 @@
|
||||
import React, { FunctionComponent, useState } from 'react';
|
||||
import { Box } from 'coral-system';
|
||||
import { Select, Input, Tabs, Button } from 'antd';
|
||||
import { FieldStringOutlined } from '@ant-design/icons';
|
||||
import { SingleMonacoEditor } from '@music163/tango-ui';
|
||||
import { FormItemComponentProps } from '../form-item';
|
||||
import { useFormVariable } from '../context';
|
||||
import { ListSetter } from './list-setter';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
// TODO: 需考虑 option选项联动 & 组件属性联动等面板
|
||||
const CustomComponent: FunctionComponent<any> = ({ value = {}, onChange }) => {
|
||||
// FIXME: 这里之前判断逻辑有问题,导致每次 value 变更都执行 parseExpression
|
||||
const [tabIndex, setTabIndex] = useState(typeof value === 'string' ? '2' : '1');
|
||||
|
||||
const triggerChange = (changedValue: any) => {
|
||||
onChange({
|
||||
...value,
|
||||
...changedValue,
|
||||
});
|
||||
};
|
||||
|
||||
const customListenerDefaultValue = `/* {(valid, field, form) => {
|
||||
form.fieldMap.input1.setValue("联动改变值");
|
||||
}}*/`;
|
||||
return (
|
||||
<Tabs type="card" activeKey={tabIndex} onChange={setTabIndex}>
|
||||
<TabPane tab="常用配置" key="1">
|
||||
<Box display="flex" flexDirection="column" gap="20px">
|
||||
<Select
|
||||
value={value?.status}
|
||||
onChange={(v) => {
|
||||
triggerChange({
|
||||
status: v,
|
||||
});
|
||||
}}
|
||||
placeholder="UI状态"
|
||||
options={[
|
||||
{
|
||||
label: '编辑',
|
||||
value: 'edit',
|
||||
},
|
||||
{
|
||||
label: '禁用',
|
||||
value: 'disabled',
|
||||
},
|
||||
{
|
||||
label: '隐藏',
|
||||
value: 'hidden',
|
||||
},
|
||||
{
|
||||
label: '预览',
|
||||
value: 'preview',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
value={value?.ui?.required}
|
||||
onChange={(v) => {
|
||||
triggerChange({
|
||||
ui: {
|
||||
...value?.ui,
|
||||
required: v,
|
||||
},
|
||||
});
|
||||
}}
|
||||
placeholder="必填"
|
||||
options={[
|
||||
{
|
||||
label: '必填',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: '不必填',
|
||||
value: false,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Input
|
||||
placeholder="设置label标题"
|
||||
value={value?.ui?.label}
|
||||
onChange={(e) => {
|
||||
triggerChange({
|
||||
ui: {
|
||||
...value?.ui,
|
||||
label: e.target.value,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
placeholder="设置字段值"
|
||||
value={value?.value}
|
||||
onChange={(e) => {
|
||||
triggerChange({
|
||||
value: e.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</TabPane>
|
||||
<TabPane tab="自定义配置" key="2">
|
||||
<SingleMonacoEditor
|
||||
defaultValue={customListenerDefaultValue}
|
||||
value={
|
||||
Object.prototype.toString.call(value) === '[object Object]'
|
||||
? customListenerDefaultValue
|
||||
: value
|
||||
}
|
||||
onChange={(v) => onChange(v.trim())}
|
||||
hasBorder
|
||||
height="150px"
|
||||
options={{
|
||||
lineNumbers: 'off',
|
||||
fontSize: 12,
|
||||
wordWrap: 'on',
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
const renderSetterItem = (item: any) => {
|
||||
return (
|
||||
<>
|
||||
<FieldStringOutlined />
|
||||
<Box display="inline-block" ml="m">
|
||||
{item.title}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* XFormItem响应器
|
||||
*/
|
||||
export const ListenerSetter = (props: FormItemComponentProps<any[]>) => {
|
||||
const { formFieldsOptions } = useFormVariable();
|
||||
|
||||
return (
|
||||
<ListSetter
|
||||
getListItemKey={(item) => item.key || item.dataIndex}
|
||||
addBtnText="配置响应器"
|
||||
listItemFormFields={[
|
||||
{
|
||||
label: '来源字段',
|
||||
name: 'watch',
|
||||
required: true,
|
||||
component: <Select mode="tags" options={formFieldsOptions} />,
|
||||
extra: <span>请选择依赖的字段。</span>,
|
||||
},
|
||||
{
|
||||
label: '触发条件',
|
||||
name: 'condition',
|
||||
required: true,
|
||||
component: (
|
||||
<SingleMonacoEditor
|
||||
hasBorder
|
||||
height="50px"
|
||||
defaultValue="// input1.value === '123'"
|
||||
options={{
|
||||
lineNumbers: 'off',
|
||||
fontSize: 12,
|
||||
wordWrap: 'on',
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
),
|
||||
extra: <span>请输入一个布尔表达式,满足条件时即触发响应。</span>,
|
||||
},
|
||||
{
|
||||
label: '响应行为',
|
||||
name: 'set',
|
||||
required: true,
|
||||
component: <CustomComponent />,
|
||||
width: '400px',
|
||||
extra: (
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() =>
|
||||
window.open(
|
||||
'https://music-cms.hz.netease.com/xform-docs/docs/tutorial-basics/listeners#set-%E4%B8%BA-function',
|
||||
)
|
||||
}
|
||||
>
|
||||
文档地址
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
]}
|
||||
newItemDefaultValues={[]}
|
||||
renderItem={renderSetterItem}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -97,16 +97,6 @@ const tableExpandableOptions: RenderSetterProps['options'] = [
|
||||
{ label: '取消可展开行', value: '' },
|
||||
];
|
||||
|
||||
const xtoolbarOptions: RenderSetterProps['options'] = [
|
||||
{
|
||||
label: '配置左侧区域',
|
||||
value: 'Box',
|
||||
render: getRender('<Box display="flex"></Box>'),
|
||||
relatedImports: ['Box'],
|
||||
},
|
||||
{ label: '取消左侧区域', value: undefined },
|
||||
];
|
||||
|
||||
export function TableCellSetter(props: FormItemComponentProps) {
|
||||
return <RenderSetter options={tableCellOptions} {...props} />;
|
||||
}
|
||||
@@ -114,7 +104,3 @@ export function TableCellSetter(props: FormItemComponentProps) {
|
||||
export function TableExpandableSetter(props: FormItemComponentProps) {
|
||||
return <RenderSetter options={tableExpandableOptions} text="配置表格可展开行" {...props} />;
|
||||
}
|
||||
|
||||
export function XToolbarSetter(props: FormItemComponentProps) {
|
||||
return <RenderSetter options={xtoolbarOptions} text="配置工具栏左侧区域" {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box } from 'coral-system';
|
||||
import { AutoComplete, Button, Select, Switch } from 'antd';
|
||||
import { FieldStringOutlined } from '@ant-design/icons';
|
||||
import { SingleMonacoEditor } from '@music163/tango-ui';
|
||||
import { FormItemComponentProps } from '../form-item';
|
||||
import { ListSetter, NewOptionFormFieldType } from './list-setter';
|
||||
|
||||
// 常用正则
|
||||
const enumPattern = [
|
||||
{
|
||||
label: '纯数字',
|
||||
value: '^[0-9]*$',
|
||||
},
|
||||
{
|
||||
label: '纯汉字',
|
||||
value: '^[\u4e00-\u9fa5]{0,}$',
|
||||
},
|
||||
{
|
||||
label: '手机号',
|
||||
value: '^[1][3-8][0-9]{9}$',
|
||||
},
|
||||
{
|
||||
label: 'IP地址',
|
||||
value: 'd+.d+.d+.d+',
|
||||
},
|
||||
{
|
||||
label: '身份证号',
|
||||
value: '^d{15}|d{18}$',
|
||||
},
|
||||
{
|
||||
label: '邮政编码',
|
||||
value: '[1-9]d{5}(?!d)',
|
||||
},
|
||||
{
|
||||
label: '图片URL',
|
||||
value: '(https?:[^:<>"]*/)([^:<>"]*)(.((png!thumbnail)|(png)|(jpg)|(webp)|(gif)))',
|
||||
},
|
||||
];
|
||||
|
||||
// async-validator type
|
||||
// https://github.com/yiminghe/async-validator#type
|
||||
const type = [
|
||||
'string', // // Must be of type string. This is the default type.
|
||||
// 'number', Input TextArea出来的value都是string / InputNumber是number。不在async-validator做数字类型的校验
|
||||
'boolean', // Must be of type boolean.
|
||||
'regexp', // Must be an instance of RegExp or a string that does not generate an exception when creating a new RegExp.
|
||||
'integer', // Must be of type number and an integer.
|
||||
'float', // Must be of type number and a floating point number.
|
||||
'array', // Must be an array as determined by Array.isArray.
|
||||
'object', // Must be of type object and not Array.isArray.
|
||||
'date', // Value // Must be valid as determined by Date
|
||||
'url', // Must be of type url.
|
||||
'email', // Must be of type email.
|
||||
];
|
||||
|
||||
const trigger = ['change', 'submit'];
|
||||
|
||||
const status = ['error', 'warning'];
|
||||
|
||||
/**
|
||||
* 正则输入框
|
||||
*/
|
||||
const PatternInput: React.FunctionComponent<any> = (props) => {
|
||||
return (
|
||||
<>
|
||||
<AutoComplete options={enumPattern} onSelect={props.onChange} {...props} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 必填选择
|
||||
*/
|
||||
const RequiredInput: React.FunctionComponent<any> = (props) => {
|
||||
return (
|
||||
<>
|
||||
<Switch onChange={props.onChange} checked={props.value} {...props} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const optionFormFields: NewOptionFormFieldType[] = [
|
||||
{
|
||||
label: '字段类型',
|
||||
name: 'type',
|
||||
required: true,
|
||||
component: <Select options={type.map((t) => ({ label: t, value: t }))} />,
|
||||
},
|
||||
{
|
||||
label: '是否必填',
|
||||
name: 'required',
|
||||
component: <RequiredInput />,
|
||||
},
|
||||
{
|
||||
label: '触发方式',
|
||||
name: 'trigger',
|
||||
component: <Select options={trigger.map((t) => ({ label: t, value: t }))} />,
|
||||
},
|
||||
{
|
||||
label: '触发状态',
|
||||
name: 'status',
|
||||
component: <Select options={status.map((t) => ({ label: t, value: t }))} />,
|
||||
},
|
||||
{
|
||||
label: '错误提示语',
|
||||
name: 'message',
|
||||
extra: '自定义错误提示',
|
||||
},
|
||||
{
|
||||
label: '正则表达式',
|
||||
name: 'pattern',
|
||||
extra: '复杂场景,可以自定义正则。',
|
||||
component: <PatternInput placeholder="例如: ^[0-9]*$" />,
|
||||
},
|
||||
{
|
||||
label: '自定义校验',
|
||||
name: 'validator',
|
||||
width: '400px',
|
||||
extra: (
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() => window.open('https://github.com/yiminghe/async-validator#validator')}
|
||||
>
|
||||
文档地址
|
||||
</Button>
|
||||
),
|
||||
component: (
|
||||
<SingleMonacoEditor
|
||||
hasBorder
|
||||
height="108px"
|
||||
defaultValue="/** {(rule, value, callback) => { return value === 'test';}}*/"
|
||||
options={{
|
||||
lineNumbers: 'off',
|
||||
fontSize: 12,
|
||||
wordWrap: 'on',
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const renderSetterItem = (item: any) => {
|
||||
return (
|
||||
<>
|
||||
<FieldStringOutlined />
|
||||
<Box display="inline-block" ml="m">
|
||||
{item.title}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 校验属性配置
|
||||
*/
|
||||
export function RuleSetter(props: FormItemComponentProps<any[]>) {
|
||||
return (
|
||||
<ListSetter
|
||||
getListItemKey={(item) => item.key || item.dataIndex}
|
||||
addBtnText="配置校验器"
|
||||
listItemFormFields={optionFormFields}
|
||||
newItemDefaultValues={{
|
||||
type: 'string',
|
||||
trigger: 'change',
|
||||
status: 'error',
|
||||
}}
|
||||
renderItem={renderSetterItem}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user