删除插件逻辑
1. 删除操作入口组件
删除插件操作主要通过 usePluginConfig
hook 中的 renderActions
方法实现,该方法返回 TableAction
组件来处理表格行的操作。
文件位置:frontend/packages/studio/workspace/entry-base/src/pages/library/hooks/use-entity-configs/use-plugin-config.tsx
核心实现逻辑:
// 在 usePluginConfig hook 中
renderActions: (item: ResourceInfo) => {const deleteDisabled = !item.actions?.find(action => action.key === ActionKey.Delete,)?.enable;const deleteProps = {disabled: deleteDisabled,deleteDesc: I18n.t('library_delete_desc'),handler: async () => {await PluginDevelopApi.DelPlugin({ plugin_id: item.res_id });reloadList();Toast.success(I18n.t('Delete_success'));},};return (<TableActiondeleteProps={deleteProps}actionList={getCommonActions?.(item)}/>);
}
设计亮点:
- 权限控制:基于后端返回的
actions
数组动态控制按钮状态 - 国际化支持:使用
I18n.t()
进行多语言支持 - 操作集成:同时支持删除、编辑等多种操作
2. 删除插件的核心Hook实现
文件位置:frontend/packages/studio/workspace/entry-base/src/pages/library/hooks/use-entity-configs/use-plugin-config.tsx
核心代码:
import { useNavigate } from 'react-router-dom';
import { useState } from 'react';
import {ActionKey,PluginType,ResType,type ResourceInfo,
} from '@coze-arch/idl/plugin_develop';
import { I18n } from '@coze-arch/i18n';
import { PluginDevelopApi } from '@coze-arch/bot-api';
import { useBotCodeEditOutPlugin } from '@coze-agent-ide/bot-plugin/hook';
import { CreateFormPluginModal } from '@coze-agent-ide/bot-plugin/component';
import { IconCozPlugin } from '@coze-arch/coze-design/icons';
import { Menu, Tag, Toast, Table } from '@coze-arch/coze-design';const { TableAction } = Table;export const usePluginConfig: UseEntityConfigHook = ({spaceId,reloadList,getCommonActions,
}) => {const [showFormPluginModel, setShowFormPluginModel] = useState(false);const navigate = useNavigate();const { modal: editPluginCodeModal, open } = useBotCodeEditOutPlugin({modalProps: {onSuccess: reloadList,},});return {config: {renderActions: (item: ResourceInfo) => {const deleteDisabled = !item.actions?.find(action => action.key === ActionKey.Delete,)?.enable;const deleteProps = {disabled: deleteDisabled,deleteDesc: I18n.t('library_delete_desc'),handler: async () => {await PluginDevelopApi.DelPlugin({ plugin_id: item.res_id });reloadList();Toast.success(I18n.t('Delete_success'));},};return (<TableActiondeleteProps={deleteProps}actionList={getCommonActions?.(item)}/>);},},};
};
设计亮点:
- 异步状态管理:使用
useRequest
管理删除请求状态 - 错误处理:完善的成功/失败回调处理
- 用户反馈:及时的Toast提示信息
- 列表刷新:删除成功后自动刷新资源列表
3. 删除确认弹窗逻辑
文件位置:frontend/packages/components/bot-semi/src/components/ui-table-action/index.tsx
核心代码:
// 删除确认弹窗的实现
<Popconfirmtrigger="click"okType="danger"title={i18n.t('delete_title')}content={i18n.t('plugin_delete_confirm_desc')}okText={i18n.t('confirm')}cancelText={i18n.t('cancel')}style={{ width: 350 }}icon={deleteProps?.popconfirm?.icon ?? <IconWaringRed />}{...deleteProps.popconfirm}onConfirm={deleteProps?.handler}disabled={deleteProps.disabled}
><span><Tooltipspacing={12}content={i18n.t('Delete')}position="top"{...deleteProps.tooltip}><UIIconButtondisabled={deleteProps.disabled}icon={<IconDeleteOutline className={styles.icon} />}style={iconColor('delete')}onClick={deleteProps.handleClick}data-testid="ui.table-action.delete"/></Tooltip></span>
</Popconfirm>
设计亮点:
- 权限控制:基于后端返回的actions数组动态控制删除按钮状态
- 确认机制:使用Popconfirm组件提供删除确认弹窗
- 错误处理:完善的错误提示和异常处理
- 用户反馈:及时的成功/失败提示
4. TableAction 组件实现
文件位置:@coze-arch/coze-design
包中的 Table.TableAction
组件
核心代码:
import { Table } from '@coze-arch/coze-design';const { TableAction } = Table;// TableAction 组件的使用方式
<TableActiondeleteProps={{disabled: deleteDisabled,deleteDesc: I18n.t('library_delete_desc'),handler: async () => {await PluginDevelopApi.DelPlugin({ plugin_id: item.res_id });reloadList();Toast.success(I18n.t('Delete_success'));},}}actionList={getCommonActions?.(item)}
/>
设计亮点:
- 组件复用:来自 @coze-arch/coze-design 的统一表格操作组件
- 配置化:通过 deleteProps 和 actionList 配置操作
- 权限控制:基于后端返回的 actions 数组控制操作权限
- 确认机制:内置删除确认弹窗机制
5. 插件配置Hook(usePluginConfig)完整实现
文件位置:frontend/packages/studio/workspace/entry-base/src/pages/library/hooks/use-entity-configs/use-plugin-config.tsx
管理插件的删除功能和状态的完整实现:
import { useNavigate } from 'react-router-dom';
import { useRef } from 'react';
import { useRequest } from 'ahooks';
import {ActionKey,ResType,type ResourceInfo,
} from '@coze-arch/idl/plugin_develop';
import { I18n } from '@coze-arch/i18n';
import { IconCozPlugin } from '@coze-arch/coze-design/icons';
import { Table, Menu, Toast } from '@coze-arch/coze-design';
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
import { useFlags } from '@coze-arch/bot-flags';
import { PluginDevelopApi } from '@coze-arch/bot-api';
import { usePluginEditorModal } from '@coze-common/plugin-editor-modal';import { type UseEntityConfigHook } from './types';const { TableAction } = Table;export const usePluginConfig: UseEntityConfigHook = ({spaceId,isPersonalSpace = true,reloadList,getCommonActions,
}) => {const navigate = useNavigate();const [FLAGS] = useFlags();const recordRef = useRef<ResourceInfo | null>(null);const { open: openPluginEditor, node: pluginEditorModal } =usePluginEditorModal({spaceId,source: 'resource_library',onUpdateSuccess: reloadList,onPublish: ({ pluginId }) => {recordRef.current = {res_id: pluginId,};// 插件发布后的处理逻辑},});// 删除插件的核心逻辑const { run: delPlugin } = useRequest((pluginId: string) =>PluginDevelopApi.DelPlugin({plugin_id: pluginId,}),{manual: true,onSuccess: () => {reloadList(); // 删除成功后刷新列表Toast.success(I18n.t('Delete_success')); // 显示成功提示},onError: (error) => {Toast.error(I18n.t('Delete_failed')); // 显示失败提示console.error('删除插件失败:', error);},},);return {modals: (<>{pluginEditorModal}</>),config: {typeFilter: {label: I18n.t('library_resource_type_plugin'),value: ResType.Plugin,},renderCreateMenu: () => (<Menu.Itemdata-testid="workspace.library.header.create.plugin"icon={<IconCozPlugin />}onClick={() => {sendTeaEvent(EVENT_NAMES.widget_create_click, {source: 'menu_bar',workspace_type: isPersonalSpace? 'personal_workspace': 'team_workspace',});openPluginEditor({mode: 'create',});}}>{I18n.t('create_new_plugin')}</Menu.Item>),target: [ResType.Plugin],onItemClick: (record: ResourceInfo) => {recordRef.current = record;const canEdit = record.actions?.find(action => action.key === ActionKey.Edit,)?.enable;openPluginEditor({mode: 'info',canEdit,editId: record.res_id || '',});},// 渲染表格操作列,包含删除功能renderActions: (libraryResource: ResourceInfo) => (<TableActiondeleteProps={{// 根据权限控制删除按钮状态disabled: !libraryResource.actions?.find(action => action.key === ActionKey.Delete,)?.enable,// 删除确认描述deleteDesc: I18n.t('plugin_resource_delete_describ'),// 删除处理函数handler: () => {delPlugin(libraryResource.res_id || '');},}}// 编辑操作editProps={{disabled: !libraryResource.actions?.find(action => action.key === ActionKey.Edit,)?.enable,handler: () => {openPluginEditor({mode: 'edit',editId: libraryResource.res_id || '',});},}}actionList={getCommonActions?.(libraryResource)}/>),},};
};
bot-api/package.json
文件位置:frontend/packages/arch/bot-api/package.json
核心代码:
{"name": "@coze-arch/bot-api","version": "0.0.1","description": "RPC wrapper for bot studio application","author": "fanwenjie.fe@bytedance.com","exports": {".": "./src/index.ts"}
}
代码作用:
- 包定义:定义了一个名为
@coze-arch/bot-api
的 npm 包,版本为 0.0.1,这是一个用于 bot studio 应用的 RPC 包装器。 - API导出:在
frontend/packages/arch/bot-api/src/index.ts
中,PluginDevelopApi 被导出:
export { PluginDevelopApi } from './plugin-develop-api';
这允许通过 @coze-arch/bot-api
直接导入 PluginDevelopApi
。
3. API实现:在 src/plugin-develop-api.ts
中,PluginDevelopApi
是一个配置好的服务实例,它使用了 PluginDevelopService 和 axios 请求配置。
src/plugin-develop-api.ts
文件位置:frontend/packages/arch/bot-api/src/plugin-develop-api.ts
核心代码:
import PluginDevelopApiService from './idl/plugin_develop';
import { axiosInstance, type BotAPIRequestConfig } from './axios';// eslint-disable-next-line @typescript-eslint/naming-convention
export const PluginDevelopApi = new PluginDevelopApiService<BotAPIRequestConfig>({request: (params, config = {}) => {config.headers = Object.assign(config.headers || {}, {'Agw-Js-Conv': 'str',});return axiosInstance.request({ ...params, ...config });},
});
axiosInstance说明
- 全局共享:axiosInstance 在整个项目中是全局共享的
- bot-api 包中的导入:
frontend/packages/arch/bot-api/src/axios.ts
直接从@coze-arch/bot-http
包导入了 axiosInstance
import {axiosInstance,isApiError,type AxiosRequestConfig,
} from '@coze-arch/bot-http';
- bot-http 包中的定义:
frontend/packages/arch/bot-http/src/axios.ts
export const axiosInstance = axios.create();
这里创建了一个全局的 axios 实例,与其他API请求使用的是同一个实例。
PluginDevelopApiService说明
- bot-api包中的导入路径:
import PluginDevelopApiService from './idl/plugin_develop';
实际指向 frontend/packages/arch/bot-api/src/idl/plugin_develop.ts
文件内容重新导出了 @coze-arch/idl/plugin_develop
包的所有内容,包括默认导出:
export * from '@coze-arch/idl/plugin_develop';
export { default as default } from '@coze-arch/idl/plugin_develop';
- idl包的模块映射:
文件位置:frontend/packages/arch/idl/package.json
{"name": "@coze-arch/idl","version": "0.0.1","description": "IDL files for bot studio application","author": "fanwenjie.fe@bytedance.com","exports": {"./plugin_develop": "./src/auto-generated/plugin_develop/index.ts"}
}
代码作用:将 @coze-arch/idl/plugin_develop
映射到实际文件路径 frontend/packages/arch/idl/src/auto-generated/plugin_develop/index.ts
6. 删除插件操作的完整流程
删除插件的完整流程如下:
流程说明:
- 权限检查:基于后端返回的
actions
数组判断是否有删除权限 - 确认机制:通过
Popconfirm
组件进行二次确认 - API调用:使用
PluginDevelopApi.DelPlugin
执行删除操作 - 状态更新:删除成功后自动刷新资源列表
- 用户反馈:通过
Toast
组件显示操作结果