templates/extension-starter/src/web。| 类型 | 导入方式 | 用途 |
|---|---|---|
| 基础 UI 组件 | @buildingai/ui/components/ui/... | 插件页面、控制台页面的按钮、表单、空状态、状态、时间等 |
| AI 展示组件 | @buildingai/ui/components/ai-elements/... | 消息、代码块、工具调用、终端、推理过程等展示 |
| 展示辅助组件 | @buildingai/ui/components/... | 复制、文件图标、图标选择、加载、计数动画等 |
| UI hooks | @buildingai/ui/hooks/... | 确认弹窗、分页、上传、移动端判断等 |
| 页面 hooks | @buildingai/hooks | 复制、页面 head、刷新用户/配置等 |
| 插件请求客户端 | @buildingai/services | 创建插件前台和控制台 HTTP client |
| 共享上传服务 | @buildingai/services/shared | 文件上传、OSS 上传、上传配置缓存 |
| 状态读取 | @buildingai/stores | 读取登录用户、站点配置、用户配置 |
| 插件路由 | @buildingai/web-core | 创建插件前台和控制台路由 |
| 插件 Vite 配置 | @buildingai/web-core/vite/extension | 创建插件前端 Vite 配置 |
import { defineExtensionViteConfig } from "@buildingai/web-core/vite/extension";import { defineExtensionViteConfig } from "@buildingai/web-core/vite/extension";
import packageJson from "./package.json";
export default defineExtensionViteConfig(packageJson, {
server: {
open: true,
},
});| 能力 | 说明 |
|---|---|
| React | 内置 React Vite 插件 |
| Tailwind | 内置 Tailwind Vite 插件 |
| React Compiler | 内置 React Compiler preset |
| 路径解析 | 启用 tsconfigPaths |
| 插件 base | 自动设置为 /extension/{packageJson.name} |
| 环境变量目录 | 自动设置 envDir: ./../../ |
| 构建输出 | 默认输出 .output/public |
| chunk 优化 | lucide-react 单独拆为 lucide chunk |
import { defineRouteOption } from "@buildingai/web-core";import { defineRouteOption } from "@buildingai/web-core";
import packageJson from "./../../package.json";
import ArticleDetailPage from "./pages/article/[id]";
import ArticleListPage from "./pages/console/article/list";
import BlogIndexPage from "./pages/index";
export const routeOption = defineRouteOption({
base: `extension/${packageJson.name}`,
identifier: packageJson.name,
routes: [
{
index: true,
element: <BlogIndexPage />,
},
{
path: "article/:id",
element: <ArticleDetailPage />,
},
],
consoleMenus: [
{
title: "文章管理",
path: "/",
icon: "file-text",
},
],
consoleRoutes: [
{
index: true,
element: <ArticleListPage />,
},
],
});| 参数 | 类型 | 说明 |
|---|---|---|
base | string | 插件前端 basename,通常为 extension/${packageJson.name} |
identifier | string | 插件标识,用于控制台布局获取插件详情 |
routes | RouteObject[] | 插件前台页面路由 |
consoleMenus | ExtensionMenuItem[] | 插件控制台菜单 |
consoleRoutes | RouteObject[] | 插件控制台页面路由 |
| 能力 | 说明 |
|---|---|
| 控制台布局 | consoleRoutes 自动套 ExtensionConsoleLayout |
| 登录守卫 | 控制台路由自动套 AuthGuard |
| 全局错误页 | 路由根部接入 GlobalError |
| 404 页面 | 未匹配路由渲染插件 404 页面 |
| iframe 路由同步 | 插件路由变化会通过 postMessage 同步父页面 |
import { createPluginHttpClients } from "@buildingai/services";import { createPluginHttpClients } from "@buildingai/services";
const { apiHttpClient, consoleHttpClient } = createPluginHttpClients();
export { apiHttpClient, consoleHttpClient };| 客户端 | 用途 | 对应后端 |
|---|---|---|
apiHttpClient | 插件前台接口 | ExtensionWebController |
consoleHttpClient | 插件控制台接口 | ExtensionConsoleController |
import type {
MutationOptionsUtil,
PaginatedQueryOptionsUtil,
PaginatedResponse,
QueryOptionsUtil,
} from "@buildingai/web-types";
import { useMutation, useQuery } from "@tanstack/react-query";
import { consoleHttpClient } from "../base";
import type { Article, CreateArticleParams, QueryArticleParams } from "../types/article";
export function useArticleListQuery(
params?: QueryArticleParams,
options?: PaginatedQueryOptionsUtil<Article>,
) {
return useQuery({
queryKey: ["articles", "list", params],
queryFn: () => consoleHttpClient.get<PaginatedResponse<Article>>("/article", { params }),
...options,
});
}
export function useArticleDetailQuery(id: string, options?: QueryOptionsUtil<Article>) {
return useQuery<Article>({
queryKey: ["articles", "detail", id],
queryFn: () => consoleHttpClient.get<Article>(`/article/${id}`),
enabled: !!id && options?.enabled !== false,
...options,
});
}
export function useCreateArticleMutation(
options?: MutationOptionsUtil<Partial<Article>, CreateArticleParams>,
) {
return useMutation<Partial<Article>, Error, CreateArticleParams>({
mutationFn: (data) => consoleHttpClient.post<Partial<Article>>("/article", data),
...options,
});
}import { Button } from "@buildingai/ui/components/ui/button";| prop | 说明 |
|---|---|
variant | default、outline、secondary、ghost、destructive、link |
size | default、xs、sm、lg、icon、icon-xs、icon-sm、icon-lg |
loading | 加载态,自动禁用按钮并显示 spinner |
asChild | 将按钮样式传给子元素 |
<Button type="submit" variant="outline" size="sm" loading={isSubmitting}>
保存
</Button>import { Spinner } from "@buildingai/ui/components/ui/spinner";<Spinner className="size-4" />import {
Empty,
EmptyContent,
EmptyDescription,
EmptyMedia,
EmptyTitle,
} from "@buildingai/ui/components/ui/empty";<Empty>
<EmptyMedia variant="icon">
<Search className="size-5" />
</EmptyMedia>
<EmptyContent>
<EmptyTitle>暂无文章</EmptyTitle>
<EmptyDescription>创建第一篇文章后会显示在这里。</EmptyDescription>
</EmptyContent>
</Empty>import {
Field,
FieldContent,
FieldDescription,
FieldGroup,
FieldLabel,
FieldSet,
} from "@buildingai/ui/components/ui/field";<FieldSet>
<FieldGroup>
<Field>
<FieldLabel>标题</FieldLabel>
<FieldContent>
<Input value={title} onChange={(event) => setTitle(event.target.value)} />
<FieldDescription>最多 200 个字符。</FieldDescription>
</FieldContent>
</Field>
</FieldGroup>
</FieldSet>import { StatusBadge } from "@buildingai/ui/components/ui/status-badge";| prop | 说明 |
|---|---|
active | 是否为启用态 |
activeText | 启用态文案 |
inactiveText | 停用态文案 |
activeVariant | 启用态 badge 样式 |
inactiveVariant | 停用态 badge 样式 |
<StatusBadge active={article.status === "published"} activeText="已发布" inactiveText="草稿" />import { TimeText } from "@buildingai/ui/components/ui/time-text";| prop | 说明 |
|---|---|
value | Date、字符串或时间戳 |
variant | datetime、date、time、relative |
format | 自定义格式 |
fallback | 空值展示 |
<TimeText value={article.createdAt} variant="datetime" />
<TimeText value={article.updatedAt} variant="relative" />import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
} from "@buildingai/ui/components/ui/input-group";<InputGroup>
<InputGroupAddon>
<Search className="size-4" />
</InputGroupAddon>
<InputGroupInput placeholder="搜索文章" />
<InputGroupButton size="sm">搜索</InputGroupButton>
</InputGroup>import {
Combobox,
ComboboxContent,
ComboboxInput,
ComboboxItem,
ComboboxTrigger,
} from "@buildingai/ui/components/ui/combobox";import { DataTableFacetedFilter } from "@buildingai/ui/components/ui/data-table-faceted-filter";<DataTableFacetedFilter
title="状态"
selectedValue={status}
onSelectionChange={setStatus}
options={[
{ label: "已发布", value: "published" },
{ label: "草稿", value: "draft" },
]}
/>import { ImageUpload } from "@buildingai/ui/components/ui/image-upload";| prop | 说明 |
|---|---|
value | 当前图片 URL |
defaultValue | 默认图片 URL |
accept | 文件类型 |
maxSize | 最大大小 |
placeholder | 占位内容 |
onChange | 上传完成后回调 |
onUploadStart | 上传开始回调 |
onUploadError | 上传失败回调 |
size | sm、default、lg、xl |
shape | circle、rounded |
<ImageUpload
value={cover}
accept="image/*"
maxSize={5 * 1024 * 1024}
onChange={(url) => setCover(url)}
/>import { Upload, useUpload } from "@buildingai/ui/components/upload";| 能力 | 说明 |
|---|---|
multiple | 多文件上传 |
accept | 限制文件类型 |
maxSize | 限制单文件大小 |
maxFiles | 限制文件数量 |
params | 透 传上传参数 |
onUploadProgress | 上传进度 |
onUploadSuccess | 单文件成功 |
onUploadError | 上传失败 |
onUploadComplete | 批量完成 |
import { useUploadFile } from "@buildingai/ui/hooks/use-upload-file";| 字段 | 说明 |
|---|---|
isUploading | 是否上传中 |
progress | 上传进度 |
uploadedFile | 上传完成文件 |
uploadFile | 执行上传 |
uploadingFile | 当前上传文件 |
import {
detectFileType,
invalidateStorageConfigCache,
uploadFile,
uploadFileAuto,
uploadFiles,
uploadFilesAuto,
uploadInitFile,
} from "@buildingai/services/shared";| 工具 | 说明 |
|---|---|
uploadFile | 上传单文件 |
uploadFiles | 上传多文件 |
uploadInitFile | 系统初始化阶段上传文件 |
uploadFileAuto | 自动选择上传方式上传单文件 |
uploadFilesAuto | 自动选择上传方式上传多文件 |
detectFileType | 识别文件类型 |
invalidateStorageConfigCache | 清理上传配置缓存 |
import { Message, MessageContent } from "@buildingai/ui/components/ai-elements/message";<Message from="assistant">
<MessageContent>生成完成。</MessageContent>
</Message>import {
CodeBlock,
CodeBlockCopyButton,
CodeBlockHeader,
CodeBlockTitle,
} from "@buildingai/ui/components/ai-elements/code-block";<CodeBlock code={source} language="tsx" showLineNumbers>
<CodeBlockHeader>
<CodeBlockTitle>example.tsx</CodeBlockTitle>
<CodeBlockCopyButton />
</CodeBlockHeader>
</CodeBlock>import {
Tool,
ToolContent,
ToolHeader,
ToolOutput,
} from "@buildingai/ui/components/ai-elements/tool";<Tool>
<ToolHeader type="tool-call" state="output-available" />
<ToolContent>
<ToolOutput output="执行完成" />
</ToolContent>
</Tool>| 组件路径 | 可用组件 |
|---|---|
ai-elements/conversation | 对话容器 |
ai-elements/prompt-input | 提示词输入框 |
ai-elements/reasoning | 推理过程 |
ai-elements/terminal | 终端输出 |
ai-elements/sources | 来源引用 |
ai-elements/file-tree | 文件树 |
ai-elements/task | 任务状态 |
ai-elements/tests | 测试结果 |
ai-elements/stack-trace | 错误堆栈 |
ai-elements/preview | 预览内容 |
import { CopyButton } from "@buildingai/ui/components/copy-button";<CopyButton value={article.url} size="icon-sm" />import { FileFormatIcon } from "@buildingai/ui/components/file-format-icon";<FileFormatIcon filename={file.name} />import { LucideIcon } from "@buildingai/ui/components/lucide-icon";<LucideIcon name="file-text" className="size-4" />import { IconPicker } from "@buildingai/ui/components/icon-picker";| prop | 说明 |
|---|---|
value | 当前图标名 |
onChange | 选择变化回调 |
placeholder | 占位文案 |
disabled | 禁用 |
container | Popover 容器 |
<IconPicker value={icon} onChange={setIcon} placeholder="选择菜单图标" />import Loader from "@buildingai/ui/components/loader";
import ReloadWindow from "@buildingai/ui/components/reload-window";
import { CountUp } from "@buildingai/ui/components/count-up";
import { SplitText } from "@buildingai/ui/components/split-text";| 组件 | 场景 |
|---|---|
Loader | 页面或局部加载 |
ReloadWindow | 提示刷新页面 |
CountUp | 数字动画 |
SplitText | 文本拆分动画 |
import { useAlertDialog } from "@buildingai/ui/hooks/use-alert-dialog";const { confirm } = useAlertDialog();
await confirm({
title: "删除文章",
description: "删除后不可恢复,确认继续?",
});import { usePagination } from "@buildingai/ui/hooks/use-pagination";| 字段 | 说明 |
|---|---|
currentPage | 当前页 |
totalPages | 总页数 |
pageSize | 每页数量 |
total | 总条数 |
hasPrevious | 是否有上一页 |
hasNext | 是否有下一页 |
goToPage | 跳转页码 |
nextPage | 下一页 |
previousPage | 上一页 |
reset | 重置分页 |
PaginationComponent | 分页组件 |
import { useCopy } from "@buildingai/hooks";const { copy, isCopying } = useCopy();
await copy(article.url);import { useDocumentHead } from "@buildingai/hooks";useDocumentHead({
title: "文章管理",
});| hook | 导入 | 场景 |
|---|---|---|
useMobile | @buildingai/ui/hooks/use-mobile | 判断移动端 |
useMounted | @buildingai/ui/hooks/use-mounted | 判断组件是否已挂载 |
useDebounce | @buildingai/ui/hooks/use-debounce | 输入防抖 |
useRefreshUser | @buildingai/hooks | 刷新当前用户 |
useRefreshUserConfig | @buildingai/hooks | 刷新用户配置 |
useRefreshWebsiteConfig | @buildingai/hooks | 刷新站点配置 |
import { useAuthStore, useConfigStore, useUserConfigStore } from "@buildingai/stores";| store | 说明 |
|---|---|
useAuthStore | 当前登录状态、用户信息、权限码 |
useConfigStore | 站点配置 |
useUserConfigStore | 用户配置 |
const userInfo = useAuthStore((state) => state.auth.userInfo);
const websiteConfig = useConfigStore((state) => state.config);import type {
MutationOptionsUtil,
PaginatedQueryOptionsUtil,
PaginatedResponse,
QueryOptionsUtil,
} from "@buildingai/web-types";| 类型 | 说明 |
|---|---|
PaginatedResponse<T> | 分页响应 |
QueryOptionsUtil<T> | React Query 查询 options |
PaginatedQueryOptionsUtil<T> | 分页查询 options |
MutationOptionsUtil<TData, TVariables> | mutation options |