Skip to content

Commit c4e1ed6

Browse files
authored
fix: add prop value template & fix components popover (#201)
* fix: clear value in CodeSetter * fix: update variable path in codesetter * fix: add prop value template * fix: update recommendedList for ComponentsPopover * fix: update icon size in line mode for ComponentsPanel
1 parent 179de0c commit c4e1ed6

File tree

12 files changed

+160
-79
lines changed

12 files changed

+160
-79
lines changed

apps/playground/src/helpers/mock-files.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ import { definePage } from "@music163/tango-boot";
153153
import {
154154
Page,
155155
Section,
156+
Box,
156157
Button,
157158
Input,
158159
FormilyForm,
@@ -167,7 +168,9 @@ class App extends React.Component {
167168
render() {
168169
return (
169170
<Page title={tango.stores.app.title} subTitle={<><Button>hello</Button></>}>
170-
<Section tid="section0" />
171+
<Section tid="section0">
172+
<Box></Box>
173+
</Section>
171174
<Section tid="section1" title="Section Title">
172175
your input: <Input tid="input1" defaultValue="hello" />
173176
copy input: <Input value={tango.page.input1?.value} />

apps/playground/src/helpers/prototypes.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,13 @@ const prototypes: Dict<IComponentPrototype> = {
200200
title: 'd',
201201
setter: 'textSetter',
202202
},
203+
{
204+
name: 'onClick',
205+
title: '点击事件',
206+
setter: 'eventSetter',
207+
template: '(e) => {\n {{content}}\n}',
208+
tip: '回调参数说明:e 为事件对象',
209+
},
203210
],
204211
},
205212
Columns: {

apps/storybook/.storybook/preview.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { SystemProvider } from 'coral-system';
33
import 'antd/dist/antd.css';
44

55
export const parameters = {
6-
actions: { argTypesRegex: '^on[A-Z].*' },
6+
actions: { argTypesRegex: '^on.*' },
77
controls: {
88
matchers: {
99
color: /(background|color)$/i,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React from 'react';
2+
import { ActionSelect } from '@music163/tango-ui';
3+
4+
export default {
5+
title: 'UI/ActionSelect',
6+
component: ActionSelect,
7+
};
8+
9+
const options = [
10+
{ label: 'action1', value: 'action1' },
11+
{ label: 'action2', value: 'action2' },
12+
{ label: 'action3', value: 'action3' },
13+
];
14+
15+
export const Basic = {
16+
args: {
17+
defaultText: '选择动作',
18+
options,
19+
onSelect: console.log,
20+
},
21+
};
22+
23+
export const showInput = {
24+
args: {
25+
text: '选择动作',
26+
options,
27+
showInput: true,
28+
},
29+
};

packages/designer/src/components/components-popover.tsx

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,17 @@ export const ComponentsPopover = observer(
3434
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
3535
workspace.componentPrototypes.get(selectedNode?.name) ?? ({} as IComponentPrototype);
3636

37-
// 推荐使用的子组件
38-
const insertedList = useMemo(
39-
() =>
40-
Array.isArray(prototype?.childrenName)
41-
? prototype?.childrenName
42-
: [prototype?.childrenName].filter(Boolean),
43-
[prototype?.childrenName],
44-
);
45-
46-
// 推荐使用的代码片段
47-
const siblingList = useMemo(() => prototype?.siblingNames ?? [], [prototype.siblingNames]);
37+
const recommendedList = useMemo(() => {
38+
if (type === 'inner') {
39+
return prototype?.childrenName
40+
? Array.isArray(prototype?.childrenName)
41+
? prototype.childrenName
42+
: [prototype.childrenName]
43+
: [];
44+
}
45+
// 默认推荐使用相同类型的组件作为兄弟节点
46+
return prototype.siblingNames || [prototype.name];
47+
}, [prototype.childrenName, prototype.siblingNames, prototype.name, type]);
4848

4949
const tipsTextMap = useMemo(
5050
() => ({
@@ -82,22 +82,15 @@ export const ComponentsPopover = observer(
8282
const menuList = JSON.parse(JSON.stringify(designer.menuData));
8383

8484
const commonList = menuList['common'] ?? [];
85-
if (commonList?.length && siblingList?.length) {
86-
commonList.unshift({
87-
title: '代码片段',
88-
items: siblingList,
89-
});
90-
}
91-
92-
if (commonList?.length && insertedList?.length) {
85+
if (commonList?.length && recommendedList.length) {
9386
commonList.unshift({
9487
title: '推荐使用',
95-
items: insertedList,
88+
items: recommendedList,
9689
});
9790
}
9891

9992
return menuList;
100-
}, [insertedList, siblingList, designer.menuData]);
93+
}, [recommendedList, designer.menuData]);
10194

10295
const innerTypeProps =
10396
// 手动触发 适用于 点击添加组件

packages/designer/src/setters/event-setter.tsx

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import React, { useCallback, useMemo, useState } from 'react';
22
import { css, Box, Text } from 'coral-system';
3-
import { AutoComplete } from 'antd';
3+
import { AutoComplete, Input } from 'antd';
44
import { ActionSelect } from '@music163/tango-ui';
55
import { FormItemComponentProps } from '@music163/tango-setting-form';
66
import { useWorkspace, useWorkspaceData } from '@music163/tango-context';
77
import { Dict, wrapCode } from '@music163/tango-helpers';
8-
import { ExpressionPopover } from './expression-setter';
8+
import { ExpressionPopover, getCallbackValue } from './expression-setter';
99
import { value2code } from '@music163/tango-core';
1010

1111
enum EventAction {
@@ -31,7 +31,7 @@ export type EventSetterProps = FormItemComponentProps<string>;
3131
* 事件监听函数绑定器
3232
*/
3333
export function EventSetter(props: EventSetterProps) {
34-
const { value, onChange, modalTitle } = props;
34+
const { value, onChange, modalTitle, modalTip, template } = props;
3535
const [type, setType] = useState<EventAction>(); // 事件类型
3636
const [temp, setTemp] = useState(''); // 二级暂存值
3737
const { actionVariables, routeOptions } = useWorkspaceData();
@@ -59,7 +59,9 @@ export function EventSetter(props: EventSetterProps) {
5959
label: (
6060
<ExpressionPopover
6161
title={modalTitle}
62+
subTitle={modalTip}
6263
value={value}
64+
template={template}
6365
onOk={(nextValue) => {
6466
handleChange(nextValue);
6567
}}
@@ -74,30 +76,34 @@ export function EventSetter(props: EventSetterProps) {
7476
{ label: '打开弹窗', value: EventAction.OpenModal },
7577
{ label: '关闭弹窗', value: EventAction.CloseModal },
7678
],
77-
[modalTitle, value, actionVariables, handleChange],
79+
[modalTitle, value, actionVariables, template, handleChange],
7880
);
7981

8082
const onAction = (key: string) => {
8183
setType(key as EventAction); // 记录事件类型
8284
setTemp(''); // 重置二级选项值
8385

84-
switch (key) {
85-
case EventAction.ConsoleLog:
86-
handleChange('(...args) => console.log(...args)');
87-
break;
88-
case EventAction.NoAction:
89-
handleChange(undefined);
90-
break;
91-
default:
92-
break;
86+
if (key === EventAction.NoAction) {
87+
handleChange(undefined);
88+
return;
9389
}
9490
};
9591

96-
const actionText = getActionText(type, temp, code);
97-
9892
return (
9993
<Box css={wrapperStyle}>
100-
<ActionSelect options={options} onSelect={onAction} text={actionText} />
94+
<ActionSelect options={options} onSelect={onAction} defaultText="请选择动作类型" />
95+
{type === EventAction.ConsoleLog && (
96+
<Input
97+
placeholder="输入 Console.log 日志内容"
98+
value={temp}
99+
onChange={(e) => setTemp(e.target.value)}
100+
onBlur={() => {
101+
if (temp) {
102+
handleChange(getExpressionValue(type, temp));
103+
}
104+
}}
105+
/>
106+
)}
101107
{type === EventAction.NavigateTo && (
102108
<AutoComplete
103109
placeholder="选择或输入页面路由"
@@ -142,25 +148,15 @@ export function EventSetter(props: EventSetterProps) {
142148
}
143149

144150
const handlerMap: Dict = {
145-
[EventAction.OpenModal]: 'openModal',
146-
[EventAction.CloseModal]: 'closeModal',
147-
[EventAction.NavigateTo]: 'navigateTo',
151+
[EventAction.ConsoleLog]: 'console.log',
152+
[EventAction.OpenModal]: 'tango.openModal',
153+
[EventAction.CloseModal]: 'tango.closeModal',
154+
[EventAction.NavigateTo]: 'tango.navigateTo',
148155
};
149156

150-
function getActionText(type: EventAction, temp: string, fallbackCode: string) {
151-
let text;
152-
if (handlerMap[type]) {
153-
text = getExpressionValue(type, temp);
154-
} else if (fallbackCode) {
155-
text = fallbackCode;
156-
}
157-
text = text || '请选择';
158-
return text;
159-
}
160-
161157
function getExpressionValue(type: EventAction, value = '') {
162158
const handler = handlerMap[type];
163159
if (handler) {
164-
return `() => tango.${handler}("${value}")`;
160+
return getCallbackValue(`${handler}("${value}");`);
165161
}
166162
}

packages/designer/src/setters/expression-setter.tsx

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useState, useEffect, useCallback } from 'react';
22
import { Box, Text, css } from 'coral-system';
33
import { Dropdown, Button } from 'antd';
44
import { isValidExpressionCode } from '@music163/tango-core';
5-
import { getValue, IVariableTreeNode, noop } from '@music163/tango-helpers';
5+
import { getValue, interpolate, IVariableTreeNode, noop } from '@music163/tango-helpers';
66
import { CloseCircleFilled, MenuOutlined } from '@ant-design/icons';
77
import {
88
Panel,
@@ -34,6 +34,19 @@ export const jsonValueValidate = (value: string) => {
3434
}
3535
};
3636

37+
/**
38+
* 拼装回调函数
39+
* @param value 回调函数体
40+
* @param template 回调函数模板
41+
* @returns
42+
*/
43+
export function getCallbackValue(value: string, template?: string) {
44+
if (!value) {
45+
return;
46+
}
47+
return template ? interpolate(template, { content: value }) : `() => {\n ${value}\n}`;
48+
}
49+
3750
const suffixStyle = css`
3851
display: flex;
3952
align-items: center;
@@ -63,6 +76,7 @@ export function ExpressionSetter(props: ExpressionSetterProps) {
6376
modalTitle,
6477
modalTip,
6578
autoCompleteOptions,
79+
template,
6680
placeholder = '在这里输入代码',
6781
value: valueProp,
6882
status,
@@ -105,7 +119,7 @@ export function ExpressionSetter(props: ExpressionSetterProps) {
105119
<CloseCircleFilled
106120
title="清空"
107121
onClick={() => {
108-
change('');
122+
change(undefined);
109123
}}
110124
/>
111125
)}
@@ -134,6 +148,7 @@ export function ExpressionSetter(props: ExpressionSetterProps) {
134148
subTitle={modalTip}
135149
placeholder={placeholder}
136150
autoCompleteOptions={autoCompleteOptions}
151+
template={template}
137152
newStoreTemplate={newStoreTemplate}
138153
value={inputValue}
139154
expressionType={expressionType}
@@ -166,6 +181,10 @@ export interface ExpressionPopoverProps extends InputCodeProps {
166181
onOk?: (value: string) => void;
167182
dataSource?: IVariableTreeNode[];
168183
autoCompleteOptions?: string[];
184+
/**
185+
* 值的模板,一般用于定义函数模板
186+
*/
187+
template?: string;
169188
/**
170189
* 新建 store 的模板代码
171190
*/
@@ -186,6 +205,7 @@ export function ExpressionPopover({
186205
value,
187206
dataSource,
188207
autoCompleteOptions,
208+
template,
189209
newStoreTemplate = CODE_TEMPLATES.newStoreTemplate,
190210
children,
191211
expressionType,
@@ -245,19 +265,18 @@ export function ExpressionPopover({
245265
autoCompleteOptions={autoCompleteOptions}
246266
/>
247267
{error ? (
248-
<Text color="red" fontSize="12px" as="p">
268+
<Text color="red" fontSize="12px" as="div">
249269
出错了!输入的表达式存在语法错误,请修改后再提交!
250270
</Text>
251271
) : null}
252-
{subTitle && (
253-
<Text fontSize="12px" color="text3" as="p">
254-
{subTitle}
272+
<Box fontSize="12px" color="text2">
273+
<Text display="block">说明:</Text>
274+
{subTitle && <Text display="block">{subTitle}</Text>}
275+
<Text display="block">
276+
你可以在上面的代码输入框里输入常规的 javascript 代码,还可以直接使用 jsx
277+
代码,但需要符合该属性的接受值定义。
255278
</Text>
256-
)}
257-
<Text fontSize="12px" color="text3" as="p">
258-
说明:你可以在上面的代码输入框里输入常规的 javascript 代码,还可以直接使用 jsx
259-
代码,但需要符合该属性的接受值定义。
260-
</Text>
279+
</Box>
261280
</Box>
262281
<Panel
263282
title="从变量列表中选中"
@@ -296,7 +315,11 @@ export function ExpressionPopover({
296315
}
297316
let str;
298317
if (/^(stores|services)\./.test(node.key)) {
299-
str = `tango.${node.key.replaceAll('.', '?.')}`;
318+
// 从匹配到的第二个点开始替换为 ?.,因为第一个点是 stores 或 services
319+
str = `tango.${node.key.replace(/(?<=\..*?)\./g, '?.')}`;
320+
if (node.type === 'function') {
321+
str = getCallbackValue(`${str}();`, template);
322+
}
300323
} else {
301324
str = `${node.key}`;
302325
}

packages/designer/src/sidebar/components-panel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ function MaterialGrid({ data }: MaterialProps) {
365365
<IconFont className="material-icon" type={data.icon || 'icon-placeholder'} />
366366
) : (
367367
<Box className="material-icon">
368-
<img src={icon} alt={data.name} />
368+
<img src={icon} alt={data.name} height="32" />
369369
</Box>
370370
);
371371

packages/helpers/src/helpers/string.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,17 @@ export function parseDndId(str: string): DndIdParsedType {
125125
id: str,
126126
};
127127
}
128+
129+
/**
130+
* 替换模板中的变量
131+
* @example interpolate('hello {{name}}', { name: 'world' }) -> 'hello world'
132+
*
133+
* @param template 带模板变量的字符串
134+
* @param props 变量字典
135+
* @returns 返回替换后的字符串
136+
*/
137+
export function interpolate(template: string, props: Record<string, any>) {
138+
return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
139+
return props[key];
140+
});
141+
}

packages/helpers/src/types/prototype.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ export interface IComponentProp<T = any> {
8888
* 自动补全的提示值,仅对 ExpressionSetter 有效
8989
*/
9090
autoCompleteOptions?: string[];
91+
/**
92+
* value 的模板,一般用于函数类型的属性,便于 setter 用来拼装返回值
93+
* @example "(arg1, arg2, arg3) => { {{content}}}"
94+
*/
95+
template?: string;
9196
/**
9297
* 设置器
9398
*/

0 commit comments

Comments
 (0)