ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

基于Qt/QML开发Win11风格虚拟键盘:跨平台桌面应用实战

基于Qt/QML开发Win11风格虚拟键盘:跨平台桌面应用实战 大家好我是专注于桌面应用开发的技术博主。在开发跨平台应用时一个美观、流畅且功能完整的屏幕键盘往往是提升用户体验的关键一环尤其是在触摸屏设备或特定无障碍场景下。然而市面上许多开源方案要么过于陈旧要么与 Windows 11 的 Fluent Design 风格格格不入要么就是功能缺失、定制困难。为此我基于 Qt/QML 技术栈开发并开源了一款高度模仿 Windows 11 原生风格、功能齐全的虚拟屏幕键盘。它不仅外观现代还支持多种键盘布局、输入法切换、触觉反馈模拟等实用功能。本文将完整分享该项目的开发思路、核心实现、部署方法以及如何二次开发无论你是想直接使用这个键盘还是学习 QML 开发桌面组件都能从中获得一套完整的实战方案。1. 项目背景与核心价值1.1 为什么需要自定义屏幕键盘在桌面应用开发中集成屏幕键盘通常出于以下几个需求触摸屏设备在平板模式或一体机上为用户提供便捷的文本输入方式。信息亭/自助终端银行 ATM、医院挂号机、商场导览等场景需要锁定物理键盘仅允许通过屏幕输入。无障碍辅助为行动不便或使用特殊输入设备的用户提供辅助输入工具。风格统一许多应用希望其内置键盘的视觉风格与自身 UI 设计语言保持一致而不是调用系统那个可能风格迥异的键盘。Windows 系统自带的屏幕键盘osk.exe功能强大但样式固定无法深度集成到第三方应用中且其启动和关闭行为不易由程序精确控制。1.2 为什么选择 Qt/QML 来实现Qt 是一个成熟的跨平台 C 框架而 QML 是其声明式 UI 语言特别适合构建动态、流畅的现代界面。选择它们的原因如下跨平台核心逻辑使用 CUI 使用 QML可以轻松编译到 Windows、macOS、Linux 甚至嵌入式系统。高性能与原生感Qt Quick 渲染引擎能保证动画的流畅性并且可以调用系统 API 来模拟真实的键盘事件体验接近原生。声明式 UIQML 的语法简洁直观描述键盘布局、动画效果、状态切换非常高效修改样式也更容易。强大的社区与工具有完善的文档、丰富的第三方模块和 Qt Creator 这样的优秀 IDE 支持。1.3 本开源项目的核心特点我开源的这款 Win11 风格屏幕键盘旨在提供一个生产可用的解决方案其主要特点包括高度还原的 Win11 视觉风格采用 Fluent Design 的亚克力背景、圆角、阴影和流畅的动画效果。完整的键盘布局支持标准 QWERTY 布局、数字小键盘、功能键区并可通过配置切换为其他布局如 DVORAK。输入法状态指示可显示当前输入法状态如中/英文。模拟按键反馈按键按下时有视觉缩放和颜色变化效果并可配置是否播放提示音。易于集成提供简单的 API可以作为一个组件Window或Item嵌入到任何 Qt Quick 应用中。开源与可定制代码完全开源采用宽松的许可证如 MIT你可以自由修改样式、增加功能或修复问题。2. 开发环境与项目准备2.1 环境要求要运行、修改或编译此项目你需要准备以下环境操作系统Windows 10/11主要开发环境理论上也支持 macOS 和 Linux。Qt 框架推荐使用Qt 5.15.x LTS或Qt 6.2及以上版本。Qt 6 在图形渲染和模块化方面有诸多改进但本项目确保对 Qt 5.15 的兼容性。编译器Windows: MSVC 2019 或 MinGW 8.1/11.2。Linux/macOS: GCC 或 Clang。IDE强烈推荐使用Qt Creator它提供了对 QML 语法高亮、实时预览QML Live Preview和集成调试的完美支持。版本控制Git用于克隆项目代码。2.2 获取项目源码项目已托管在 GitHub 上你可以通过以下命令克隆仓库git clone https://github.com/your-username/win11-virtual-keyboard.git cd win11-virtual-keyboard注意请将上述 URL 中的your-username替换为实际的仓库所有者用户名。本文示例代码和结构基于一个典型的 Qt Quick 项目。2.3 项目目录结构解析克隆后你会看到类似如下的目录结构理解它有助于后续的开发和定制win11-virtual-keyboard/ ├── CMakeLists.txt # CMake 构建配置文件 (Qt 6 推荐) ├── win11-virtual-keyboard.pro # QMake 项目文件 (Qt 5) ├── src/ │ ├── main.cpp # 应用程序入口点 │ └── main.qml # 主 QML 文件定义应用窗口 ├── qml/ │ ├── components/ │ │ ├── KeyButton.qml # 单个按键的自定义组件 │ │ ├── KeyboardRow.qml # 单行键盘布局组件 │ │ └── KeyboardLayout.qml # 整个键盘布局管理组件 │ ├── models/ │ │ └── KeyModel.qml # 定义按键数据字符、类型、宽度等的模型 │ ├── styles/ │ │ └── FluentStyle.qml # 集中定义颜色、尺寸、动画等样式常量 │ └── windows/ │ └── MainWindow.qml # 主窗口包含键盘和设置面板 ├── resources/ # 资源文件如图标、音效 │ └── sounds/ │ └── keyclick.wav └── README.md # 项目说明文档这个结构遵循了 Qt Quick 应用的良好实践将 UI 组件、业务逻辑和资源分离使得代码更清晰、更易维护。3. QML 核心组件与原理拆解在深入代码之前我们需要理解构建这个屏幕键盘的几个核心 QML 概念和组件设计思路。3.1 声明式 UI 与组件化QML 的核心是声明式语法。我们不是通过命令式代码一步步“创建”UI而是“描述”UI 应该是什么样子。例如一个按键// KeyButton.qml - 自定义按钮组件 import QtQuick 2.15 import QtQuick.Controls 2.15 Button { id: control property string keyText: “A“ // 自定义属性显示在按钮上的文本 property real keyWidthScale: 1.0 // 宽度缩放因子用于调整特殊键如Shift, Enter的宽度 width: 60 * keyWidthScale height: 60 text: keyText background: Rectangle { id: bgRect radius: 8 color: control.pressed ? “#0078D4“ : “#E6E6E6“ // 按下时变为Win11主题蓝色 border.color: “#CCCCCC“ // 内阴影效果增强立体感 layer.enabled: true layer.effect: DropShadow { transparentBorder: true radius: 4 samples: 9 color: “#40000000“ verticalOffset: control.pressed ? 1 : 2 // 按下时阴影偏移变小模拟按下效果 } } // 点击动画 onPressedChanged: { if (pressed) { clickAnimation.start(); // 这里可以触发按键音效 soundEffect.play(); } } SequentialAnimation { id: clickAnimation PropertyAnimation { target: control; property: “scale“; to: 0.95; duration: 50 } PropertyAnimation { target: control; property: “scale“; to: 1.0; duration: 100 } } SoundEffect { id: soundEffect source: “qrc:/sounds/keyclick.wav“ } }这个KeyButton组件封装了一个按键的所有视觉和行为逻辑。通过定义keyText和keyWidthScale属性它可以在不同的布局中被复用。3.2 数据模型与视图分离键盘布局哪些键在哪一行是数据。在 QML 中我们通常使用ListModel或 JavaScript 数组来定义这些数据然后使用Repeater或ListView来根据数据动态创建视图。// 在 KeyboardLayout.qml 中定义一行按键的数据模型 ListModel { id: topRowModel ListElement { keyText: “Q“; keyCode: Qt.Key_Q; widthScale: 1.0 } ListElement { keyText: “W“; keyCode: Qt.Key_W; widthScale: 1.0 } ListElement { keyText: “E“; keyCode: Qt.Key_E; widthScale: 1.0 } // ... 省略其他字母键 ListElement { keyText: “Backspace“; keyCode: Qt.Key_Backspace; widthScale: 1.5 } // 退格键更宽 } // 使用 Repeater 根据模型创建一行按键 Row { spacing: 6 Repeater { model: topRowModel delegate: KeyButton { keyText: model.keyText keyWidthScale: model.widthScale onClicked: { keyboardController.handleKeyPress(model.keyCode, model.keyText); } } } }这种模式使得更改键盘布局例如切换到 DVORAK变得非常简单只需修改数据模型而无需改动 UI 代码。3.3 模拟键盘事件屏幕键盘的核心功能是向系统发送按键事件让其他应用程序如记事本、浏览器能接收到输入。在 Qt 中我们可以使用QGuiApplication来发送全局的键盘事件。为了在 QML 中调用 C 功能我们需要创建一个简单的“桥梁”。首先创建一个 C 类例如KeyboardController// keyboardcontroller.h #ifndef KEYBOARDCONTROLLER_H #define KEYBOARDCONTROLLER_H #include QObject #include QGuiApplication #include QKeyEvent class KeyboardController : public QObject { Q_OBJECT public: explicit KeyboardController(QObject *parent nullptr); public slots: // QML 可以调用的函数 void sendKeyPress(int keyCode, const QString text QString()); void sendKeyRelease(int keyCode); }; #endif // KEYBOARDCONTROLLER_H// keyboardcontroller.cpp #include “keyboardcontroller.h“ #include QDebug KeyboardController::KeyboardController(QObject *parent) : QObject(parent) {} void KeyboardController::sendKeyPress(int keyCode, const QString text) { // 获取当前焦点窗口 QWindow *focusWindow QGuiApplication::focusWindow(); if (!focusWindow) { qWarning() “No focused window to send key event to.“; return; } // 构造一个键盘按下事件 QKeyEvent *pressEvent new QKeyEvent(QEvent::KeyPress, keyCode, Qt::NoModifier, text); // 发送事件到焦点窗口 QGuiApplication::postEvent(focusWindow, pressEvent); // 通常按下后应立即发送释放事件模拟一次完整的击键。 // 但为了更真实的控制如长按可以将按下和释放分开。 // 这里我们简单起见立即发送释放。 QKeyEvent *releaseEvent new QKeyEvent(QEvent::KeyRelease, keyCode, Qt::NoModifier, text); QGuiApplication::postEvent(focusWindow, releaseEvent); }然后将这个 C 类注册到 QML 引擎中并在 QML 中像使用普通对象一样使用它// 在 main.cpp 中注册 KeyboardController keyboardController; engine.rootContext()-setContextProperty(“keyboardController“, keyboardController); // 在 KeyButton.qml 的 onClicked 信号处理器中调用 onClicked: { keyboardController.sendKeyPress(model.keyCode, model.keyText); }4. 完整实战从零构建 Win11 风格键盘现在让我们一步步构建这个键盘的主要部分。我们将创建一个简单的应用其主窗口只包含这个键盘。4.1 创建 Qt Quick 应用项目打开 Qt Creator选择“新建项目”。选择“Application (Qt Quick)“ - “Qt Quick Application - Empty“。设置项目名称如Win11VirtualKeyboard和路径。在“选择 Qt 套件”页面选择你安装的 Qt 版本如 Qt 5.15.2 MSVC2019 64bit。完成创建。4.2 设计键盘布局数据我们首先在KeyboardLayout.qml中定义整个键盘的布局数据。为了清晰我们使用一个 JavaScript 对象来组织// KeyboardLayout.qml import QtQuick 2.15 Item { id: root // 定义所有行的布局数据 property var layoutModel: [ // 第一行功能键和数字键 [ { text: “Esc“, code: Qt.Key_Escape, widthScale: 1.2 }, { text: “F1“, code: Qt.Key_F1, widthScale: 1.0 }, { text: “F2“, code: Qt.Key_F2, widthScale: 1.0 }, // ... 省略 F3-F12 { text: “PrtSc“, code: Qt.Key_Print, widthScale: 1.0 }, { text: “Scroll Lock“, code: Qt.Key_ScrollLock, widthScale: 1.0 }, { text: “Pause“, code: Qt.Key_Pause, widthScale: 1.0 } ], // 第二行主键盘区第一行 [ { text: ““, code: Qt.Key_QuoteLeft, widthScale: 1.0 }, { text: “1“, code: Qt.Key_1, widthScale: 1.0 }, { text: “2“, code: Qt.Key_2, widthScale: 1.0 }, // ... 省略 3-9, 0, -, { text: “Backspace“, code: Qt.Key_Backspace, widthScale: 1.8 } ], // 第三行主键盘区第二行 (Tab, Q, W, E, R, T, Y...) [ { text: “Tab“, code: Qt.Key_Tab, widthScale: 1.5 }, { text: “Q“, code: Qt.Key_Q, widthScale: 1.0 }, { text: “W“, code: Qt.Key_W, widthScale: 1.0 }, // ... 省略其他 { text: “\\“, code: Qt.Key_Backslash, widthScale: 1.0 } ], // 第四行主键盘区第三行 (Caps Lock, A, S, D...) [ { text: “Caps Lock“, code: Qt.Key_CapsLock, widthScale: 1.8, toggle: true }, { text: “A“, code: Qt.Key_A, widthScale: 1.0 }, { text: “S“, code: Qt.Key_S, widthScale: 1.0 }, // ... 省略其他 { text: “Enter“, code: Qt.Key_Return, widthScale: 1.8, special: “enter“ } ], // 第五行主键盘区第四行 (Shift, Z, X, C...) [ { text: “Shift“, code: Qt.Key_Shift, widthScale: 2.2, modifier: true }, { text: “Z“, code: Qt.Key_Z, widthScale: 1.0 }, { text: “X“, code: Qt.Key_X, widthScale: 1.0 }, // ... 省略其他 { text: “Shift“, code: Qt.Key_Shift, widthScale: 2.2, modifier: true } ], // 第六行空格键行 [ { text: “Ctrl“, code: Qt.Key_Control, widthScale: 1.2, modifier: true }, { text: “Win“, code: Qt.Key_Meta, widthScale: 1.2 }, { text: “Alt“, code: Qt.Key_Alt, widthScale: 1.2, modifier: true }, { text: ““, code: Qt.Key_Space, widthScale: 6.0 }, // 空格键 { text: “Alt“, code: Qt.Key_Alt, widthScale: 1.2, modifier: true }, { text: “Win“, code: Qt.Key_Meta, widthScale: 1.2 }, { text: “Menu“, code: Qt.Key_Menu, widthScale: 1.2 }, { text: “Ctrl“, code: Qt.Key_Control, widthScale: 1.2, modifier: true } ] ] // ... 其他属性和函数 }4.3 实现键盘布局组件有了数据我们需要一个组件来渲染它。我们创建一个Keyboard.qml作为键盘的根组件// Keyboard.qml import QtQuick 2.15 import QtQuick.Layouts 1.15 ColumnLayout { id: keyboardLayout spacing: 8 // 使用 Repeater 为每一行布局数据创建一个 RowLayout Repeater { model: root.layoutModel // 引用上面定义的 layoutModel delegate: RowLayout { spacing: 6 Layout.alignment: Qt.AlignHCenter // 为每一行中的每个按键数据创建一个 KeyButton Repeater { model: modelData // modelData 是 layoutModel 中当前行的数组 delegate: KeyButton { Layout.preferredWidth: 60 * modelData.widthScale Layout.preferredHeight: 60 keyText: modelData.text isModifier: modelData.modifier || false isToggle: modelData.toggle || false specialType: modelData.special || ““ // 连接点击信号到键盘控制器 onClicked: { if (keyboardController) { keyboardController.sendKeyPress(modelData.code, modelData.text); } // 处理修饰键如Shift, Ctrl的状态切换 if (isModifier) { modifierPressed !modifierPressed; } if (isToggle) { toggled !toggled; // 如 Caps Lock } } } } } } }4.4 集成到主窗口并添加 Win11 视觉风格最后我们在主窗口中使用这个键盘并应用 Win11 Fluent Design 风格。这主要通过背景和阴影实现。// MainWindow.qml import QtQuick 2.15 import QtQuick.Controls 2.15 import QtGraphicalEffects 1.15 ApplicationWindow { id: mainWindow width: 1000 height: 400 visible: true title: qsTr(“Win11 Virtual Keyboard“) flags: Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint // 无边框置顶 // Win11 风格的亚克力背景 Rectangle { anchors.fill: parent color: “#F3F3F3“ // 浅灰色基底 opacity: 0.9 layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: mainWindow.width height: mainWindow.height radius: 12 // 圆角 } } } // 背景模糊效果 (Qt 6 支持更好Qt 5 可能需要其他方式) BackdropBlur { anchors.fill: parent source: parent radius: 32 visible: Qt.platform.os “windows“ // 可根据平台调整 } // 主内容区域 Rectangle { id: contentArea anchors.fill: parent anchors.margins: 20 color: “transparent“ Column { anchors.centerIn: parent spacing: 20 // 输入法状态显示 Rectangle { width: 200 height: 40 radius: 8 color: “#FFFFFF“ border.color: “#E0E0E0“ Text { anchors.centerIn: parent text: keyboardController.inputMethod “en“ ? “ENG“ : “中文“ font.pixelSize: 16 color: “#323130“ } } // 我们的键盘组件 Keyboard { id: virtualKeyboard anchors.horizontalCenter: parent.horizontalCenter } // 一些控制按钮 Row { spacing: 10 anchors.horizontalCenter: parent.horizontalCenter Button { text: “Hide“ onClicked: mainWindow.hide() } Button { text: “Switch Layout“ onClicked: virtualKeyboard.switchToNextLayout() } Button { text: “Settings“ onClicked: settingsPopup.open() } } } } // 简单的设置弹窗 Popup { id: settingsPopup anchors.centerIn: Overlay.overlay width: 300 height: 200 modal: true focus: true Column { anchors.fill: parent anchors.margins: 20 spacing: 15 CheckBox { text: qsTr(“Enable key press sound“) checked: true onCheckedChanged: keyboardController.soundEnabled checked } CheckBox { text: qsTr(“Show key press animation“) checked: true } Slider { id: opacitySlider from: 0.3 to: 1.0 value: 0.9 onValueChanged: mainWindow.opacity value } Text { text: qsTr(“Window Opacity: “) opacitySlider.value.toFixed(2) } } } // 拖动区域因为是无边框窗口 MouseArea { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right height: 40 property point clickPos: “0,0“ onPressed: clickPos Qt.point(mouse.x, mouse.y) onPositionChanged: { var delta Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y) mainWindow.x delta.x mainWindow.y delta.y } } }4.5 编译与运行在 Qt Creator 中确保main.cpp正确注册了我们的KeyboardControllerC 类。在项目文件.pro或CMakeLists.txt中确保添加了必要的模块例如对于 Qt 5# win11-virtual-keyboard.pro QT quick quickcontrols2 multimedia # multimedia 用于 SoundEffect对于 Qt 6# CMakeLists.txt find_package(Qt6 REQUIRED COMPONENTS Quick QuickControls2 Multimedia) qt_add_executable(Win11VirtualKeyboard ...) target_link_libraries(Win11VirtualKeyboard PRIVATE Qt6::Quick Qt6::QuickControls2 Qt6::Multimedia)点击 Qt Creator 的“构建”按钮绿色三角形然后点击“运行”。如果一切顺利你将看到一个具有 Win11 风格的虚拟键盘窗口。点击按键焦点应在其他应用如记事本中输入字符。5. 常见问题与排查思路在开发和使用过程中你可能会遇到以下问题问题现象可能原因排查与解决思路按键点击无反应无法输入文字1.KeyboardController未正确注册到 QML 上下文。2. 发送键盘事件的焦点窗口 (QGuiApplication::focusWindow()) 为nullptr。3. 按键的keyCode值不正确。1. 在main.cpp中检查engine.rootContext()-setContextProperty调用。2. 在点击按键前先用鼠标点击一下目标输入框如记事本确保其获得焦点。3. 在sendKeyPress函数中添加qDebug()打印确认函数被调用且参数正确。检查 Qt 的Qt::Key枚举值。键盘界面显示异常布局错乱1. QML 组件尺寸计算错误。2.Layout.preferredWidth/Height与实际尺寸冲突。3. 在高 DPI 屏幕上未做适配。1. 使用 Qt Creator 的 QML Debugger 检查各个 Item 的实际尺寸。2. 尝试将RowLayout改为简单的Row或检查spacing属性。3. 在main.cpp中启用高 DPI 支持QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);按键音效不播放1. 音效文件路径错误或未加入资源系统 (qrc)。2. 系统音频输出设备问题。3. Qt Multimedia 模块未正确链接。1. 确认.wav文件在resources/sounds/目录下并在.qrc文件中正确引用。2. 检查系统音量是否正常。尝试播放一个简单的MediaPlayer测试。3. 在项目文件 (.pro) 中确认已添加QT multimedia。窗口无法拖动无边框模式下MouseArea的拖动逻辑未生效或区域被覆盖。检查MouseArea是否覆盖在窗口最上层onPressed和onPositionChanged信号处理器是否正确连接。可以给MouseArea临时设置一个颜色以便调试其范围。编译错误提示 QML 模块未找到项目中引用的 QML 模块如QtQuick.Controls 2.15版本高于当前安装的 Qt 版本。1. 在 QML 文件顶部将 import 语句的版本号改为你实际安装的版本如import QtQuick.Controls 2.12。2. 升级你的 Qt 安装到更高版本。在 Linux/macOS 上运行风格不一致Win11 的 Fluent 设计如亚克力模糊是平台特定效果。使用Qt.platform.os进行条件判断在不同平台应用不同的视觉效果。例如在非 Windows 平台使用纯色背景。6. 最佳实践与进阶优化一个基础的屏幕键盘已经完成但要将其用于实际项目还需要考虑更多工程化细节。6.1 性能优化避免过度绘制键盘按键数量多每个按键都有阴影、圆角等效果可能影响性能。可以考虑对静态背景使用layer.enabled: false。对于大量重复的按键研究使用QtQuick.ShaderEffect进行批量渲染高级技巧。在隐藏键盘时将其visible设为false或opacity设为0并停用相关动画计时器。预编译 QML 文件对于发布版本可以使用qtquickcompiler将 QML 文件预编译为 C 代码这能显著提升加载速度和保护代码。根据项目规模加载速度提升可达30%-50%。在.pro文件中添加CONFIG qtquickcompiler即可启用。6.2 功能增强多语言与布局支持将键盘布局数据外部化为 JSON 或 XML 文件。在运行时根据用户选择加载不同的布局文件轻松支持美式键盘、英式键盘、德语键盘等。输入法引擎集成实现一个简单的输入法状态机用于中文拼音输入。这需要维护一个候选词列表并与系统的输入法状态同步在 Windows 上可通过ImmGetConversionStatus等 API。手势支持在触摸屏上可以实现滑动手势来移动光标、长按按键显示特殊字符如长按a显示à, á, â, ä等。系统托盘集成将应用最小化到系统托盘而不是直接关闭。使用QSystemTrayIcon类实现。6.3 代码结构与可维护性使用 QML Singleton 管理全局状态例如创建一个Settings.qml单例管理音效开关、透明度、键盘布局等全局设置避免属性在组件间层层传递。// Settings.qml pragma Singleton import QtQuick 2.15 QtObject { property bool soundEnabled: true property real windowOpacity: 0.9 property string currentLayout: “us“ }然后在任何 QML 文件中都可以通过Settings.soundEnabled直接访问。良好的组件接口设计为自定义组件如KeyButton设计清晰、完整的属性接口并做好文档注释。例如使用property alias暴露内部子组件的关键属性。分离样式与逻辑将颜色、尺寸、动画时长等样式相关的变量集中定义在FluentStyle.qml或Theme.qml中方便整体换肤。6.4 生产环境注意事项异常处理在 C 的sendKeyPress函数中增加更健壮的错误处理比如对无效keyCode的检查。安全考虑如果你的应用会以高权限运行需要谨慎处理接收到的所有输入避免注入攻击。对于屏幕键盘本身输入是安全的。无障碍支持为每个按键设置Accessible.name和Accessible.description属性让屏幕阅读器能够识别和播报这对于视障用户至关重要。跨平台测试务必在目标平台Windows, Linux, macOS上进行完整的 UI 和功能测试处理平台差异。开源这个项目只是一个起点。桌面应用的交互细节浩如烟海从精准的事件模拟到极致的性能优化每一步都值得深入探索。希望这个基于 QML 的 Win11 屏幕键盘项目不仅能作为你即拿即用的工具更能成为你学习现代桌面 UI 开发、理解声明式编程思想的一个扎实案例。代码已开源期待你的 Star、Fork 和宝贵的 Pull Request让我们一起让它变得更好。
返回列表