Cocos Creator下拉框组件开发:UI与逻辑解耦实战指南
1. 项目概述为什么UI与逻辑解耦是Cocos Creator开发的核心课题在Cocos Creator项目里摸爬滚打几年我见过太多新手开发者甚至一些老手写出来的UI代码那叫一个“剪不断理还乱”。一个简单的下拉框点击事件、数据更新、界面刷新、业务逻辑全挤在一个脚本里动一发而牵全身。后期想改个样式或者加个筛选功能简直是一场灾难。这其实就是典型的UI与业务逻辑高度耦合带来的恶果。这次我们就拿游戏开发中最常见但也最容易被忽视的UI组件——下拉框Dropdown来开刀。我们的目标很明确实现一个点击下拉项能传递参数并且能优雅地进行数据绑定的下拉框组件。最终让UI层只负责展示和交互业务逻辑层只负责处理数据和规则两者通过清晰的接口通信真正做到“桥归桥路归路”。这种解耦带来的好处是实实在在的代码可读性飙升、可维护性增强、团队协作效率提高更重要的是它为你的项目应对未来可能的需求变更比如换皮、多语言、动态配置铺平了道路。2. 核心思路拆解从“硬编码”到“数据驱动”的转变在动手写代码之前我们必须把思路理清楚。传统的、耦合式的下拉框实现是怎样的通常我们会在一个Monobehavior脚本里手动获取下拉框组件然后在start或onLoad里通过addItem动态添加选项并为每个选项的点击事件绑定一个回调函数。这个回调函数里可能直接操作了其他UI的显示或者调用了某个管理器的某个方法。// 传统耦合式写法示例问题代码 onLoad() { let dropdown this.node.getComponent(cc.Dropdown); dropdown.addItem(new cc.Label().string 选项A); // ... 添加更多选项 dropdown.node.on(toggle, (event) { if(event.target.name 选项A) { this.player.equipWeapon(sword); // 直接操作业务逻辑 this.uiManager.updateWeaponIcon(sword); // 直接操作UI } // ... 更多if else }); }这种写法的弊端一目了然下拉框的选项文本是硬编码的点击事件的处理逻辑与UI强绑定一旦业务逻辑如equipWeapon或UI更新updateWeaponIcon需要修改你必须找到这个脚本并深入其中。如果十个地方用了类似的下拉框你就要改十次。我们的解耦思路核心在于引入“数据驱动”和“事件通信”两个概念数据驱动下拉框的选项列表不应该在UI脚本里写死而应该由外部如一个配置表、一个数据管理器提供。UI脚本只负责接收一个数据数组例如Array{label: string, value: any}然后根据这个数组动态生成选项。这样要修改选项你只需要改数据源UI会自动同步。事件通信下拉框被点击后不应该直接执行业务逻辑。它应该只做一件事抛出一个携带了必要信息如选中的值的事件。至于谁监听这个事件、如何处理这个事件UI脚本完全不用关心。这通常通过Cocos Creator内置的EventTarget或自定义的全局事件管理器来实现。基于这个思路我们将构建一个双向的、松耦合的架构数据流向UI驱动显示UI交互产生事件通知逻辑。3. 构建可配置、可复用的下拉框预制体理论说再多不如动手实践。首先我们从最基础的UI结构开始搭建。我们不直接使用Cocos Creator原始的Dropdown组件因为它虽然基础但定制化和解耦程度不够。我们要创建一个自己的预制体Prefab。步骤一创建UI结构在场景编辑器中创建一个节点作为下拉框根节点如CustomDropdown。其子节点通常包括Button(或一个SpriteLabel)作为显示当前选中项的按钮。Label附着在Button上用于显示当前选中的文本。Arrow一个下拉箭头图标。ScrollView作为下拉列表的容器初始状态为active false。ViewScrollView的视图区域。Content用于动态生成下拉选项列表的布局节点通常挂载Vertical Layout组件。ItemTemplate作为下拉选项的模板预制体或一个隐藏的节点模板。它通常包含一个背景Sprite和一个文本Label。步骤二编写核心控制器脚本创建一个TypeScript脚本例如CustomDropdown.ts挂载到根节点上。这个脚本是UI部分的核心。// CustomDropdown.ts 部分核心代码 import { _decorator, Component, Node, Label, Button, ScrollView, instantiate, Prefab } from cc; const { ccclass, property } _decorator; ccclass(CustomDropdown) export class CustomDropdown extends Component { property(Label) selectedLabel: Label null!; // 显示选中项的Label property(Button) triggerButton: Button null!; // 触发下拉的按钮 property(ScrollView) listScrollView: ScrollView null!; // 下拉列表 property(Node) itemContent: Node null!; // 选项容器 property(Prefab) itemTemplate: Prefab null!; // 选项预制体模板 private _isListShowing: boolean false; private _dataList: Array{label: string, value: any} []; // 数据源 private _selectedIndex: number -1; private _selectedValue: any null; // 外部设置数据的方法 setDataList(data: Array{label: string, value: any}) { this._dataList data || []; this._selectedIndex -1; this._selectedValue null; this.selectedLabel.string 请选择; this._clearList(); this._generateListItems(); } // 动态生成列表项 private _generateListItems() { this._dataList.forEach((itemData, index) { const itemNode instantiate(this.itemTemplate); itemNode.parent this.itemContent; const labelComp itemNode.getComponentInChildren(Label); if (labelComp) { labelComp.string itemData.label; } // 为每个选项绑定点击事件 itemNode.on(Node.EventType.TOUCH_END, () { this._onItemSelected(index, itemData); }); }); } // 选中项的处理 private _onItemSelected(index: number, data: {label: string, value: any}) { this._selectedIndex index; this._selectedValue data.value; this.selectedLabel.string data.label; this._toggleList(false); // 选中后收起列表 // 关键步骤派发自定义事件而不是直接执行业务逻辑 this.node.emit(dropdown-select, { index, value: data.value, data: data }); } // 显示/隐藏下拉列表 private _toggleList(show: boolean) { this.listScrollView.node.active show; this._isListShowing show; } // 清空已有选项 private _clearList() { this.itemContent.removeAllChildren(); } }注意这里使用node.emit派发事件。这是一种简单的组件间通信方式适用于父子节点或小范围通信。对于跨场景、跨模块的通信强烈建议使用一个单例模式的事件管理器避免节点引用链过长或难以管理。步骤三预制体与数据配置将制作好的节点保存为预制体CustomDropdown.prefab。现在你可以在任何需要的地方实例化这个预制体并通过调用setDataList方法传入不同的数据数组来得到功能相同但内容各异的下拉框。这就是“可配置”和“可复用”的第一步。4. 实现优雅的点击传参与事件通信机制UI部分准备好了接下来就是让点击动作变得有意义——把参数传出去。我们在_onItemSelected方法里已经派发了一个dropdown-select事件。现在我们需要在业务逻辑层监听它。假设我们有一个角色装备选择的下拉框。我们创建一个装备管理器EquipmentManager.ts。// EquipmentManager.ts import { _decorator, Component, Node } from cc; import { CustomDropdown } from ./CustomDropdown; // 假设脚本路径 const { ccclass, property } _decorator; ccclass(EquipmentManager) export class EquipmentManager extends Component { property(CustomDropdown) weaponDropdown: CustomDropdown null!; onLoad() { // 初始化下拉框数据 const weaponData [ { label: 长剑, value: sword, attack: 15 }, { label: 战斧, value: axe, attack: 20 }, { label: 法杖, value: staff, magic: 25 }, ]; this.weaponDropdown.setDataList(weaponData); // 监听下拉框的选择事件 this.weaponDropdown.node.on(dropdown-select, this.onWeaponSelected, this); } onWeaponSelected(event: any) { // event.detail 包含了我们在CustomDropdown中emit时传递的参数 const selectedValue event.detail.value; const selectedData event.detail.data; // 这里是完整的 {label: 长剑, value: sword, attack: 15} console.log(玩家选择了武器${selectedData.label}, 价值${selectedValue}); // 这里执行纯业务逻辑例如更新玩家数据 this.changePlayerWeapon(selectedValue, selectedData.attack); // 注意这里不直接操作任何UIUI的更新通过数据绑定下一步或另一个事件触发。 } changePlayerWeapon(weaponId: string, attack: number) { // ... 更新玩家模型数据的逻辑 // 数据更新后可能会触发一个全局事件如 player-data-updated // 让关心玩家数据的UI组件如属性面板自己去监听和更新 } onDestroy() { // 记得移除监听避免内存泄漏 this.weaponDropdown.node.off(dropdown-select, this.onWeaponSelected, this); } }通过这种方式CustomDropdown组件完全不知道EquipmentManager的存在它只负责展示和抛出事件。EquipmentManager也无需知道下拉框内部是如何渲染的它只关心数据和业务逻辑。两者通过一个约定好的事件名dropdown-select和数据结构进行通信实现了完美的解耦。5. 进阶实现自动化的数据绑定事件通信解耦了动作响应但UI的更新比如选中后下拉框显示的文字还是我们在事件回调里手动设置的this.selectedLabel.string data.label。更进一步我们可以实现数据绑定当业务逻辑层的数据发生变化时UI自动更新无需手动调用。在Cocos Creator中没有像Vue或React那样的响应式系统内置支持但我们可以模拟实现一个简单的观察者模式或使用装饰器。这里介绍一种实用且清晰的方法结合TypeScript的访问器getter/setter和自定义事件。首先我们定义一个可观察的数据模型。// ObservableModel.ts - 一个简单的可观察对象基类 import { EventTarget } from cc; export class ObservableModel { private _eventTarget: EventTarget new EventTarget(); // 监听属性变化 onPropertyChanged(propertyName: string, callback: Function, target?: any) { this._eventTarget.on(propertyName, callback, target); } offPropertyChanged(propertyName: string, callback?: Function, target?: any) { this._eventTarget.off(propertyName, callback, target); } // 触发属性变化通知 protected emitPropertyChange(propertyName: string, ...args: any[]) { this._eventTarget.emit(propertyName, ...args); } }然后让我们的业务数据模型继承它。// PlayerData.ts import { ObservableModel } from ./ObservableModel; export class PlayerData extends ObservableModel { private _currentWeapon: string ; private _attack: number 10; get currentWeapon(): string { return this._currentWeapon; } set currentWeapon(value: string) { if (this._currentWeapon ! value) { this._currentWeapon value; this.emitPropertyChange(currentWeapon, value); // 数据变化发出通知 } } get attack(): number { return this._attack; } set attack(value: number) { if (this._currentWeapon ! value) { this._attack value; this.emitPropertyChange(attack, value); } } }接着改造我们的CustomDropdown让它支持绑定到一个数据模型的某个属性上。// CustomDropdown.ts 新增数据绑定功能 export class CustomDropdown extends Component { // ... 原有属性 ... property bindTarget: any null; // 绑定的数据对象在编辑器中拖入 property bindProperty: string ; // 绑定的属性名如 currentWeapon onLoad() { // ... 原有初始化 ... if (this.bindTarget this.bindProperty) { this._setupDataBinding(); } } private _setupDataBinding() { // 监听数据模型的变化 if (this.bindTarget.onPropertyChanged) { this.bindTarget.onPropertyChanged(this.bindProperty, this._onBoundPropertyChanged, this); } // 初始化显示 this._updateDisplayFromBinding(); } private _onBoundPropertyChanged(newValue: any) { // 当绑定的数据变化时更新UI显示 this._selectedValue newValue; // 在_dataList中查找对应value的项并更新selectedLabel const item this._dataList.find(d d.value newValue); if (item) { this.selectedLabel.string item.label; this._selectedIndex this._dataList.indexOf(item); } } private _updateDisplayFromBinding() { if (this.bindTarget this.bindProperty) { const value this.bindTarget[this.bindProperty]; this._onBoundPropertyChanged(value); } } private _onItemSelected(index: number, data: {label: string, value: any}) { // ... 原有逻辑 ... this.node.emit(dropdown-select, { index, value: data.value, data: data }); // 新增如果设置了绑定则更新数据模型 if (this.bindTarget this.bindProperty) { this.bindTarget[this.bindProperty] data.value; // 这里会触发setter进而触发onPropertyChanged } } onDestroy() { if (this.bindTarget this.bindTarget.offPropertyChanged) { this.bindTarget.offPropertyChanged(this.bindProperty, this._onBoundPropertyChanged, this); } } }最后在编辑器里我们将PlayerData类的实例比如挂在某个管理器节点上拖到CustomDropdown组件的bindTarget属性并在bindProperty里填写currentWeapon。这样一个双向数据绑定就建立了用户点击下拉框选择“长剑” -_onItemSelected触发 - 设置bindTarget.currentWeapon sword-PlayerData的setter触发发出currentWeapon变化事件 -CustomDropdown监听到事件自动更新显示文本。如果其他地方比如使用道具修改了playerData.currentWeapon axe同样会触发事件下拉框的显示会自动变为“战斧”。实操心得这种数据绑定模式在管理复杂UI状态时非常强大但它也引入了额外的复杂度。对于小型项目或简单组件使用单纯的事件通信可能更轻量、更直接。你需要根据项目规模和团队习惯进行权衡。一个实用的建议是对于全局的核心数据模型如玩家数据、游戏设置采用绑定对于局部、一次性的交互用事件就够了。6. 常见问题、性能优化与避坑指南在实际使用这套方案时你可能会遇到以下几个典型问题问题一下拉列表的滚动视图ScrollView在动态生成大量选项时卡顿。原因瞬间实例化几十上百个itemTemplate节点可能造成主线程阻塞。解决方案实现对象池Object Pooling。不要每次都instantiate和destroy而是预先创建一定数量的Item节点放入池中需要时取出复用不需要时放回并隐藏。// 简化的对象池思路 private _itemPool: Node[] []; private _getItemFromPool(): Node { let item this._itemPool.pop(); if (!item) { item instantiate(this.itemTemplate); } item.active true; return item; } private _recycleItemToPool(item: Node) { item.active false; item.removeFromParent(); this._itemPool.push(item); } // 在_generateListItems和_clearList中使用池化方法问题二点击下拉框外部区域无法自动收起下拉列表。解决方案在根节点上添加一个覆盖全屏的透明拦截节点一个单独的Node添加Widget组件铺满全屏并挂载Button组件默认隐藏。当下拉列表展开时显示这个拦截节点并为其添加TOUCH_END事件在事件中收起下拉列表。记得在收起后隐藏该拦截节点。问题三数据绑定后下拉框的初始显示不正确不显示绑定的初始值。排查检查onLoad和start的执行顺序。CustomDropdown的onLoad中调用_setupDataBinding时bindTarget的数据可能还未初始化。解决在start生命周期中进行数据绑定的初始更新或者确保bindTarget的数据在onLoad阶段就已就绪。也可以让bindTarget在数据就绪后主动通知所有绑定者。问题四事件监听导致的内存泄漏。现象切换场景后旧场景的下拉框节点已被销毁但之前注册的事件监听器没有移除导致回调函数和关联对象无法被垃圾回收。规避养成好习惯在组件的onDestroy或onDisable生命周期中移除所有通过on或once注册的事件监听。如上文代码示例所示。问题五下拉框在复杂UI层级中被其他UI组件遮挡。解决确保下拉列表的根节点ScrollView.node在UI树中的层级足够高。可以动态设置其zIndex或者在显示时将其移到UI根节点下。Cocos Creator的Canvas节点有sortingOrder属性也可以利用。性能优化小贴士避免每帧查询不要在update里频繁获取组件或查询节点。所有引用在onLoad中获取并缓存。合批优化下拉选项的Item模板尽量使用相同的材质和纹理以促进渲染合批。列表项复用如前所述对于超长列表对象池是必须的。可以考虑只渲染可视区域内的项即虚拟列表但这在Cocos Creator中需要自行实现复杂度较高非必要不推荐。这套从点击传参到数据绑定的下拉框解决方案其价值远不止于实现一个组件。它代表的是一种清晰、可维护的UI架构思想。当你把项目中所有的交互组件都按照这种“展示-事件-数据”的模式重构后你会发现代码的脉络变得异常清晰新人接手的速度快得惊人自己半年后回来修改功能也不再头疼。UI与逻辑的优雅解耦是通往高质量、可长期维护的游戏前端代码的必经之路。

相关新闻

5个实战秘籍快速搞定YimMenu菜单配置:从零到精通完全宝典

5个实战秘籍快速搞定YimMenu菜单配置:从零到精通完全宝典

5个实战秘籍快速搞定YimMenu菜单配置:从零到精通完全宝典 【免费下载链接】YimMenu YimMenu, a GTA V menu protecting against a wide ranges of the public crashes and improving the overall experience. 项目地址: https://gitcode.com/GitHub_Trending/yi/Y…

2026/8/11 13:28:03 阅读更多 →
AstronClaw企业级AI部署与安全优化实战

AstronClaw企业级AI部署与安全优化实战

1. AstronClaw与OpenClaw技术定位解析讯飞AstronClaw本质上是一个经过商业验证的OpenClaw发行版,其核心价值在于解决了开源项目在企业级场景中的三大痛点:部署复杂度、安全合规性以及生产环境适配。实测发现,相比原生OpenClaw需要手动处理依赖…

2026/8/11 13:27:03 阅读更多 →
PHP表单安全防护实战:CSRF与XSS攻击原理与防御方案详解

PHP表单安全防护实战:CSRF与XSS攻击原理与防御方案详解

1. 项目概述:PHP表单安全为何总是“失守”? 如果你是一名PHP开发者,或者正在维护一个基于PHP的Web应用,那么“表单被攻击”这件事,可能就像房间里的大象——你隐约知道它存在,却总希望它不要真的撞过来。我…

2026/8/11 13:27:03 阅读更多 →

最新新闻

MCP重大更新:移除会话机制,全面无状态化

MCP重大更新:移除会话机制,全面无状态化

💡 核心导读:MCP 本次规范修订的重点不是增加能力,而是移除初始化握手与会话机制,让每个请求都能独立成立。 这意味着协议将不再替分布式系统管理状态,而是把复杂度交还给业务层与成熟的基础设施。Model Context Proto…

2026/8/11 14:18:21 阅读更多 →
思源黑体TTF实战指南:7字重专业字体构建深度解析

思源黑体TTF实战指南:7字重专业字体构建深度解析

思源黑体TTF实战指南:7字重专业字体构建深度解析 【免费下载链接】source-han-sans-ttf A (hinted!) version of Source Han Sans 项目地址: https://gitcode.com/gh_mirrors/so/source-han-sans-ttf 思源黑体TTF是一款专注于高质量TTF字体生成的开源工具&am…

2026/8/11 14:18:21 阅读更多 →
基于SpringBoot的中医养生系统开发实践

基于SpringBoot的中医养生系统开发实践

1. 项目背景与核心价值 中医养生文化作为传统医学的重要组成部分,正随着健康意识的提升而受到广泛关注。这个基于JavaWeb的中医养生文化传播系统,正是为了解决当前养生知识传播渠道分散、专业性不足的问题而设计。系统采用B/S架构,通过整合养…

2026/8/11 14:18:21 阅读更多 →
AI前沿模型监管的三种制度路径与全球治理碎片化

AI前沿模型监管的三种制度路径与全球治理碎片化

AI前沿模型监管的三种制度路径与全球治理碎片化 本文为完整研究报告的精华版。完整版含七维度对比矩阵、制度模板忠实度分析、四个附录与时间线,已存档于腾讯文档。回复"AI治理"获取 导论 2026年6月,美国政府两次临时干预前沿AI模型的发布。先…

2026/8/11 14:18:21 阅读更多 →
Git项目身份重置:彻底清除原仓库信息并关联新远程仓库

Git项目身份重置:彻底清除原仓库信息并关联新远程仓库

1. 项目概述:为什么需要“净身出户”?在团队协作或者个人项目迭代中,我们经常会遇到这样一个场景:你从某个开源项目、公司内部模板或者同事那里拿到一个现成的代码仓库,想基于它开始自己的新项目。直接复制文件夹&…

2026/8/11 14:18:21 阅读更多 →
AI Agent开发框架选型指南:从零搭建与主流方案对比

AI Agent开发框架选型指南:从零搭建与主流方案对比

1. 从“造轮子”到“选轮子”:一个Agent开发者的困惑与觉醒最近在社区和几个技术群里,关于AI Agent开发的讨论热度一直居高不下。一个反复被提及、也最容易引发争论的话题就是:“做Agent项目,到底要不要用框架?” 新手…

2026/8/11 14:17:21 阅读更多 →

日新闻

如何用Video2X实现专业级视频画质提升:AI视频增强完整指南

如何用Video2X实现专业级视频画质提升:AI视频增强完整指南

如何用Video2X实现专业级视频画质提升:AI视频增强完整指南 【免费下载链接】video2x A machine learning-based video super resolution and frame interpolation framework. Est. Hack the Valley II, 2018. 项目地址: https://gitcode.com/GitHub_Trending/vi/v…

2026/8/11 0:00:02 阅读更多 →
前后端分离项目中控制台与接口工具数据差异排查指南

前后端分离项目中控制台与接口工具数据差异排查指南

1. 问题现象解析:控制台与Apifox的数据差异 最近在调试一个前后端分离项目时,遇到了一个典型问题:后端服务在本地开发环境控制台能正常输出查询数据,但通过Apifox测试时却返回空结果。这种"控制台有数据,接口工具…

2026/8/11 0:00:03 阅读更多 →
AI编程实战:从Claude Code踩坑到游戏开发入门

AI编程实战:从Claude Code踩坑到游戏开发入门

1. 从“AI能帮我做游戏”到“AI让我重新学编程”最近身边不少朋友,尤其是一些非技术背景、但对游戏开发有浓厚兴趣的朋友,都在问我同一个问题:“听说现在用Claude Code这种AI编程工具,小白也能做游戏了,是真的吗&#…

2026/8/11 0:00:03 阅读更多 →

周新闻

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁 【免费下载链接】baidupankey 在线查询网盘提取码(维护中 rm repo) 项目地址: https://gitcode.com/gh_mirrors/ba/baidupankey 你是否曾经在深夜寻找一份重要资料&#x…

2026/8/11 1:08:05 阅读更多 →
如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南 【免费下载链接】chinese_license_plate_generator 中国车牌生成器 项目地址: https://gitcode.com/gh_mirrors/ch/chinese_license_plate_generator 中国车牌生成器是一个基于Python的开源项目&#xff0c…

2026/8/11 1:08:05 阅读更多 →
收藏!小白程序员轻松入门大模型,从Harness工程开始实践

收藏!小白程序员轻松入门大模型,从Harness工程开始实践

文章强调学习大模型不应只关注模型本身,而应重视模型外的系统搭建,即Harness。提出AgentModelHarness的实用公式,详细介绍Harness的四个层次:持久化层、执行层、控制层和观察与验证层。文章还探讨了上下文工程、工具设计、AGENTS.…

2026/8/11 1:08:05 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/10 17:07:33 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/11 1:08:06 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/10 17:07:33 阅读更多 →