当前位置: 首页 > news >正文

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

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

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 (<><BaseLibraryPagespaceId={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

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

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 (<LayoutclassName={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

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

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 actionconst { 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.Itemdata-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 operationconst deleteProps = {disabled: deleteDisabled,deleteDesc: I18n.t('library_delete_desc'),handler: () => {deleteDatabase(item.res_id || '');},};return (<TableActiondeleteProps={deleteProps}actionList={getCommonActions?.(item)}/>);},},};
};

设计亮点

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

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

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

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

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 basicInfoconst [databaseInfo, setDatabaseInfo] = useState<DatabaseInfo>({});// tab keyconst [activeKey, setActiveKey] = useState(version ? DatabaseTabs.Structure : initialTab ?? DatabaseTabs.Structure,);// btn loadingconst [btnLoading, setBtnLoading] = useState(false);// page loadingconst [loading, setLoading] = useState(true);// fetch database basicInfoconst 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 callbackif (onAfterEditBasicInfo) {onAfterEditBasicInfo();}// close basicInfo modalif (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 (<><divclassName={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 - 包含数据库名称、描述和编辑按钮 */}<divclassName={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]">{/* 图标、名称和编辑按钮 */}<CozAvatartype="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 : (<IconButtonsize="mini"color="secondary"icon={<IconCozEdit className="coz-fg-secondary" />}onClick={() => setBasicInfoVisible(true)}/>)}</div><Typography.Text fontSize="12px">{basicInitData.description}</Typography.Text></div></div>{/* 读写模式切换 */}</div>{/* 基本信息编辑模态框 */}<DatabaseBaseInfoModalvisible={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

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

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 componentconsole.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 (<Modaltitle={I18n.t(mode === ModalMode.CREATE ? 'new_db_001' : 'new_db_003')}open={visible}onCancel={handleClose}onOk={handleSubmit}>{/* 表单内容:包含名称、描述和图标上传 */}<Form ref={formRef} layout="vertical">{/* 名称输入字段 */}<Form.Itemlabel={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') },]}><CozeInputWithCountFieldplaceholder={I18n.t('new_db_011')}maxLength={100}/></Form.Item>{/* 描述输入字段 */}<Form.Itemlabel={I18n.t('new_db_005')}name="description"rules={[{ max: 500, message: I18n.t('new_db_012') },]}><CozeFormTextAreaplaceholder={I18n.t('new_db_013')}maxLength={500}rows={4}/></Form.Item>{/* 图标上传 */}<Form.Itemlabel={I18n.t('new_db_006')}name="icon_uri"><PictureUploadtype={IconType.Database}renderAutoGenerate={renderAutoGenerate}bizType={FileBizType.DATABASE_ICON}/></Form.Item></Form></Modal>);
};// 数据库编辑模态框的完整实现已在前面章节展示

设计亮点

  1. 权限控制:通过参数配置确保只有有权限的用户才能编辑知识库
  2. 表单验证:完善的编辑表单验证逻辑,包括必填项验证和格式验证
  3. 图标上传:支持自定义图标上传功能,提升知识库识别度
  4. 状态管理:清晰的加载状态和错误处理机制
  5. API交互:封装了知识库详情获取和更新的API调用逻辑
  6. 错误处理:统一的错误捕获和用户反馈机制
  7. 用户体验优化:表单预填充和操作成功提示
http://www.dtcms.com/a/471437.html

相关文章:

  • 如何创建一个论坛网站海南省交通建设局网站首页
  • 2025年生物学、农业与污染控制技术国际会议(BAPCT 2025)
  • 【全志V821_FoxPi】6-3 GC2083 MIPI摄像头适配
  • 东莞做网站哪家最好营销与销售的区别
  • 动态效果的网站建设技术网站开发技术分享ppt
  • 建设网站服务器选择html在线编辑器网页手机
  • 网站开发毕业设计开题报告跨境电商开发公司
  • 遂平县网站建设网页后台设计师工资一般多少
  • URL 设计
  • 档案网站的建设企业网站建立流程
  • 【OpenGauss】知识总结
  • 海淀教育人才网站网站系统正在升级维护
  • 各种不同光谱工业相机的特性的详细介绍
  • Java学习之旅第二季-21:记录
  • 自己建设网站怎么被百度收入外国人做网站
  • 如何查询网站是谁做的做淘客网站怎么
  • 工业自动化通信控制
  • NetworkPolicy详解
  • 郑州网站建设行情wordpress网站第一次打开慢
  • Python多进程编程核心组件详解:Event、Queue与进程生命周期管理
  • 真空共晶贴装技术
  • 添加SystemProperties的4种方法
  • 汕头建站平台免费推广网站入口2023燕
  • 深圳做棋牌网站建设有哪些公司海阳建设局网站
  • 网站优化大赛做电子商务网站需要什么软件
  • 重庆网站建设外贸加盟建筑公司办分公司
  • 用 “按位统计” 找唯一出现少于 3 次的数
  • 【解决】FAILED TO lOAD IDLINUX.c32
  • 去重表格的几种思路
  • 网站美工做的是什么合肥外贸网站建设公司排名