Coze源码分析-资源库-编辑数据库-前端源码-核心组件

概述

本文深入分析Coze Studio中用户编辑数据库功能的前端实现。该功能允许用户在资源库中选择现有数据库进行编辑,修改其基本信息(名称、描述、图标等)和表结构,为开发者提供了灵活的数据库管理能力。通过对源码的详细解析,我们将了解从资源库入口到编辑页面的完整架构设计、组件实现、状态管理和用户体验优化等核心技术要点。

功能特性

核心功能

  • 数据库基本信息编辑:支持修改数据库名称、描述和图标配置
  • 数据库读写模式管理:提供数据库读写模式切换功能
  • 表结构编辑:支持编辑数据库表结构
  • 权限控制:基于用户权限动态显示编辑功能
  • 实时更新:编辑完成后自动更新页面展示
  • 表单验证:完善的编辑内容验证机制

用户体验特性

  • 即时反馈:编辑操作结果实时展示和验证
  • 表单验证:完善的数据库信息验证机制
  • 便捷操作:通过表格行点击直接进入数据库详情页面
  • 图标智能生成:自动生成数据库图标
  • 国际化支持:多语言界面适配

技术架构

整体架构设计

复制代码
┌─────────────────────────────────────────────────────────────┐
│                      数据库编辑模块                          │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │ LibraryPage │  │BaseLibrary  │  │DatabaseDetail       │  │
│  │ (资源库页面) │  │    Page     │  │  (数据库详情页面)     │  │
│  └─────────────┘  └─────────────┘  └─────────────────────┘  │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │Table组件    │  │Database    │  │BaseInfoModal       │  │
│  │ (表格展示)  │  │  Header组件 │  │  (基本信息编辑模态框) │  │
│  └─────────────┘  └─────────────┘  └─────────────────────┘  │
├─────────────────────────────────────────────────────────────┤
│                      状态管理层                             │
│  ┌─────────────────┐  ┌─────────────────────────────────┐  │
│  │useDatabaseConfig│  │ handleEditBasicInfo 函数         │  │
│  │  (配置逻辑)      │  │   (编辑逻辑处理)               │  │
│  └─────────────────┘  └─────────────────────────────────┘  │
├─────────────────────────────────────────────────────────────┤
│                       API服务层                            │
│  ┌─────────────────────────────────────────────────────────┐
│  │              Memory API                                 │
│  │             UpdateDatabase API                          │
│  └─────────────────────────────────────────────────────────┘
└────────────────────────────────────────────────────────────┘

核心模块结构

复制代码
frontend/
├── apps/coze-studio/src/
│   └── pages/
│       ├── library.tsx            # 资源库入口页面
│       └── database/
│           ├── layout.tsx         # 数据库页面布局
│           ├── page.tsx           # 数据库详情页面
├── packages/studio/workspace/
│   ├── entry-adapter/src/pages/library/
│   │   └── index.tsx              # LibraryPage适配器组件
│   └── entry-base/src/pages/library/
│       ├── index.tsx              # BaseLibraryPage核心组件
│       └── hooks/use-entity-configs/
│           └── use-database-config.tsx  # 数据库配置Hook
├── packages/data/memory/
│   ├── database-v2-main/src/
│   │   └── components/database-detail/
│   │       └── index.tsx          # 数据库详情页面组件
│   ├── database-v2-base/src/
│   │   └── components/base-info-modal/
│   │       └── index.tsx          # 基本信息编辑模态框
│   └── database-v2-adapter/src/
│       └── components/base-info-modal/
│           └── index.tsx          # 基本信息编辑模态框适配器
├── packages/arch/idl/src/auto-generated/
│   └── memory/
│       └── database.ts            # 数据库相关类型定义
└── packages/arch/bot-api/src/
    └── memory-api.ts              # MemoryApi定义(含UpdateDatabase)

用户编辑数据库流程概述

复制代码
用户登录Coze Studio
        ↓
  点击"资源库"菜单
        ↓
  LibraryPage 组件加载
        ↓
  在表格中点击要编辑的数据库行
        ↓
  导航到数据库详情页面
        ↓
  DatabaseDetail 组件加载并展示数据库信息
        ↓
  用户点击数据库名称旁边的编辑按钮
        ↓
  BaseInfoModal 模态框显示,预填充数据库信息
        ↓
  用户修改数据库名称、描述或图标
        ↓
  表单实时验证(名称必填且不能包含特殊字符)
        ↓
  用户点击"确认"按钮
        ↓
  handleSubmit 回调触发
        ↓
  handleEditBasicInfo 函数调用
        ↓
  MemoryApi.UpdateDatabase() 调用
        ↓
  后端更新数据库信息
        ↓
  fetchDatabaseInfo 重新获取数据库信息
        ↓
  更新本地状态(setDatabaseInfo)
        ↓
  关闭模态框,页面显示更新后的数据库信息

该流程包含多层验证和处理:

  1. 权限控制:通过isReadOnlyMode状态控制编辑按钮的显示
  2. 前端表单验证:通过Form组件进行名称必填验证和格式验证
  3. 数据预填充:编辑模态框自动填充当前数据库的信息
  4. API调用:使用MemoryApi.UpdateDatabase API处理数据库更新
  5. 状态同步:更新成功后同步更新页面状态
  6. 用户体验优化:提供图标自动生成功能
    整个流程确保了数据库编辑的便捷性和用户体验的流畅性。

核心组件实现

组件层次结构

数据库编辑功能涉及多个层次的组件:

  1. LibraryPage组件:资源库主页面
  2. BaseLibraryPage组件:资源库核心逻辑
  3. DatabaseDetail组件:数据库详情页面,包含编辑功能
  4. BaseInfoModal组件:数据库基本信息编辑模态框
  5. useDatabaseConfig Hook:管理数据库在资源库中的配置状态

1. 资源库入口组件(LibraryPage)

文件位置:frontend/packages/studio/workspace/entry-adapter/src/pages/library/index.tsx

作为资源库的适配器组件,整合各种资源配置,包括数据库的展示和操作:

typescript 复制代码
import { type FC, useRef } from 'react';

import {
  BaseLibraryPage,
  useDatabaseConfig,
  usePluginConfig,
  useWorkflowConfig,
  usePromptConfig,
  useKnowledgeConfig,
} from '@coze-studio/workspace-base/library';

export const LibraryPage: FC<{ spaceId: string }> = ({ spaceId }) => {
  const basePageRef = useRef<{ reloadList: () => void }>(null);
  const configCommonParams = {
    spaceId,
    reloadList: () => {
      basePageRef.current?.reloadList();
    },
  };
  const { config: pluginConfig, modals: pluginModals } =
    usePluginConfig(configCommonParams);
  const { config: workflowConfig, modals: workflowModals } =
    useWorkflowConfig(configCommonParams);
  const { config: knowledgeConfig, modals: knowledgeModals } =
    useKnowledgeConfig(configCommonParams);
  const { config: promptConfig, modals: promptModals } =
    usePromptConfig(configCommonParams);
  // 数据库配置,包含数据库相关操作
  const { config: databaseConfig, modals: databaseModals } =
    useDatabaseConfig(configCommonParams);

  return (
    <>
      <BaseLibraryPage
        spaceId={spaceId}
        ref={basePageRef}
        entityConfigs={[
          pluginConfig,
          workflowConfig,
          knowledgeConfig,
          promptConfig,
          databaseConfig,
        ]}
      />
      {pluginModals}
      {workflowModals}
      {knowledgeModals}
      {promptModals}
      {databaseModals} {/* 包含创建数据库的模态框 */}
    </>
  );
};

设计亮点

  • 状态集中管理:通过统一的reloadList机制管理各资源类型的状态更新
  • 组件复用:BaseLibraryPage作为核心组件,通过配置系统支持多种资源类型

2. 资源库核心组件(BaseLibraryPage)

文件位置:frontend/packages/studio/workspace/entry-base/src/pages/library/index.tsx

负责资源库的核心展示逻辑,包括数据库列表的渲染和导航到详情页面的功能:

typescript 复制代码
import { forwardRef, useImperativeHandle } from 'react';

import classNames from 'classnames';
import { useInfiniteScroll } from 'ahooks';
import { I18n } from '@coze-arch/i18n';
import {
  Table,
  Select,
  Search,
  Layout,
  Cascader,
  Space,
} from '@coze-arch/coze-design';
import { renderHtmlTitle } from '@coze-arch/bot-utils';
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
import {
  type ResType,
  type LibraryResourceListRequest,
  type ResourceInfo,
} from '@coze-arch/idl/plugin_develop';
import { PluginDevelopApi } from '@coze-arch/bot-api';

import { type ListData, type BaseLibraryPageProps } from './types';
import { LibraryHeader } from './components/library-header';

export const BaseLibraryPage = forwardRef<
  { reloadList: () => void },
  BaseLibraryPageProps
>(
  ({ spaceId, isPersonalSpace = true, entityConfigs }, ref) => {
    const { params, setParams, resetParams, hasFilter, ready } =
      useCachedQueryParams({
        spaceId,
      });

    // 获取资源列表,包括数据库
    const listResp = useInfiniteScroll<ListData>(
      async prev => {
        if (!ready) {
          return {
            list: [],
            nextCursorId: undefined,
            hasMore: false,
          };
        }
        const resp = await PluginDevelopApi.LibraryResourceList(
          entityConfigs.reduce<LibraryResourceListRequest>(
            (res, config) => config.parseParams?.(res) ?? res,
            {
              ...params,
              cursor: prev?.nextCursorId,
              space_id: spaceId,
              size: LIBRARY_PAGE_SIZE,
            },
          ),
        );
        return {
          list: resp?.resource_list || [],
          nextCursorId: resp?.cursor,
          hasMore: !!resp?.has_more,
        };
      },
      {
        reloadDeps: [params, spaceId],
      },
    );

    useImperativeHandle(ref, () => ({
      reloadList: listResp.reload,
    }));

    return (
      <Layout
        className={s['layout-content']}
        title={renderHtmlTitle(I18n.t('navigation_workspace_library'))}
      >
        <Layout.Header className={classNames(s['layout-header'], 'pb-0')}>
          <div className="w-full">
            <LibraryHeader entityConfigs={entityConfigs} />
            {/* 过滤器组件 */}
          </div>
        </Layout.Header>
        <Layout.Content>
          {/* 表格和列表内容,点击数据库行可导航到详情页进行编辑 */}
        </Layout.Content>
      </Layout>
    );
  }
);

设计亮点

  • 无限滚动 :使用useInfiniteScroll实现资源列表的无限滚动加载
  • 配置化设计:通过entityConfigs配置支持多种资源类型的展示
  • 参数缓存 :使用useCachedQueryParams管理筛选和分页参数

3. 数据库配置Hook(useDatabaseConfig)

文件位置:frontend/packages/studio/workspace/entry-base/src/pages/library/hooks/use-entity-configs/use-database-config.tsx

管理数据库在资源库中的配置状态、列表渲染和操作权限:

typescript 复制代码
import { useNavigate } from 'react-router-dom';

import { useRequest } from 'ahooks';
import {
  ActionKey,
  type ResourceInfo,
  ResType,
} from '@coze-arch/idl/plugin_develop';
import { I18n } from '@coze-arch/i18n';
import { IconCozDatabase } from '@coze-arch/coze-design/icons';
import { Menu, Table, Toast } from '@coze-arch/coze-design';
import { MemoryApi } from '@coze-arch/bot-api';
import { useLibraryCreateDatabaseModal } from '@coze-data/database-v2';

import { type UseEntityConfigHook } from './types';

const { TableAction } = Table;

export const useDatabaseConfig: UseEntityConfigHook = ({
  spaceId,
  reloadList,
  getCommonActions,
}) => {
  const navigate = useNavigate();

  const {
    modal: createDatabaseModal,
    open: openCreateDatabaseModal,
    close: closeCreateDatabaseModal,
  } = useLibraryCreateDatabaseModal({
    enterFrom: 'library',
    onFinish: databaseID => {
      navigate(
        `/space/${spaceId}/database/${databaseID}?page_modal=normal&biz=create`,
      );
      closeCreateDatabaseModal();
    },
  });
  // delete action
  const { run: deleteDatabase } = useRequest(
    (databaseId: string) =>
      MemoryApi.DeleteDatabase({
        id: databaseId,
      }),
    {
      manual: true,
      onSuccess: () => {
        reloadList();
        Toast.success(I18n.t('Delete_success'));
      },
    },
  );

  return {
    modals: <>{createDatabaseModal}</>,
    config: {
      typeFilter: {
        label: I18n.t('new_db_001'),
        value: ResType.Database,
      },
      renderCreateMenu: () => (
        <Menu.Item
          data-testid="workspace.library.header.create.card"
          icon={<IconCozDatabase />}
          onClick={openCreateDatabaseModal}
        >
          {I18n.t('new_db_001')}
        </Menu.Item>
      ),
      target: [ResType.Database],
      // 点击数据库项导航到详情页,在详情页可以进行编辑操作
      onItemClick: (item: ResourceInfo) => {
        navigate(
          `/space/${spaceId}/database/${item.res_id}?page_mode=normal&from=library`,
        );
      },
      renderActions: (item: ResourceInfo) => {
        // Can it be deleted?
        const deleteDisabled = !item.actions?.find(
          action => action.key === ActionKey.Delete,
        )?.enable;
        // delete operation
        const deleteProps = {
          disabled: deleteDisabled,
          deleteDesc: I18n.t('library_delete_desc'),
          handler: () => {
            deleteDatabase(item.res_id || '');
          },
        };

        return (
          <TableAction
            deleteProps={deleteProps}
            actionList={getCommonActions?.(item)}
          />
        );
      },
    },
  };
};

设计亮点

  • 导航配置 :通过onItemClick定义数据库项的点击行为,导航到数据库详情页
  • 操作权限 :根据资源的actions配置动态显示删除等操作
  • 创建功能:集成了创建数据库的模态框功能

4. 数据库详情组件(DatabaseDetail)

文件位置:frontend/packages/data/memory/database-v2-main/src/components/database-detail/index.tsx

数据库详情页面的核心组件,包含基本信息展示和编辑功能:

typescript 复制代码
import React, { useState, useEffect, useMemo } from 'react';

import { pick } from 'lodash-es';
import classNames from 'classnames';
import { userStoreService } from '@coze-studio/user-store';
import { type DatabaseInfo as DatabaseInitInfo } from '@coze-studio/bot-detail-store';
import { type WidgetUIState } from '@coze-data/knowledge-stores';
import { BotE2e } from '@coze-data/e2e';
import { DatabaseTabs } from '@coze-data/database-v2-base/types';
import { DismissibleBanner } from '@coze-data/database-v2-base/components/dismissible-banner';
import {
  type FormData,
  ModalMode,
} from '@coze-data/database-v2-base/components/base-info-modal';
import { DatabaseModeSelect } from '@coze-data/database-v2-adapter/components/database-mode-select';
import { DatabaseCreateTableModal } from '@coze-data/database-v2-adapter/components/create-table-modal';
import { DatabaseBaseInfoModal } from '@coze-data/database-v2-adapter/components/base-info-modal';
import { DatabaseDetailWaring } from '@coze-data/database-v2-adapter';
import { I18n } from '@coze-arch/i18n';
import {
  IconCozEdit,
  IconCozCross,
  IconCozArrowLeft,
} from '@coze-arch/coze-design/icons';
import {
  Button,
  IconButton,
  TabBar,
  Toast,
  CozAvatar,
  Typography,
  Space,
} from '@coze-arch/coze-design';
import {
  BotTableRWMode,
  TableType,
  type DatabaseInfo,
  type UpdateDatabaseRequest,
} from '@coze-arch/bot-api/memory';
import { MemoryApi } from '@coze-arch/bot-api';

export const DatabaseDetail = ({
  version,
  enterFrom,
  initialTab,
  needHideCloseIcon = false,
  addRemoveButtonText,
  onClose,
  onClickAddRemoveButton,
  onIDECallback,
  onAfterEditBasicInfo,
  onAfterEditRecords,
  databaseId,
}: DatabaseDetailProps) => {
  const userId = userStoreService.useUserInfo()?.user_id_str;

  const [basicInfoVisible, setBasicInfoVisible] = useState(false);
  const [createTableVisible, setCreateTableVisible] = useState(false);
  // database basicInfo
  const [databaseInfo, setDatabaseInfo] = useState<DatabaseInfo>({});
  // tab key
  const [activeKey, setActiveKey] = useState(
    version ? DatabaseTabs.Structure : initialTab ?? DatabaseTabs.Structure,
  );
  // btn loading
  const [btnLoading, setBtnLoading] = useState(false);
  // page loading
  const [loading, setLoading] = useState(true);

  // fetch database basicInfo
  const fetchDatabaseInfo = async () => {
    try {
      setLoading(true);
      const response = await MemoryApi.GetDatabaseByID({
        id: databaseId,
        ...(version ? { version } : {}),
      });
      if (response.database_info) {
        setDatabaseInfo(response.database_info);
        if (response.database_info.table_name) {
          onIDECallback?.onUpdateDisplayName?.(response.database_info.table_name);
          onIDECallback?.onStatusChange?.('normal');
        }
      } else {
        onIDECallback?.onStatusChange?.('error');
      }
    } catch {
      onIDECallback?.onStatusChange?.('error');
    } finally {
      setLoading(false);
    }
  };

  // 权限控制:判断是否为只读模式
  const isReadOnlyMode = databaseInfo.creator_id !== userId || !!version;

  // 数据库基本信息编辑处理函数
  const handleEditBasicInfo = async (obj: UpdateDatabaseRequest) => {
    try {
      const response = await MemoryApi.UpdateDatabase({
        ...pick(databaseInfo, [
          'id',
          'icon_uri',
          'table_name',
          'table_desc',
          'field_list',
          'rw_mode',
          'prompt_disabled',
          'extra_info',
        ]),
        ...obj,
      });
      if (response?.database_info?.id) {
        await fetchDatabaseInfo();
        // update basicInfo callback
        if (onAfterEditBasicInfo) {
          onAfterEditBasicInfo();
        }
        // close basicInfo modal
        if (basicInfoVisible) {
          setBasicInfoVisible(false);
        }
      } else {
        Toast.error('Update database failed');
      }
    } catch (error) {
      Toast.error('Failed to update database: ' + (error instanceof Error ? error.message : String(error)));
    }
  };

  // 初始化数据
  const basicInitData: FormData = useMemo(
    () => ({
      name: databaseInfo.table_name || '',
      description: databaseInfo.table_desc || '',
      icon_uri: [
        {
          url: databaseInfo.icon_url || '',
          uri: databaseInfo.icon_uri || '',
          uid: databaseInfo.icon_uri || '',
          isDefault: true,
        },
      ],
    }),
    [databaseInfo],
  );

  useEffect(() => {
    fetchDatabaseInfo();
  }, []);

  return (
    <>
      <div
        className={classNames(
            'h-full w-full max-w-[100vw] flex flex-col overflow-hidden',
            enterFrom === 'project'
              ? 'coz-bg-max rounded-b-[8px] border-solid coz-stroke-primary'
              : 'coz-bg-plus',
          )}
        >
          {/* header - 包含数据库名称、描述和编辑按钮 */}
          <div
            className={classNames(
              'flex flex-row items-center justify-between shrink-0',
              enterFrom === 'library'
                ? 'h-[40px] m-[24px]'
                : 'h-[64px] px-[16px] py-[12px] border-0 border-b border-solid coz-stroke-primary',
          )}
        >
          <div className="flex items-center gap-[8px]">
            {/* 图标、名称和编辑按钮 */}
            <CozAvatar
              type="bot"
              color="grey"
              src={basicInitData.icon_uri?.[0]?.url}
            />
            <div className="flex flex-col">
              <div className="flex flex-row items-center gap-[2px] leading-none">
                <Typography.Text weight={500} fontSize="14px">
                  {basicInitData.name}
                </Typography.Text>
                {/* 根据权限控制显示编辑按钮 */}
                {isReadOnlyMode ? null : (
                  <IconButton
                    size="mini"
                    color="secondary"
                    icon={<IconCozEdit className="coz-fg-secondary" />}
                    onClick={() => setBasicInfoVisible(true)}
                  />
                )}
              </div>
              <Typography.Text fontSize="12px">
                {basicInitData.description}
              </Typography.Text>
            </div>
          </div>
          {/* 读写模式切换 */}
        </div>
        
        {/* 基本信息编辑模态框 */}
        <DatabaseBaseInfoModal
          visible={basicInfoVisible}
          mode={ModalMode.EDIT}
          initValues={basicInitData}
          onClose={() => setBasicInfoVisible(false)}
          onSubmit={handleEditBasicInfo}
        />
      </div>
    </>
  );
};

设计亮点

  • 权限控制 :通过isReadOnlyMode状态判断用户是否有权限编辑数据库
  • 数据同步 :编辑完成后通过fetchDatabaseInfo重新获取数据,确保状态一致性
  • 状态管理:使用多个state管理模态框显示、加载状态等
  • 组件化:将编辑功能封装在独立的模态框组件中

5. 数据库基本信息编辑模态框(DatabaseBaseInfoModal)

文件位置:frontend/packages/data/memory/database-v2-base/src/components/base-info-modal/index.tsx

实现数据库基本信息编辑的核心模态框组件:

typescript 复制代码
import { useState, type FC, useRef, useEffect, useCallback } from 'react';

import { CozeFormTextArea, CozeInputWithCountField } from '@coze-data/utils';
import {
  PictureUpload,
  type RenderAutoGenerateParams,
} from '@coze-common/biz-components/picture-upload';
import { I18n } from '@coze-arch/i18n';
import { Form, type FormApi, Modal } from '@coze-arch/coze-design';
import { FormatType } from '@coze-arch/bot-api/memory';
import { FileBizType, IconType } from '@coze-arch/bot-api/developer_api';
import { KnowledgeApi } from '@coze-arch/bot-api';

interface DatabaseBaseInfoModalProps {
  visible: boolean;
  mode: ModalMode;
  initValues?: FormData;
  onClose: () => void;
  onSubmit: (data: any) => void;
  renderAutoGenerate?: (params: RenderAutoGenerateParams) => React.ReactNode;
}

export enum ModalMode {
  CREATE = 'create',
  EDIT = 'edit',
}

export interface FormData {
  name: string;
  description: string;
  icon_uri?: Array<{
    url: string;
    uri: string;
    uid?: string;
    isDefault?: boolean;
  }>;
}

export const DatabaseBaseInfoModal: FC<DatabaseBaseInfoModalProps> = ({
  visible,
  initValues,
  onClose,
  onSubmit,
  mode,
  renderAutoGenerate,
}) => {
  const formRef = useRef<FormApi<FormData> | null>(null);
  const [coverIcon, setCoverIcon] = useState<{ uri: string; url: string }>({ uri: '', url: '' });
  const [iconInfoGenerate, setIconInfoGenerate] = useState<{ name: string; desc: string }>({ name: '', desc: '' });

  // 提交表单处理
  const handleSubmit = async () => {
    if (!formRef.current) {
      return;
    }
    try {
      const formData = await formRef.current.validate();

      onSubmit({
        ...formData,
        icon_uri: [
          {
            url: formData?.icon_uri?.[0]?.url ?? '',
            uri: formData?.icon_uri?.[0]?.uid ?? '',
          },
        ],
      });
    } catch (validationError) {
      // Form validation failed, errors will be displayed by the Form component
      console.error('Form validation failed:', validationError);
    }
  };

  // 处理关闭
  const handleClose = () => {
    onClose();
  };

  // 设置默认图标
  const setDefaultIcon = async () => {
    try {
      const { icon } = await KnowledgeApi.GetIcon({
        format_type: FormatType.Database,
      });
      setCoverIcon({
        uri: icon?.uri ?? '',
        url: icon?.url ?? '',
      });
      formRef.current?.setValue('icon_uri', [
        {
          url: icon?.url ?? '',
          uri: icon?.uri ?? '',
          uid: icon?.uri ?? '',
          isDefault: true,
        },
      ]);
    } catch (error) {
      console.error('Failed to set default icon:', error);
    }
  };

  // 初始化表单数据
  const initForm = useCallback(
    ({ name, description, icon_uri }: FormData) => {
      if (!formRef.current) {
        return;
      }
      formRef.current.setValue('name', name);
      formRef.current.setValue('description', description);
      setIconInfoGenerate({
        name: name ?? '',
        desc: description ?? '',
      });
      if (!icon_uri || !icon_uri[0]?.url) {
        setDefaultIcon();
        return;
      }
      formRef.current.setValue('icon_uri', [
        {
          url: icon_uri[0].url,
          uri: icon_uri[0].uri,
          uid: icon_uri[0].uri,
          isDefault: true,
        },
      ]);
    },
    [formRef],
  );

  // 当模态框显示时初始化表单
  useEffect(() => {
    if (!visible) {
      return;
    }
    if (!initValues) {
      return;
    }
    initForm(initValues);
  }, [visible, initValues, initForm]);

  return (
    <Modal
      title={I18n.t(mode === ModalMode.CREATE ? 'new_db_001' : 'new_db_003')}
      open={visible}
      onCancel={handleClose}
      onOk={handleSubmit}
    >
      {/* 表单内容:包含名称、描述和图标上传 */}
      <Form ref={formRef} layout="vertical">
        {/* 名称输入字段 */}
        <Form.Item
          label={I18n.t('new_db_004')}
          name="name"
          rules={[
            { required: true, message: I18n.t('new_db_009') },
            { max: 100, message: I18n.t('new_db_010') },
            { pattern: /^[^"]*$/, message: I18n.t('database_name_cannot_contain_special_characters') },
          ]}
        >
          <CozeInputWithCountField
            placeholder={I18n.t('new_db_011')}
            maxLength={100}
          />
        </Form.Item>

        {/* 描述输入字段 */}
        <Form.Item
          label={I18n.t('new_db_005')}
          name="description"
          rules={[
            { max: 500, message: I18n.t('new_db_012') },
          ]}
        >
          <CozeFormTextArea
            placeholder={I18n.t('new_db_013')}
            maxLength={500}
            rows={4}
          />
        </Form.Item>

        {/* 图标上传 */}
        <Form.Item
          label={I18n.t('new_db_006')}
          name="icon_uri"
        >
          <PictureUpload
            type={IconType.Database}
            renderAutoGenerate={renderAutoGenerate}
            bizType={FileBizType.DATABASE_ICON}
          />
        </Form.Item>
      </Form>
    </Modal>
  );
};
        // 数据库编辑模态框的完整实现已在前面章节展示

设计亮点

  1. 权限控制:通过参数配置确保只有有权限的用户才能编辑知识库
  2. 表单验证:完善的编辑表单验证逻辑,包括必填项验证和格式验证
  3. 图标上传:支持自定义图标上传功能,提升知识库识别度
  4. 状态管理:清晰的加载状态和错误处理机制
  5. API交互:封装了知识库详情获取和更新的API调用逻辑
  6. 错误处理:统一的错误捕获和用户反馈机制
  7. 用户体验优化:表单预填充和操作成功提示
相关推荐
勤奋菲菲3 小时前
Koa.js 完全指南:下一代 Node.js Web 框架
前端·javascript·node.js
wei_shuo3 小时前
KingbaseES聚焦产品上线
数据库·kingbasees
晒太阳5793 小时前
懒加载与按需加载
前端
10年前端老司机3 小时前
面试官爱问的 Object.defineProperty,90%的人倒在这些细节上!
前端·javascript
庞囧3 小时前
从输入 URL 到开始解析 HTML 之间:浏览器背后发生了什么
前端
少年阿闯~~3 小时前
解决HTML塌陷的方法
前端·html
AI浩3 小时前
Redis中的RPOP、BRPOP、LPOP 和 BLPOP
数据库·chrome·redis
数据和云4 小时前
从Databricks和Supabase看AI时代的中国数据库启示
数据库·人工智能
我科绝伦(Huanhuan Zhou)4 小时前
Oracle ADRCI工具全面使用指南:从基础到故障诊断实战
数据库·oracle