/** * 渲染层组件源码(JSX)。构建: * esbuild src/renderer.tsx --bundle --format=esm --outfile=renderer.mjs \ * --jsx=automatic --alias:react=./shims/react.js --alias:react/jsx-runtime=./shims/jsx-runtime.js * 产物 renderer.mjs 是自包含单文件,放回插件目录即被宿主装载。 * * UI 一律用 @aso/plugin-sdk/ui 公共组件:token 与宿主同源、深浅色自动跟随, * 等待/失败/空态统一呈现(规范见 docs/plugins/07-ui-components.md 与 DESIGN.md)。 * 一切异步操作都要有超时(withTimeout/withRetry 见 '@aso/plugin-sdk'), * 区块三态:LoadingState → ErrorState(可重试) / EmptyState / 数据。 */ import { useEffect, useState } from 'react' import { withTimeout } from '@aso/plugin-sdk' import { Button, EmptyState, ErrorState, LoadingState, SettingRow, Switch } from '@aso/plugin-sdk/ui' /** 模拟异步数据源:换成真实 IPC / 请求时保持「必超时、必 catch」的形状 */ function fetchItems(): Promise { return withTimeout(Promise.resolve(['条目一', '条目二', '条目三']), 3000, '拉取数据') } export default function mount(ctx) { function Panel() { const [items, setItems] = useState(null) // null=加载中 const [error, setError] = useState(null) const [verbose, setVerbose] = useState(false) const [tick, setTick] = useState(0) useEffect(() => { let alive = true setItems(null) setError(null) fetchItems() .then((data) => { if (alive) setItems(data) }) .catch((err) => { if (alive) setError(err instanceof Error ? err.message : String(err)) }) return () => { alive = false } }, [tick]) return (
我的插件面板
{error !== null ? ( setTick((t) => t + 1)} /> ) : items === null ? ( ) : items.length === 0 ? ( ) : ( items.map((it) => ) )}
) } ctx.registerPanel(Panel) ctx.registerView(Panel) }