fix: hoist status badge components to module level

Inline badge components captured the styles object on every render,
tripping noComponentHookFactories; move them out and pass styles as a
prop. Also autofix useMathMinMax and unused-variable findings.
This commit is contained in:
向宁 2026-10-25 17:05:46 +08:00
parent 77aac58f22
commit bd3e592598
7 changed files with 127 additions and 109 deletions

View File

@ -73,7 +73,7 @@ const AvatarList: React.FC<AvatarListProps> & {
} = ({ children, size, maxLength = 5, excessItemsStyle, ...other }) => { } = ({ children, size, maxLength = 5, excessItemsStyle, ...other }) => {
const { styles } = useStyles(); const { styles } = useStyles();
const numOfChildren = React.Children.count(children); const numOfChildren = React.Children.count(children);
const numToShow = maxLength >= numOfChildren ? numOfChildren : maxLength; const numToShow = Math.min(maxLength, numOfChildren);
const childrenArray = React.Children.toArray( const childrenArray = React.Children.toArray(
children, children,
) as React.ReactElement<AvatarItemProps>[]; ) as React.ReactElement<AvatarItemProps>[];

View File

@ -348,6 +348,22 @@ const STATUS_TEXT: Record<BtnStatus, string> = {
draft: '草稿', draft: '草稿',
}; };
type PageStyles = ReturnType<typeof useStyles>['styles'];
const StatusBadge: React.FC<{ status: number; styles: PageStyles }> = ({
status,
styles,
}) => {
const s = STATUS_MAP[status] ?? 'draft';
const cls = s === 'published' ? styles.badgeSuccess : styles.badgeWarning;
const dot = s === 'published' ? BF.green500 : BF.amber500;
return (
<span className={`${styles.badge} ${cls}`}>
<span className={styles.badgeDot} style={{ background: dot }} />
{STATUS_TEXT[s]}
</span>
);
};
const fmtTime = (ts?: number) => const fmtTime = (ts?: number) =>
ts ? dayjs(ts).format('YYYY-MM-DD HH:mm') : '—'; ts ? dayjs(ts).format('YYYY-MM-DD HH:mm') : '—';
@ -393,18 +409,6 @@ const ButtonPage: React.FC = () => {
}); });
}, [allButtons, activeTab, search]); }, [allButtons, activeTab, search]);
const StatusBadge: React.FC<{ status: number }> = ({ status }) => {
const s = STATUS_MAP[status] ?? 'draft';
const cls = s === 'published' ? styles.badgeSuccess : styles.badgeWarning;
const dot = s === 'published' ? BF.green500 : BF.amber500;
return (
<span className={`${styles.badge} ${cls}`}>
<span className={styles.badgeDot} style={{ background: dot }} />
{STATUS_TEXT[s]}
</span>
);
};
const [createOpen, setCreateOpen] = useState(false); const [createOpen, setCreateOpen] = useState(false);
const openCreate = () => setCreateOpen(true); const openCreate = () => setCreateOpen(true);
@ -493,7 +497,7 @@ const ButtonPage: React.FC = () => {
) : null} ) : null}
</div> </div>
<div className={styles.cell}> <div className={styles.cell}>
<StatusBadge status={b.status} /> <StatusBadge status={b.status} styles={styles} />
</div> </div>
<div className={`${styles.cell} ${styles.cellFaint}`}> <div className={`${styles.cell} ${styles.cellFaint}`}>
{fmtTime(b.createdAt)} {fmtTime(b.createdAt)}

View File

@ -461,6 +461,32 @@ const STATUS_TEXT: Record<FormStatus, string> = {
draft: '草稿', draft: '草稿',
}; };
type PageStyles = ReturnType<typeof useStyles>['styles'];
const StatusBadge: React.FC<{ status: number; styles: PageStyles }> = ({
status,
styles,
}) => {
const s = STATUS_MAP[status] ?? 'draft';
const cls =
s === 'published'
? styles.badgeSuccess
: s === 'disabled'
? styles.badgeDanger
: styles.badgeWarning;
const dotColor =
s === 'published'
? BF.green500
: s === 'disabled'
? BF.red500
: BF.amber500;
return (
<span className={`${styles.badge} ${cls}`}>
<span className={styles.badgeDot} style={{ background: dotColor }} />
{STATUS_TEXT[s]}
</span>
);
};
const fmtTime = (ts?: number) => const fmtTime = (ts?: number) =>
ts ? dayjs(ts).format('YYYY-MM-DD HH:mm') : '—'; ts ? dayjs(ts).format('YYYY-MM-DD HH:mm') : '—';
@ -519,28 +545,6 @@ const FormList: React.FC = () => {
}); });
}, [allForms, activeTab, search]); }, [allForms, activeTab, search]);
const StatusBadge: React.FC<{ status: number }> = ({ status }) => {
const s = STATUS_MAP[status] ?? 'draft';
const cls =
s === 'published'
? styles.badgeSuccess
: s === 'disabled'
? styles.badgeDanger
: styles.badgeWarning;
const dotColor =
s === 'published'
? BF.green500
: s === 'disabled'
? BF.red500
: BF.amber500;
return (
<span className={`${styles.badge} ${cls}`}>
<span className={styles.badgeDot} style={{ background: dotColor }} />
{STATUS_TEXT[s]}
</span>
);
};
const toggleSelect = (key: string) => { const toggleSelect = (key: string) => {
setSelectedKeys((prev) => { setSelectedKeys((prev) => {
const next = new Set(prev); const next = new Set(prev);
@ -689,7 +693,7 @@ const FormList: React.FC = () => {
<span className={styles.version}> <span className={styles.version}>
{f.version != null ? `v${f.version}` : '—'} {f.version != null ? `v${f.version}` : '—'}
</span> </span>
<StatusBadge status={f.status} /> <StatusBadge status={f.status} styles={styles} />
</div> </div>
<div className={styles.cardRow}> <div className={styles.cardRow}>
<span style={{ fontSize: 12, color: BF.gray400 }}> <span style={{ fontSize: 12, color: BF.gray400 }}>
@ -760,7 +764,7 @@ const FormList: React.FC = () => {
</span> </span>
</div> </div>
<div> <div>
<StatusBadge status={f.status} /> <StatusBadge status={f.status} styles={styles} />
</div> </div>
<div className={styles.actions}> <div className={styles.actions}>
{st !== 'published' && ( {st !== 'published' && (

View File

@ -408,6 +408,31 @@ const STATUS_TEXT: Record<FnStatus, string> = {
disabled: '已停用', disabled: '已停用',
}; };
type PageStyles = ReturnType<typeof useStyles>['styles'];
const StatusBadge: React.FC<{ status: FnStatus; styles: PageStyles }> = ({
status,
styles,
}) => {
const cls =
status === 'enabled'
? styles.badgeSuccess
: status === 'draft'
? styles.badgeWarning
: styles.badgeDanger;
const dot =
status === 'enabled'
? BF.green600
: status === 'draft'
? BF.amber500
: BF.red500;
return (
<span className={`${styles.badge} ${cls}`}>
<span className={styles.badgeDot} style={{ background: dot }} />
{STATUS_TEXT[status]}
</span>
);
};
/** 从 Code 字符串提取一个简短的首行预览,避免整段代码塞进列表。 */ /** 从 Code 字符串提取一个简短的首行预览,避免整段代码塞进列表。 */
function codePreview(code: string): string { function codePreview(code: string): string {
if (!code) return ''; if (!code) return '';
@ -482,27 +507,6 @@ const FunctionPage: React.FC = () => {
}); });
}, [allItems, activeTab, search]); }, [allItems, activeTab, search]);
const StatusBadge: React.FC<{ status: FnStatus }> = ({ status }) => {
const cls =
status === 'enabled'
? styles.badgeSuccess
: status === 'draft'
? styles.badgeWarning
: styles.badgeDanger;
const dot =
status === 'enabled'
? BF.green600
: status === 'draft'
? BF.amber500
: BF.red500;
return (
<span className={`${styles.badge} ${cls}`}>
<span className={styles.badgeDot} style={{ background: dot }} />
{STATUS_TEXT[status]}
</span>
);
};
/* ---- 新建函数弹窗 ---- */ /* ---- 新建函数弹窗 ---- */
const [createOpen, setCreateOpen] = useState(false); const [createOpen, setCreateOpen] = useState(false);
const [createForm] = Form.useForm<{ const [createForm] = Form.useForm<{
@ -699,7 +703,7 @@ const FunctionPage: React.FC = () => {
<span className={styles.mono}>{r.returns}</span> <span className={styles.mono}>{r.returns}</span>
</div> </div>
<div className={styles.cell}> <div className={styles.cell}>
<StatusBadge status={r.status} /> <StatusBadge status={r.status} styles={styles} />
</div> </div>
</div> </div>
)); ));

View File

@ -370,7 +370,7 @@ const Knowledge: React.FC = () => {
}); });
}, [editing, editForm]); }, [editing, editForm]);
const openEdit = (kb: KbDto) => setEditing(kb); const _openEdit = (kb: KbDto) => setEditing(kb);
const closeEdit = () => { const closeEdit = () => {
if (editSaving) return; if (editSaving) return;

View File

@ -380,6 +380,57 @@ const INST_STATUS_TEXT: Record<InstStatus, string> = {
terminated: '已终止', terminated: '已终止',
}; };
type PageStyles = ReturnType<typeof useStyles>['styles'];
const DefStatusBadge: React.FC<{ status: number; styles: PageStyles }> = ({
status,
styles,
}) => {
const s = DEF_STATUS_MAP[status] ?? 'draft';
const cls =
s === 'published'
? styles.badgeSuccess
: s === 'disabled'
? styles.badgeDanger
: styles.badgeWarning;
const dot =
s === 'published'
? BF.green500
: s === 'disabled'
? BF.red500
: BF.amber500;
return (
<span className={`${styles.badge} ${cls}`}>
<span className={styles.badgeDot} style={{ background: dot }} />
{DEF_STATUS_TEXT[s]}
</span>
);
};
const InstStatusBadge: React.FC<{ status: number; styles: PageStyles }> = ({
status,
styles,
}) => {
const s = INST_STATUS_MAP[status] ?? 'pending';
let cls = styles.badgeNeutral;
let dot: string = BF.gray400;
if (s === 'running' || s === 'completed') {
cls = styles.badgeSuccess;
dot = BF.green500;
} else if (s === 'waiting' || s === 'suspended') {
cls = styles.badgeWarning;
dot = BF.amber500;
} else if (s === 'failed') {
cls = styles.badgeDanger;
dot = BF.red500;
}
return (
<span className={`${styles.badge} ${cls}`}>
<span className={styles.badgeDot} style={{ background: dot }} />
{INST_STATUS_TEXT[s]}
</span>
);
};
const fmtTime = (ts?: number) => const fmtTime = (ts?: number) =>
ts ? dayjs(ts).format('YYYY-MM-DD HH:mm') : '—'; ts ? dayjs(ts).format('YYYY-MM-DD HH:mm') : '—';
@ -499,51 +550,6 @@ const Workflow: React.FC = () => {
}); });
}, [instList, activeTab, search]); }, [instList, activeTab, search]);
/* ---- badges ---- */
const DefStatusBadge: React.FC<{ status: number }> = ({ status }) => {
const s = DEF_STATUS_MAP[status] ?? 'draft';
const cls =
s === 'published'
? styles.badgeSuccess
: s === 'disabled'
? styles.badgeDanger
: styles.badgeWarning;
const dot =
s === 'published'
? BF.green500
: s === 'disabled'
? BF.red500
: BF.amber500;
return (
<span className={`${styles.badge} ${cls}`}>
<span className={styles.badgeDot} style={{ background: dot }} />
{DEF_STATUS_TEXT[s]}
</span>
);
};
const InstStatusBadge: React.FC<{ status: number }> = ({ status }) => {
const s = INST_STATUS_MAP[status] ?? 'pending';
let cls = styles.badgeNeutral;
let dot: string = BF.gray400;
if (s === 'running' || s === 'completed') {
cls = styles.badgeSuccess;
dot = BF.green500;
} else if (s === 'waiting' || s === 'suspended') {
cls = styles.badgeWarning;
dot = BF.amber500;
} else if (s === 'failed') {
cls = styles.badgeDanger;
dot = BF.red500;
}
return (
<span className={`${styles.badge} ${cls}`}>
<span className={styles.badgeDot} style={{ background: dot }} />
{INST_STATUS_TEXT[s]}
</span>
);
};
/* ---- actions ---- */ /* ---- actions ---- */
const navigate = useNavigate(); const navigate = useNavigate();
const [createOpen, setCreateOpen] = useState(false); const [createOpen, setCreateOpen] = useState(false);
@ -727,7 +733,7 @@ const Workflow: React.FC = () => {
<div className={styles.cellStrong}>{i.definitionName || '—'}</div> <div className={styles.cellStrong}>{i.definitionName || '—'}</div>
</div> </div>
<div className={styles.cell}> <div className={styles.cell}>
<InstStatusBadge status={i.status} /> <InstStatusBadge status={i.status} styles={styles} />
</div> </div>
<div className={`${styles.cell} ${styles.cellMuted}`}> <div className={`${styles.cell} ${styles.cellMuted}`}>
{i.currentTask || '—'} {i.currentTask || '—'}
@ -801,7 +807,7 @@ const Workflow: React.FC = () => {
) : null} ) : null}
</div> </div>
<div className={styles.cell}> <div className={styles.cell}>
<DefStatusBadge status={d.status} /> <DefStatusBadge status={d.status} styles={styles} />
</div> </div>
<div className={`${styles.cell} ${styles.cellMuted}`}> <div className={`${styles.cell} ${styles.cellMuted}`}>
{d.version != null ? `v${d.version}` : '—'} {d.version != null ? `v${d.version}` : '—'}

View File

@ -149,7 +149,7 @@ const Register: FC = () => {
<Progress <Progress
status={passwordProgressMap[passwordStatus]} status={passwordProgressMap[passwordStatus]}
size={6} size={6}
percent={value.length * 10 > 100 ? 100 : value.length * 10} percent={Math.min(value.length * 10, 100)}
showInfo={false} showInfo={false}
/> />
</div> </div>