Files
ONE-OS/axhub-make/src/components/ref-button/index.tsx
王冕 a27e3b8e43 feat: sync full workspace including web modules, docs, and configurations to Gitea
Optimized the root .gitignore to exclude virtual environments, node modules,
and temp folders to ensure clean and lightweight version tracking.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 18:12:25 +08:00

260 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @name 按钮元素
*
* 参考资料:
* - /rules/development-guide.md
* - /rules/axure-api-guide.md
* - /docs/设计规范.UIGuidelines.md
*
*/
import './style.css';
import React, { useState, useCallback, useImperativeHandle, forwardRef } from 'react';
import type {
KeyDesc,
DataDesc,
ConfigItem,
Action,
EventItem,
AxureProps,
AxureHandle
} from '../../common/axure-types';
// 图标组件(内部使用)
const IconPlus = () => (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
);
const IconRefresh = () => (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M23 4v6h-6"></path>
<path d="M1 20v-6h6"></path>
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
</svg>
);
const EVENT_LIST: EventItem[] = [
{ name: 'onClick', desc: '点击按钮时触发' },
{ name: 'onCountChange', desc: '计数器改变时触发' }
];
const ACTION_LIST: Action[] = [
{ name: 'increment', desc: '计数器加1' },
{ name: 'reset', desc: '重置计数器' },
{ name: 'setMessage', desc: '设置消息内容' }
];
const VAR_LIST: KeyDesc[] = [
{ name: 'count', desc: '当前计数值' },
{ name: 'message', desc: '消息内容' }
];
// type 可以是input, inputNumber, colorPicker, checkbox, select 等
const CONFIG_LIST: ConfigItem[] = [
{
type: 'input',
attributeId: 'title',
displayName: '元素标题',
info: '元素顶部显示的标题文本',
initialValue: 'React 示例'
},
{
type: 'input',
attributeId: 'buttonText',
displayName: '按钮文本',
info: '主按钮显示的文本内容',
initialValue: '点击我'
},
{
type: 'colorPicker',
attributeId: 'primaryColor',
displayName: '主色调',
info: '元素的主题颜色',
initialValue: '#1890ff'
},
{
type: 'inputNumber',
attributeId: 'initialCount',
displayName: '初始计数值',
info: '计数器的初始值',
initialValue: 0,
min: 0
},
{
type: 'input',
attributeId: 'message',
displayName: '消息内容',
info: '元素显示的消息文本',
initialValue: '这是一个 React 元素示例'
}
];
const DATA_LIST: DataDesc[] = [
{
name: 'data1',
desc: '基础数据列表',
keys: [
{ name: 'name', desc: '名称' },
{ name: 'value', desc: '值' }
]
}
];
const Component = forwardRef<AxureHandle, AxureProps>(function AxhubButton(innerProps, ref) {
// 安全解构 props 并提供默认值,避免访问 undefined 属性
const dataSource = innerProps && innerProps.data ? innerProps.data : {};
const configSource = innerProps && innerProps.config ? innerProps.config : {};
const onEventHandler = typeof innerProps.onEvent === 'function' ? innerProps.onEvent : function () { return undefined; };
// 使用类型检查避免使用 || 运算符(会误判 0、false 等值)
const initialCount = typeof configSource.initialCount === 'number' ? configSource.initialCount : 0;
const defaultMessage = typeof configSource.message === 'string' && configSource.message ? configSource.message : '这是一个 React 元素示例';
const titleText = typeof configSource.title === 'string' && configSource.title ? configSource.title : 'React 元素示例';
const buttonText = typeof configSource.buttonText === 'string' && configSource.buttonText ? configSource.buttonText : '点击我';
// 避免使用 ES6 解构,使用数组索引访问 state 和 setter
const countState = useState<number>(initialCount);
const count = countState[0];
const setCount = countState[1];
const messageState = useState<string>(defaultMessage);
const message = messageState[0];
const setMessage = messageState[1];
// 使用 useCallback 优化性能,包含错误处理
const emitEvent = useCallback(function (eventName: string, payload?: any) {
try {
onEventHandler(eventName, payload);
} catch (error) {
console.warn('onEvent 调用失败:', error);
}
}, [onEventHandler]);
// 使用 useCallback 包装所有回调函数,避免在 JSX 中直接定义函数
const incrementCount = useCallback(function () {
setCount(function (prev) {
const newValue = prev + 1;
emitEvent('onCountChange', { count: newValue, action: 'increment' });
return newValue;
});
}, [emitEvent]);
const resetCount = useCallback(function () {
setCount(initialCount);
emitEvent('onCountChange', { count: initialCount, action: 'reset' });
}, [initialCount, emitEvent]);
const updateMessage = useCallback(function (params?: any) {
if (params && typeof params.message === 'string' && params.message.length > 0) {
setMessage(params.message);
}
}, []);
const handlePrimaryClick = useCallback(function () {
emitEvent('onClick', { count });
incrementCount();
}, [count, emitEvent, incrementCount]);
const handleResetClick = useCallback(function () {
resetCount();
}, [resetCount]);
// 使用 switch 语句处理不同的动作类型
const fireActionHandler = useCallback(function (name: string, params?: any) {
switch (name) {
case 'increment':
incrementCount();
break;
case 'reset':
resetCount();
break;
case 'setMessage':
updateMessage(params);
break;
default:
console.warn('未知的动作类型:', name);
}
}, [incrementCount, resetCount, updateMessage]);
useImperativeHandle(ref, function () {
return {
getVar: function (name: string) {
const vars: Record<string, any> = { count, message };
return vars[name];
},
fireAction: fireActionHandler,
eventList: EVENT_LIST,
actionList: ACTION_LIST,
varList: VAR_LIST,
configList: CONFIG_LIST,
dataList: DATA_LIST
};
}, [count, message, fireActionHandler]);
// 从配置和数据源中提取需要的值,提供默认值
const primaryColor = typeof configSource.primaryColor === 'string' && configSource.primaryColor ? configSource.primaryColor : '#1890ff';
const dataListSource = Array.isArray(dataSource.data1) ? dataSource.data1 : [];
// 使用语义化的类名,添加元素前缀避免冲突
// 避免在 JSX 中直接定义函数,使用预定义的 useCallback 函数
return (
<div className="axhub-button-container">
<div className="axhub-button-header">
<h2 className="axhub-button-title">{titleText}</h2>
</div>
<div className="axhub-button-controls">
<button
type="button"
className="axhub-button-primary"
style={{ backgroundColor: primaryColor }}
onClick={handlePrimaryClick}
>
<IconPlus />
<span>{buttonText}</span>
<span className="axhub-button-count-badge">{count}</span>
</button>
<button
type="button"
className="axhub-button-secondary"
onClick={handleResetClick}
>
<IconRefresh />
<span></span>
</button>
</div>
<div className="axhub-button-content">
<div className="axhub-button-message">
<div className="axhub-button-label"></div>
<div className="axhub-button-value">{message}</div>
</div>
{dataListSource.length > 0 && (
<div className="axhub-button-data-container">
<div className="axhub-button-label"></div>
<div className="axhub-button-data-list">
{dataListSource.map(function (item: any, index: number) {
return (
<div key={index} className="axhub-button-data-item">
<span className="axhub-button-data-name">{item.name}</span>
<span className="axhub-button-data-val">{item.value}</span>
</div>
);
})}
</div>
</div>
)}
</div>
</div>
);
});
// 这是本项目平台集成的必要条件
export default Component;