86 lines
2.6 KiB
TypeScript
86 lines
2.6 KiB
TypeScript
/**
|
||
* 能力演示卡片的定义结构
|
||
* - title: 卡片标题
|
||
* - desc: 能力说明
|
||
* - actions: 该卡片下的操作按钮列表
|
||
*/
|
||
export interface DemoAction {
|
||
/** 按钮文字 */
|
||
label: string;
|
||
/** 是否使用次要样式(灰色按钮) */
|
||
secondary?: boolean;
|
||
/** 点击后执行的处理函数,返回要展示的文本 */
|
||
run: (ctx: DemoContext) => Promise<string> | string;
|
||
}
|
||
|
||
/** 卡片定义 */
|
||
export interface DemoCard {
|
||
/** 卡片唯一 id,用于关联输出区域 */
|
||
id: string;
|
||
/** 卡片标题 */
|
||
title: string;
|
||
/** 卡片说明文字 */
|
||
desc: string;
|
||
/** 是否需要一个文本输入框 */
|
||
input?: { placeholder: string; default?: string };
|
||
/** 操作按钮 */
|
||
actions: DemoAction[];
|
||
}
|
||
|
||
/** 传递给操作函数的上下文,可读取输入框内容 */
|
||
export interface DemoContext {
|
||
/** 获取当前卡片输入框的值(不存在时返回空字符串) */
|
||
getInput: () => string;
|
||
}
|
||
|
||
/**
|
||
* 渲染单个能力卡片到指定容器
|
||
* @param container 卡片挂载的父节点
|
||
* @param card 卡片定义
|
||
* 注意事项:按钮点击时会自动捕获异常并以红色输出错误信息,避免未处理的 Promise 报错。
|
||
*/
|
||
export function renderCard(container: HTMLElement, card: DemoCard): void {
|
||
const el = document.createElement("section");
|
||
el.className = "card";
|
||
|
||
const inputHtml = card.input
|
||
? `<div class="row" style="margin-bottom:10px">
|
||
<input type="text" id="input-${card.id}" placeholder="${card.input.placeholder}" value="${card.input.default ?? ""}" />
|
||
</div>`
|
||
: "";
|
||
|
||
el.innerHTML = `
|
||
<h2>${card.title}</h2>
|
||
<p class="desc">${card.desc}</p>
|
||
${inputHtml}
|
||
<div class="row" id="actions-${card.id}"></div>
|
||
<div class="output" id="out-${card.id}">—</div>
|
||
`;
|
||
container.appendChild(el);
|
||
|
||
const out = el.querySelector<HTMLDivElement>(`#out-${card.id}`)!;
|
||
const actionsRow = el.querySelector<HTMLDivElement>(`#actions-${card.id}`)!;
|
||
const ctx: DemoContext = {
|
||
getInput: () =>
|
||
el.querySelector<HTMLInputElement>(`#input-${card.id}`)?.value ?? "",
|
||
};
|
||
|
||
for (const action of card.actions) {
|
||
const btn = document.createElement("button");
|
||
btn.textContent = action.label;
|
||
if (action.secondary) btn.classList.add("secondary");
|
||
btn.addEventListener("click", async () => {
|
||
out.className = "output";
|
||
out.textContent = "执行中…";
|
||
try {
|
||
out.textContent = await action.run(ctx);
|
||
out.classList.add("ok");
|
||
} catch (err) {
|
||
out.textContent = `出错:${err instanceof Error ? err.message : String(err)}`;
|
||
out.classList.add("err");
|
||
}
|
||
});
|
||
actionsRow.appendChild(btn);
|
||
}
|
||
}
|