# ComponentInstaller

`ComponentInstaller` 是一个实用工具组件，为依赖其他 blocklet 的功能充当网关。它会验证指定的组件依赖项是否已安装。如果未安装，它会为管理员（默认为 `owner` 或 `admin` 角色）提供一个用户友好的界面，以便直接进行安装。如果用户缺少必要的权限，它会显示一条消息，建议他们联系管理员。

该组件对于创建能够优雅处理可选依赖项缺失的稳健应用程序至关重要，通过引导管理员完成安装过程来改善用户体验。

## 工作原理

该组件的逻辑遵循一个清晰的顺序流程，以确保在渲染其子组件之前满足依赖关系。

<!-- DIAGRAM_IMAGE_START:flowchart:4:3:1765962229 -->
![ComponentInstaller](assets/diagram/component-installer-diagram-0.zh.jpg)
<!-- DIAGRAM_IMAGE_END -->

1.  **依赖检查**：它读取 `did` prop，并与 blocklet 的元数据（`window.blocklet.optionalComponents`）进行核对，以确定所需的组件是否已安装。
2.  **权限检查**：如果缺少任何组件，它会使用 `SessionPermission` 组件来验证当前用户的角色是否与 `roles` prop 中指定的角色匹配。
3.  **条件渲染**：
    *   如果所有依赖项都已安装，它将渲染其 `children`。
    *   如果依赖项缺失且用户拥有权限，它将显示一个弹出式安装面板。
    *   如果依赖项缺失且用户缺乏权限，它将显示联系管理员的建议，或者如果启用了 `noPermissionMute`，则渲染一个 `fallback` 组件。

## 基本用法

使用 `ComponentInstaller` 包裹任何依赖于可选 blocklet 的组件或功能。提供所需组件的 DID。

```jsx "MyFeature.jsx" icon=logos:react
import ComponentInstaller from '@arcblock/blocklet-ui-react/lib/ComponentInstaller';
import MyDependentComponent from './MyDependentComponent';

export default function MyFeature() {
  // 替换为所需组件的实际 DID
  const requiredComponentDid = 'z8ia2427634f1e909a304e2b963715a18';

  return (
    <ComponentInstaller did={requiredComponentDid}>
      {/* 只有在满足依赖关系时，此组件才会被渲染 */}
      <MyDependentComponent />
    </ComponentInstaller>
  );
}
```

## Props

`ComponentInstaller` 接受以下 props 来自定义其行为。

<x-field-group>
  <x-field data-name="did" data-type="string | string[]" data-required="true">
    <x-field-desc markdown>要检查的组件依赖项的 DID（或 DID 数组）。这是您需要的 blocklet 的主要标识符。</x-field-desc>
  </x-field>
  <x-field data-name="children" data-type="any" data-required="true">
    <x-field-desc markdown>确认所有依赖项都已安装后要渲染的内容。可以是标准的 React 节点或渲染函数。</x-field-desc>
  </x-field>
  <x-field data-name="roles" data-type="string[]" data-default='["owner", "admin"]'>
    <x-field-desc markdown>允许查看安装界面并安装缺失组件的用户角色数组。</x-field-desc>
  </x-field>
  <x-field data-name="fallback" data-type="React.ReactNode" data-required="false">
    <x-field-desc markdown>一个回退组件，用于在检查依赖项时或当 `noPermissionMute` 对没有安装权限的用户激活时显示。</x-field-desc>
  </x-field>
  <x-field data-name="noPermissionMute" data-type="boolean" data-default="false">
    <x-field-desc markdown>如果为 `true`，没有权限的用户将看到 `fallback` 组件（或什么也不显示），而不是“联系管理员”的消息。</x-field-desc>
  </x-field>
  <x-field data-name="disabled" data-type="boolean" data-default="false">
    <x-field-desc markdown>如果为 `true`，该组件将绕过所有检查并立即渲染其 `children`。</x-field-desc>
  </x-field>
  <x-field data-name="onInstalled" data-type="function" data-required="false">
    <x-field-desc markdown>依赖项检查完成后触发的回调函数。它接收已安装组件的列表。</x-field-desc>
  </x-field>
  <x-field data-name="onError" data-type="function" data-required="false">
    <x-field-desc markdown>如果在依赖项检查期间发生错误，则触发的回调函数。它接收导致错误的组件列表。</x-field-desc>
  </x-field>
  <x-field data-name="onClose" data-type="function" data-required="false">
    <x-field-desc markdown>当安装弹出窗口关闭时调用的回调函数。</x-field-desc>
  </x-field>
  <x-field data-name="closeByOutSize" data-type="boolean" data-default="false">
    <x-field-desc markdown>如果为 `true`，当用户点击安装弹出窗口外部时，该窗口将关闭。</x-field-desc>
  </x-field>
  <x-field data-name="warnIcon" data-type="React.ReactNode" data-required="false">
    <x-field-desc markdown>一个自定义的 React 节点，用于替换安装弹出窗口中的默认警告图标。</x-field-desc>
  </x-field>
</x-field-group>

## 高级用法

### 检查多个组件

您可以向 `did` prop 传递一个 DID 数组，以一次性检查多个依赖项。只有在所有指定的组件都已安装的情况下，子组件才会渲染。

```jsx "MyDashboard.jsx" icon=logos:react
import ComponentInstaller from '@arcblock/blocklet-ui-react/lib/ComponentInstaller';
import AnalyticsWidget from './AnalyticsWidget';
import CmsWidget from './CmsWidget';

export default function MyDashboard() {
  const requiredDids = [
    'z8ia2427634f1e909a304e2b963715a18', // Analytics Service
    'z8ia3c1f2e4b8e6a1b2c3d4e5f6a7b8c9', // CMS Service
  ];

  return (
    <ComponentInstaller did={requiredDids}>
      <AnalyticsWidget />
      <CmsWidget />
    </ComponentInstaller>
  );
}
```

### 为加载状态使用回退组件

提供一个 `fallback` 组件，以在依赖项检查进行中时改善用户体验。这对于与 `noPermissionMute` 一起使用，为未经授权的用户显示占位符也很有用。

```jsx "FeatureWithLoading.jsx" icon=logos:react
import ComponentInstaller from '@arcblock/blocklet-ui-react/lib/ComponentInstaller';
import CircularProgress from '@mui/material/CircularProgress';
import Box from '@mui/material/Box';
import MyDependentComponent from './MyDependentComponent';

export default function FeatureWithLoading() {
  const LoadingSpinner = (
    <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>
      <CircularProgress />
    </Box>
  );

  return (
    <ComponentInstaller
      did="z8ia2427634f1e909a304e2b963715a18"
      fallback={LoadingSpinner}
      noPermissionMute={true}
    >
      <MyDependentComponent />
    </ComponentInstaller>
  );
}
```

### 使用渲染 Props 自定义 UI

为了完全控制 UI，您可以将一个函数作为 `children` 传递。该函数接收一个包含 `hasPermission`、`optComponents` 和 `installStatus` 的对象，允许您构建一个完全自定义的安装界面。

```jsx "CustomInstallerButton.jsx" icon=logos:react
import ComponentInstaller from '@arcblock/blocklet-ui-react/lib/ComponentInstaller';
import Button from '@mui/material/Button';

export default function CustomInstallerButton() {
  const requiredDid = 'z8ia2427634f1e909a304e2b963715a18';

  return (
    <ComponentInstaller did={requiredDid}>
      {({ hasPermission, optComponents, installStatus }) => {
        const isMissing = optComponents.length > 0;
        const status = installStatus[requiredDid] || 'not_installed';

        if (!isMissing) {
          return <p>功能已准备就绪！</p>;
        }

        if (!hasPermission) {
          return <p>请请求管理员安装所需组件。</p>;
        }

        return (
          <Button
            variant="contained"
            disabled={status !== 'not_installed'}
            onClick={() => window.open(optComponents[0].installUrl, '_blank')}
          >
            {status === 'not_installed' ? `安装 ${optComponents[0].meta.title}` : `正在安装... (${status})`}
          </Button>
        );
      }}
    </ComponentInstaller>
  );
}
```

## 总结

`ComponentInstaller` 是一个功能强大的组件，用于管理 blocklet 应用程序中的可选依赖项。它提供了一种结构化且用户友好的方式，以确保所需组件可用，并在必要时引导管理员完成安装过程。通过使用此组件，您可以构建更具弹性和功能丰富的应用程序，以适应用户的环境。

有关管理组件的更多详细信息，您可能还对 [BlockletStudio](./components-component-management-blocklet-studio.md) 组件感兴趣，它为资源和组件管理提供了更全面的界面。