
Sim 设置页开发指南基于 SettingsPanel 设计系统的新增、审计与资源行迁移【免费下载链接】simSim is the collaborative workspace to build, deploy, and monitor AI agents and workflows. Used by 100,000 builders.项目地址: https://gitcode.com/GitHub_Trending/sim16/sim设置页是 Sim 工作台中承载偏好、密钥、成员、计费等配置能力的高频入口。本指南以仓库内 add-settings-page 技能文档 为骨架结合其依赖的 sim-settings-pages 规范 与真实源码完整讲解 Sim 设置页的架构约定、SettingsPanel注册机制以及三条实战操作路径Mode A 新增设置页、Mode B 审计并迁移既有页面、Mode C 将列表行迁移到SettingsResourceRow。读完你将掌握如何在 Sim 代码库中零散装地新增一个符合设计系统的设置 Tab如何用几条 grep 命令找出所有偏离规范的页面以及如何在不改动业务逻辑的前提下完成纯结构性迁移。一、架构总览Chrome 与 Body 的强制分离Sim 设置页的核心设计原则是Sections 只提供数据永远不提供 Chrome。所有页面外壳header bar、滚动区域、内容列、标题块都不是由页面自己渲染的而是由 Next.js 路由层的settings/[section]/layout.tsx统一持有Chrome 外壳apps/sim/app/workspace/[workspaceId]/settings/[section]/layout.tsx中的SettingsHeaderShell包含左侧返回 Chip、右侧操作 Chip 的固定 header bar、滚动区域以及一个居中max-w-[48rem]的内容列列首是来自导航元数据的标题 描述Chrome 常驻外壳在 section 之间切换时保持挂载路由标题与描述在 section body 尚未加载时就已可用见下文 layout 的解析逻辑SettingsPanel注册器每个 section 通过SettingsPanel渲染它把页面的 header 数据actions、search、back注册给外壳自身只渲染 body。真实实现位于 settings-panel.tsxSettingsPanel从SettingsSectionContext读取meta缺省时通过getSettingsSectionMeta(plane, section)解析然后调用useSettingsHeader({ title, description, docsLink, back, actions, search, scrollContainerRef })完成注册最后只返回{children}。页面代码中的引入路径/app/workspace/[workspaceId]/settings/components/settings-panel见 路由树 settings-panel 目录与components/settings/下的共享实现一一对应与 navigation.ts 的 re-export shim 模式 相同。关键路径速查表职责路径Chrome 外壳SettingsHeaderShellapps/sim/app/workspace/[workspaceId]/settings/[section]/layout.tsxSettingsPanel注册器apps/sim/components/settings/settings-panel.tsx导航元数据标题 描述apps/sim/components/settings/navigation.tsSection 切换 Providerapps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx设置页组件apps/sim/app/workspace/[workspaceId]/settings/components/name/name.tsx企业版页面apps/sim/ee/feature/components/外壳层还承担了两个不显而易见的路由职责见 layout.tsx 源码顶层重定向integrations、skills等已提升为独立路由的 section以及迁入 General 的privacy通过TOP_LEVEL_REDIRECTS做段级 307 重定向旧链接和书签依然可用404 判定上移是否是一个合法的 section 由 layout 在loading.tsx的 Suspense 边界之上判定resolveSettingsSection否则直接加载坏 URL 时会先答 200 再二次跳转。不要在设置页手写的五样东西外壳通过SettingsPanel供给拥有以下全部实现设置页内严禁手写div classNameflex h-full flex-col bg-[var(--bg)]外壳header bar —— 使用PAGE_HEADER_BARapps/sim/components/page-header-bar.tsx永远不要重写它的 padding滚动容器min-h-0 flex-1 overflow-y-auto px-6 [scrollbar-gutter:stable_both-edges]内容列mx-auto … max-w-[48rem] … gap-7标题块h1 classNamefont-medium text-[var(--text-body)] text-lgp classNametext-[var(--text-muted)] text-md以及页面级搜索框。二、SettingsPanel的规范页面形态与 Props规范页面形态任何设置页的主返回必须是SettingsPanelbody 里只放 sections、列表、表单import { SettingsPanel } from /app/workspace/[workspaceId]/settings/components/settings-panel return ( SettingsPanel actions{[{ text: Create, icon: Plus, variant: primary, onSelect: onCreate }]} search{{ value: searchTerm, onChange: setSearchTerm, placeholder: Search … }} {/* body only — sections, lists, forms */} /SettingsPanel )页面带 Modal/Dialog 兄弟节点时用 Fragment 把面板与弹窗包在一起return ( SettingsPanel actions{…}{body}/SettingsPanel SomeModal … / / )Props 契约SettingsPanel的完整 Props 定义见 settings-panel.tsx类型层面用判别联合强制了back与title/description的配对关系有back才能传title/descriptionactions?: SettingsAction[]右对齐的 header 操作 Chip纯数据。{ id?, text, textTone?: error, icon?, variant?: primary|destructive, active?, onSelect, onPrefetch?, disabled?, tooltip? }。外壳把每个 action 渲染成一个Chip——永远不要传 JSX、div或className这个锁定契约使凭感觉改 padding在结构上不可能。多/条件操作是普通数组展开[...(canManage ? [{…}] : []), …]。文案用句首小写Add override而非Add Override。需要解释自己的禁用操作设置tooltip想悬停预热路由的操作设置onPrefetch标签会在 pending 时翻转Delete→Deleting...的操作必须设稳定的id否则 Chip 会在操作中途重挂载。Save/Discard 对由saveDiscardActions()helper 产出后展开进actions。back?: SettingsBackAction{ text, icon?, onSelect }详情子视图如选中的 MCP server、权限组、保留策略的左对齐返回 Chip。详情子视图和列表页一样走SettingsPanel不手写自己的外壳。docsLink?: string渲染 header 里的DocsChipLink。search?: { value; onChange; placeholder?; disabled? }在标题正下方渲染规范搜索框直接把setSearchTerm传给onChange。独立搜索才用它如果搜索与其他控件排序、筛选、日期选择器共用一行就把整行放进children并省略此 prop。title?/description?仅用于需要不同标题的详情子视图普通页面永远不传。顶层页的 header 身份在数据加载期间必须保持稳定永远不要用客户端拉取的数据替换首帧后的导航元数据把数据相关上下文放进页面 body。scrollContainerRef?: React.RefHTMLDivElement把 ref 转发给滚动区域如编程式滚到底部。三、标题与描述住在导航元数据里apps/sim/components/settings/navigation.ts是标题/描述的唯一事实来源路由树中的 settings/navigation.ts 只是 re-export shim。每个NavigationItem都带一行descriptionSettingsPanel通过getSettingsSectionMeta(plane, section)解析二者settings 外壳用SettingsSectionProvider包住当前 section。从源码看navigation.tssection 的并集类型按平面拆分Accountgeneral | billing | api-keys | admin | mothershipSelfHostgeneral | billing | chat-keysOrganizationmembers | billing | usage | access-control | audit-logs | sso | sessions |>cd apps/sim bun run type-check bunx biome check --write file五、文本刻度 Token设置页禁止字面像素字号设置页永远不使用字面text-[Npx]class必须用apps/sim/app/_styles/globals.css的theme块中命名的 Tailwind 刻度 token。完整映射Token像素说明text-micro10pxtext-xs11px注意陷阱此处非 Tailwind 默认 12pxcaption 用text-xs会矮 1pxtext-caption12pxtext-small13pxtext-sm14pxTailwind 默认未修改text-base15pxtext-md16pxtext-lg18pxTailwind 默认字面字号要么是等价 token 的直改名像素值恰好匹配时要么是页面从未迁移的信号。用git grep -nE text-\[1[0-8]px\]在apps/sim/app/workspace/*/settings/**与apps/sim/ee/**下抓漏网之鱼。超出刻度范围的展示级字号是有意为之、不在审计范围内——如text-[40px]的 hero 标题、text-[8px]的成员头像首字母。两行列表行与切换行标题在上、灰副标题在下的两行列表行name email、tool 名 描述、server 名 状态不是用来手搓的它就是SettingsResourceRow由它统一持有配对text-[var(--text-body)] text-sm压在text-[var(--text-muted)] text-caption上。切换行Switch 标题 可选描述的标题必须用 emcn 的Label组件配Switch的id/Label的htmlFordiv classNameflex items-center justify-between div classNameflex flex-col gap-1 Label htmlFormy-toggleEnable thing/Label p classNametext-[var(--text-muted)] text-captionOne-line description./p /div Switch idmy-toggle checked{enabled} onCheckedChange{onToggle} / /divLabel自带样式font-medium text-[var(--text-primary)] text-small已匹配既定标题处理不要加className覆盖其字号/颜色。六、SettingsResourceRow每一个资源列表行的唯一实现SettingsResourceRowsettings-resource-row.tsx是所有设置资源的列表行——包括 skills、integrations 和ee/表面。它拥有tile、标题/副标题 token、行 padding 与出血-mx-2 … rounded-lg p-2、命中区域、焦点环、导航 chevron以及仅可激活行hover 带。永远不要手搓其中任何部分也不要用自己的button/Link包住行——这正是onClick/href存在的意义trailing持有控件时包裹行还是非法 HTML。div className{RESOURCE_LIST_STACK} {items.map((item) ( SettingsResourceRow key{item.id} icon{Wrench classNametext-[var(--text-icon)] /} iconFilled title{item.name} description{item.summary} onClick{() open(item.id)} // or href{…/${item.id}} clickLabel{Open ${item.name}} navigable / ))} /div关键 Prop 语义icon?iconVarianttile默认36px 带边框 tile、plain裸 14px 字形、custom自带整个 tile如品牌色调的IntegrationTile。无身份字形的资源API key、权限组直接省略icon。iconFilled使用 skills/tools 填充iconFill让上传图片铺满 tile 边缘。tile 会把其中的svg强制归一化到 20pximg在iconFill时除外调用方传原始图标节点即可、无需预置尺寸。onClick/href通过一个拉伸遮罩让整行可激活。目标是路由时优先href让行保留 prefetch、中键点击、新标签打开等真链接语义。二者都必须配clickLabel——遮罩上没有文字它是控件唯一可访问名称该 prop 在类型里是可选的漏掉会发布一个无名按钮而不是构建失败。navigable追加唯一的规范 chevron。打开详情页的行才设置onClick原地动作如展开文件夹时不要设置。永远不要自己引入箭头lucide-react与sim/emcn/icons的字形肉眼可见地不同行已经选好了。被批准的特制行用同模块导出的RESOURCE_ROW_ARROW_CLASSES自画箭头。trailingvsbadgetrailing放可交互控件Chip、RowActionsMenu位于命中区域之上badge放装饰状态标签点击穿透。放反了会把行的右缘变成死区。RESOURCE_LIST_STACK/RESOURCE_LIST_GRID单列与两列容器。SettingsResourceRow自带-mx-2出血与 padding容器只负责节奏永远不要再加第二个-mx-2会叠成 16px 出血或不同 gap。行在固定高度overflow-y-auto盒内、或标题需与下方 section 标签对齐时传flush去掉出血与 padding。RESOURCE_LIST_GRID的列 gap 是为出血预算好的24px track gap − 16px 出血 8px 沟槽收窄会让相邻行的拉伸命中区域重叠点击沟槽会打开错误的卡片。三点菜单 vs Chevron不是审美问题打开详情页→navigable 整行点击行内不放 Delete它住在详情 header 里没有详情页→trailing里放RowActionsMenu不设navigable。行仍可带onClick做原地动作文件夹挂载既在 Finder 中展开又带...菜单但一行永远不能同时有 chevron 和菜单。RowActionsMenuapps/sim/app/workspace/[workspaceId]/settings/components/row-actions-menu/传labelaria-label与actions: RowAction[]{ label, onSelect, destructive?, disabled? }渲染规范的 flush...触发器 DropdownMenuContent条件项用数组展开...(canManage ? [{…}] : [])永远不要每页手写DropdownMenuMoreHorizontal。七、Save / Discard 与未保存更改守卫唯一共享栈任何带可编辑状态的设置表面都用同一个共享栈绝不手写 Save 按钮、Discard 按钮、beforeunload或未保存更改弹窗saveDiscardActions(config)save-discard-actions.tsx返回规范的Discard SaveSettingsAction[]。两条铁律Save 永远渲染primary在无可保存内容前保持禁用——这样每个可编辑表面都在同一位置宣告主操作创建表单永远不会成为看不见提交方式的页面Discard 只在 dirty 时出现。配置项dirty、saving、onSave、onDiscard、saveDisabled?、saveTooltip?、creating?、saveLabel?、savingLabel?。创建流传creating——Create/Creating... 标签作为一对出现、永不错位saveLabel/savingLabel只给真正特殊的措辞如 SSO 的Update不要为了拿创建标签而手写这对。SaveDiscardChips {...config} /同模块把同一规则渲染成 Chip供 header 收ReactNode的表面CredentialDetailLayoutskills、secrets、已连接凭据使用。两个栈都由同一个函数派生永远不要手写 Save Chip。CredentialDetailLayout保持插槽驱动有且只有两个原因它的返回控件是真ChipLink href可深链/中键点击SettingsBackAction的onSelect表达不了且SkillImportButton这类 action 自带隐藏文件输入与自己的 pending 状态。其余 header 内容都应是SettingsAction数据经SettingsActionChipsapps/sim/components/settings/settings-header.tsx渲染——这是共享 Chip 路径保证两套外壳的 tone/icon/variant/tooltip 处理不漂移。useSettingsUnsavedGuard({ isDirty })apps/sim/app/workspace/[workspaceId]/settings/hooks/use-settings-unsaved-guard.ts把页面局部isDirty同步进共享useSettingsDirtyStore侧边栏切 section 确认与集中式beforeunload因此免费生效并返回{ showUnsavedModal, setShowUnsavedModal, guardBack, confirmDiscard }供详情视图的页内返回 Chip使用顶层页whitelabeling、sso不赋值调用useSettingsUnsavedGuard({ isDirty: hasChanges })只要 store 同步侧边栏/beforeunload做其余工作详情子视图data-retention、access-control group-detail返回 Chip 走onClick{() guard.guardBack(closeFn)}并渲染共享UnsavedChangesModal open{guard.showUnsavedModal} onOpenChange{guard.setShowUnsavedModal} onDiscard{guard.confirmDiscard} /。页内 header 的DiscardChip经SaveDiscardActions onDiscard是重置回原始值——与返回确认的 discard离开页面是两回事。useSettingsBeforeUnload由 settings 外壳挂载settings/layout.tsx与components/settings/standalone-settings-shell.tsx永远不要在页面里加beforeunload。三条硬性约束Dirty 的计算留在局部各页面形态不同字段比较 vs normalizestringify共享的只是 dirty 的消费方式用const/useMemo派生它不要存进useState关键rules of hooksuseSettingsUnsavedGuard(...)必须无条件地、在任何 early-return 门之前调用entitlement / loading / not-entitled 的return SettingsEmptyState。门之后放置的 hook 会在门控渲染时被跳过而崩溃基于路由的凭据详情settings/secrets/[credentialId]保留自己的useUnsavedChangesGuard它通过 history sentinel 守卫真router.push导航与浏览器返回但已共享UnsavedChangesModal文案保持统一。八、Mode B —— 审计既有设置页对每个页面组件用规范清单sim-settings-pages.md逐项核对1. 找出应改为SettingsPanel的手写外壳——grep 出的每个匹配要么是settings-panel.tsx本身要么是详情子视图带Chip leftIcon{ArrowLeft}返回按钮要么是 entitlement/loading 的门控early-return除此之外都是待迁移页面git grep -n flex h-full flex-col bg-\[var(--bg)\] -- apps/sim/**/settings/ apps/sim/ee/2. 找出手写标题块详情视图外应为 0git grep -n text-\[var(--text-body)\] text-lg -- apps/sim/**/settings/ apps/sim/ee/3. 找出字面像素字号应为 0刻度映射见第五节git grep -nE text-\[1[0-8]px\] -- apps/sim/**/settings/ apps/sim/ee/4. 确认每页导入SettingsPanel且其NavigationItem的description与同级长度一致。可编辑页面确认 Save/Discard 走SaveDiscardActions、dirty 经useSettingsUnsavedGuard接入且在 early-return 门之前调用标记任何手写 Save 按钮、beforeunload或未保存弹窗git grep -n beforeunload -- apps/sim/**/settings/ apps/sim/ee/此命令只应命中集中式 use-settings-before-unload.ts。迁移的纪律只改结构迁移页面时只做外壳 →SettingsPanel的结构性替换header Chip 移进actions、独立搜索移进search、删除h1标题块、把收尾的三个/divcolumn/scroll/shell换成/SettingsPanel、Modal 兄弟节点留在Fragment。不要碰handlers、state、queries、条件渲染或 detail/gate 返回。内容列上页面自带的gap-*/pt-*一律去掉、改用面板默认值。修字面像素字号时只替换size class 为其像素等价的命名 token如text-[12px]→text-caption绝不用不同字号——必须像素级渲染一致而不是重排页面。颜色 token--text-primaryvs--text-body等除非是无关的刻意变更否则保持不动。删除不再使用的导入ChipInput/Search之前先 grep 确认文件其他位置如某个详情视图没有继续使用。整体验证bun run type-check、对每个触碰文件跑biome check并运行受影响页面的测试把每个文件与基线 diff确认改动纯粹是结构性的再合入。九、Mode C —— 把列表行迁移到SettingsResourceRow先读规范文档中 The resource row 一节即第六节的内容这就是契约然后逐页执行1. 找手写行git grep -n truncate text-\[var(--text-body)\] text-sm -- apps/sim/app/workspace/ apps/sim/ee/settings-resource-row.tsx之外的每个匹配要么是待迁移的行要么是真正不同形态多行 body、表格列、网格而保持特制的行——需要做出判断并说明。2. 替换行和它的包裹层包在行外的button/Link变成行自身的onClick/href。包裹行正是该 primitive 存在要阻止的事——trailing持有控件时它也是非法 HTML。3. 归类 trailing 内容可交互 →trailing装饰 →badge。放反了会使行右缘成为死区。4. 行打开详情页才加navigableclickLabel永远要加。5. 去掉容器的-mx-2——行现在自己持有出血用RESOURCE_LIST_STACK/RESOURCE_LIST_GRID不要手写 gap。6. 与 Mode B 不同此迁移可能改变条件渲染button disabled{!can}变成onClick{can ? … : undefined}navigable{can}渲染为纯非交互行。验证门控状态不再有任何可点击的 affordance。7. 检查旧行在标题旁渲染了什么badge、时间戳、传输标签。行的标题作为一个整体截断折叠进标题的东西可能被省略号吃掉——移到description或badge。8. 验证bun run type-check、biome check、页面测试并 diff 阅读每个转换块确认没有丢失 props、条件与key位置。十、Header 操作顺序与删除语义顺序不需要你操心但要知道规则每个详情 header 从左到右读← Back [secondary actions] → Delete → Discard → Save你不需要自己把数组排对——orderHeaderActions()会排序secondary →id:delete→id:discard→variant:primary各档内保序所以先展开saveDiscardActions()依然会渲染出 Save 在最后。三处应用此排序SettingsHeaderShell、SettingsActionChips、Resource.Header——因此 tables、files、knowledge、logs 与 settings 得到相同顺序。Delete 靠id而非位置摆放这正是id:delete是必需而非装饰的原因没有主操作的页面也必须让破坏性 Chip 离开主操作本应占据的槽位。bar 几何与操作簇都单一来源于apps/sim/components/page-header-bar.tsxPAGE_HEADER_BAR/Resource.Header的带边框变体、HEADER_ACTION_CLUSTER永远不要在每个 header 里重新推导h-[30px]、gap-1或 lane padding。该行为由settings-header-order.test.ts与settings-header-shell.test.tsx覆盖——后者钉死了重排后的 Chip 仍路由到自己的 handler。两个值得知道的推论主 Chip 永远在最右且不一定是 Save无保存状态的页面上它就是主操作Add workflows、ImportDelete 仍排在它前面CredentialDetailLayout收ReactNode你直接写的 Chip 保持你的顺序——只有经SettingsActionChips/SaveDiscardChips路由的才被排序。SaveDiscardChips要放最后skills、secrets、已连接凭据已如此。删除资源普通 Chip 确认弹窗Delete 住在详情 header形态是{ id: delete, text: Delete, onSelect: … }背后跟一个ChipConfirmModal——普通 Chip不用textTone: error也从不免确认。在SettingsPanelheader 里它是 action数据绝不手写Chip只有收ReactNode的CredentialDetailLayout表面直接渲染一个。始终设id: delete——没有稳定 id标签翻到Deleting...时 Chip 会重挂载。variant: destructive保留给规模级破坏操作——Delete all密码、Clear all浏览数据、Sign out allmembers。删除你正在看的单个资源由弹窗确认所以不需要红色 Chip。列表行在资源有详情页时不携带 Delete。十一、详情子视图与其它共享 primitive详情子视图从列表行下钻选中的 MCP server、workflow MCP server、权限组、保留策略像列表页一样走SettingsPanel传back{{ text, icon: ArrowLeft, onSelect }}给左侧返回 Chip、title实体名、headeractions然后渲染 body。不要手写外壳或 header bartab bar 作为第一个 body 子节点渲染。门控/early-return 状态not-entitled、loading、升级提示保持原样。路由式凭据详情settings/secrets/[credentialId]是唯一例外——它在[section]之外保留自己的CredentialDetailLayout。其余不要重复造轮的共享 primitiveSettingsSectionapps/sim/app/workspace/[workspaceId]/settings/components/settings-section/此目录无 barrel灰色 label 发丝分隔线 body还带headerAccessory与action插槽SettingsField…/components/settings-field详情 body 里的只读 label/value 对灰 caption 压在 value 上value 文本配SETTINGS_FIELD_VALUE_CLASSESSettingsEmptyState…/components/settings-empty-state规范的空态信息用于空列表、无结果、loading 门以及加载失败toneerror。variantfill默认在可用高度内居中variantinline在流内。永远不要手写div classNameflex h-full items-center justify-center …或div classNamepy-4 text-center …RESOURCE_TILE_BASERESOURCE_TILE_FILL/RESOURCE_TILE_PLAINapps/sim/app/workspace/[workspaceId]/components/resource-tile注意不在settings/下36px tile chrome用于行不自己画 tile 的场景详情标题、iconVariantcustomResourceTile包装填充配对RESOURCE_TILE_FILL用于字形、RESOURCE_TILE_PLAIN用于品牌 logo/favicon。十二、验收清单与验证一个设置页达到设计系统洁净的标准即 sim-settings-pages.md 的 Audit checklist主返回是SettingsPanel或带 Modal 兄弟的…SettingsPanel…/无手写 shell/header/scroll/column不渲染手写h1/描述标题块——标题来自导航元数据header Chip 在actions独立搜索在searchpropNavigationItem有准确、长度一致的description详情子视图与 entitlement/loading 门保留自己的 chrome有意为之有可编辑状态时Save/Discard 走SaveDiscardActionsdirty 经useSettingsUnsavedGuard接入在任何 early-return 门之前调用且没有手写 Save 按钮 /beforeunload/ 未保存更改弹窗迁移未改动业务逻辑、handlers 或条件渲染——除非共享 primitive 使门控变成结构性的权限门变成onClick{can ? … : undefined}navigable{can}渲染为纯非交互行无字面text-[Npx]class——只用命名刻度 token每个资源列表行有身份的实体——工具、服务器、密钥、凭据都是RESOURCE_LIST_STACK/RESOURCE_LIST_GRID中的SettingsResourceRow——无包裹button/Link、无手传箭头、无重新推导的 title/subtitle span。真正不同形态的行保持特制并用RESOURCE_ROW_ARROW_CLASSES自画箭头多行 bodyinbox tasks、表格列billing invoices、credit usage、网格secrets、成员行见头像双组件注记打开详情页的行用navigableclickLabel扁平记录用RowActionsMenu。不能两者都有装饰性 trailing 内容在badge而非trailing带标签的分段用SettingsSection只读字段用SettingsField空/加载/错误用SettingsEmptyStateDelete 是ChipConfirmModal后的普通id:deleteheader actiondestructive保留给批量操作tsc、biome、页面测试全部通过。这套以Chrome 归外壳、页面只出数据为核心的约定让 Sim 数十个设置页面general、secrets、mcp、billing、access-control、whitelabeling……共享同一套 header 排序、保存守卫与列表行行为。无论你要新增一个设置 Tab、清理一批历史页面还是把旧式资源行升级为SettingsResourceRow遵循 SKILL.md 的三条模式与本文档的契约就能保证迁移是纯结构性的、像素级一致的并且始终通过类型检查、lint 与测试。【免费下载链接】simSim is the collaborative workspace to build, deploy, and monitor AI agents and workflows. Used by 100,000 builders.项目地址: https://gitcode.com/GitHub_Trending/sim16/sim创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考