js 代码片段模拟 window.prompt 函数

众所周知,思源不支持 prompt,确切的说 Electron 不支持。

但有时写脚本时确实需要一些交互式操作,如果使用思源 api 调用 Dialog 确实可以实现,但终究是麻烦了些。

就自己动手写了一个 prompt 模拟函数,放到 js 代码片段中即可,然后需要的时候就可以直接调用了。

效果

Snipaste20240816091141.png

Snipaste20240816091253.png

接口定义如下

// 弹出prompt对话框
// 参数说明
// message 提示消息,类似标题
// defaultValue 输入框默认值
// okName 确认按钮名字
// cancelName 取消按钮名字
// width对话框宽度,默认400,高度根据内容自适应
// modalMode,是否模态窗口,模态窗口,除了当前对话框,其他地方无法点击,false非模态窗,true模态窗
// clickOverlayClose 是否点击遮罩层关闭对话框,仅在模态窗口下有效
function showPrompt(message = '', defaultValue = '', okName="确定", cancelName="取消", width=400, modalMode=true, clickOverlayClose=false);


// 弹出自定义prompt对话框(可自定义定义复杂表单)
// 参数说明
// message 提示消息,类似标题
// html 对话框自定义表单内容的HTML代码
// onSubmit 提交时的回调函数,对话框的返回值依赖于这个回调函数的返回值
// onOpen 打开窗口时的回调函数,可以在打开窗口话进行一些初始化操作,比如某个输入框设为焦点
// okName 确认按钮名字
// cancelName 取消按钮名字
// width 对话框宽度,默认400px,高度根据内容自适应
// modalMode,是否模态窗口,模态窗口,除了当前对话框,其他地方无法点击,false非模态窗,true模态窗
// clickOverlayClose 是否点击遮罩层关闭对话框,仅在模态窗口下有效
function showPromptForm(message = '', html = '', onSubmit = null, onOpen = null, okName="确定", cancelName="取消", width=400, modalMode=true, clickOverlayClose=false);


调用示例

// showPrompt 示例用法
const result = await showPrompt('这里是提示文本', '');
console.log(result);


// showPromptForm 示例用法
const result = await showPromptForm('这里是提示文本', `
  <input type="text" id="promptName" placeholder="标题" />
  <input type="text" id="promptHref" placeholder="链接" />
`, (promptContainer) => {
    const inputs = promptContainer.querySelectorAll('input[type="text"]');
    const values = Array.from(inputs).map(input => input.value);
    return values;
}, ()=> {
    document.getElementById("promptName").focus();
});
console.log(result);

完整代码如下

// 功能:模拟window.prompt函数
// 使用示例
/*
// showPrompt 示例用法
const result = await showPrompt('这里是提示文本', '');
console.log(result);

// showPromptForm 示例用法
const result = await showPromptForm('这里是提示文本', `
  <input type="text" id="promptName" placeholder="标题" />
  <input type="text" id="promptHref" placeholder="链接" />
`, (promptContainer) => {
    const inputs = promptContainer.querySelectorAll('input[type="text"]');
    const values = Array.from(inputs).map(input => input.value);
    return values;
}, ()=> {
    document.getElementById("promptName").focus();
});
console.log(result);
*/
(()=>{
    // 样式变量
    const dialogStyleText = `
    .prompt-dialog {
        display: none;
        position: fixed;
        z-index: 9999;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        padding: 20px;
        border: 1px solid var(--b3-theme-surface-lighter);
        border-radius: var(--b3-border-radius-b);
        box-shadow: var(--b3-dialog-shadow);
        width: 400px; /* 设置初始宽度 */
        text-align: left; /* 左对齐 */
        font-size: 100%;
        font-family: var(--b3-font-family);
        color: var(--b3-theme-on-background);
        background-color: var(--b3-theme-surface);
    }
    .prompt-overlay {
        display: none;
        position: fixed;
        z-index: 9998;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        /*background-color: rgba(0, 0, 0, 0);*/
        background-color: var(--b3-mask-background);
        /*backdrop-filter: blur(3px);*/
    }
    .prompt-dialog .prompt-text {
        margin-bottom: 10px;
    }
    .prompt-dialog .prompt-input-container {
        margin-bottom: 20px;
    }
    .prompt-dialog .prompt-input-container input[type="text"] {
        width: calc(100% - 16px); /* 输入框占满容器宽度 */
        padding: 4px 8px;
        line-height: 20px;
        margin-bottom: 10px;
        color: var(--b3-theme-on-background);
        transition: box-shadow 120ms 0ms cubic-bezier(0, 0, 0.2, 1);
        background-color: var(--b3-theme-background);
        border: 0;
        border-radius: var(--b3-border-radius);
        box-shadow: inset 0 0 0 .6px var(--b3-theme-on-surface);
    }
    .prompt-dialog .prompt-input-container input[type="text"]:hover{
        box-shadow: inset 0 0 0 .6px var(--b3-theme-on-background);
    }
    .prompt-dialog .prompt-input-container input[type="text"]:focus{
        box-shadow: inset 0 0 0 1px var(--b3-theme-primary), 0 0 0 3px var(--b3-theme-primary-lightest);
    }
    .prompt-dialog .prompt-input-container input[type="text"]:last-child {
        margin-bottom: 0;
    }
    .prompt-dialog .prompt-button-container {
        display: flex;
        justify-content: flex-end; /* 按钮靠右 */
    }
    .prompt-dialog button {
        margin-left: 10px;
        line-height: 20px;
        padding: 4px 12px;
        color: var(--b3-theme-primary);
        box-shadow: inset 0 0 0 .6px var(--b3-theme-primary);
        background-color: rgba(0, 0, 0, 0);
        transition: box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);
        border: 0;
        border-radius: var(--b3-border-radius);
    }
    .prompt-dialog button:focus, .prompt-dialog button:hover {
        background-color: var(--b3-theme-primary-lightest);
        box-shadow: inset 0 0 0 1px var(--b3-theme-primary);
        text-decoration: none;
    }
    @keyframes prompt-shake {
        0% { transform: translate(-50%, -50%) rotate(0deg); }
        25% { transform: translate(-50%, -50%) rotate(2deg); }
        50% { transform: translate(-50%, -50%) rotate(0deg); }
        75% { transform: translate(-50%, -50%) rotate(-2deg); }
        100% { transform: translate(-50%, -50%) rotate(0deg); }
    }
    .prompt-dialog.prompt-shake {
        animation: prompt-shake 0.5s ease-in-out;
    }
  `;
    // HTML模板
    const dialogHtml = `
    <div class="prompt-text" id="promptText">请输入</div>
    <div class="prompt-input-container">
      <input type="text" id="promptInput" />
    </div>
    <div class="prompt-button-container">
      <button id="promptCancel">取消</button>
      <button id="promptSubmit">确认</button>
    </div>
  `;
    // 创建样式
    const dialogStyle = document.createElement('style');
    dialogStyle.textContent = dialogStyleText;
    document.head.appendChild(dialogStyle);
    // 创建对话框
    const dialog = document.createElement('div');
    dialog.className = 'prompt-dialog';
    dialog.id = 'promptDialog';
    dialog.innerHTML = dialogHtml;
    document.body.appendChild(dialog);
    // 创建遮罩层
    const overlay = document.createElement('div');
    overlay.className = 'prompt-overlay';
    document.body.appendChild(overlay);
    // 获取元素
    const promptContainer = document.querySelector(".prompt-input-container");
    const submitButton = document.getElementById('promptSubmit');
    const cancelButton = document.getElementById('promptCancel');
    const promptText = document.getElementById('promptText');
    let focusEl;
    // 弹出对话框
    function showPrompt(message = '', defaultValue = '', okName="确定", cancelName="取消", width=400, modalMode=true, clickOverlayClose=false) {
        return new Promise((resolve, reject) => {
            // 初始化窗口
            dialog.style.width = (width || 400) + 'px';
            promptText.textContent = message;
            let input = document.getElementById('promptInput');
            if(!input) {
                promptContainer.innerHTML = `<input type="text" id="promptInput" />`;
                input = document.getElementById('promptInput');
            }
            input.value = defaultValue || '';
            submitButton.textContent = okName || '确定';
            cancelButton.textContent = cancelName || '取消';
            if(!cancelName) cancelButton.style.display = 'none';
            dialog.style.display = 'block';
            if(modalMode) overlay.style.display = 'block';
            input.focus();
            focusEl = promptContainer.querySelector(":focus");
            // 监听按钮点击事件
            submitButton.addEventListener('click', () => {
                dialog.style.display = 'none';
                if(modalMode) overlay.style.display = 'none';
                document.removeEventListener('keydown', handleKeydown);
                resolve(input.value);
            });
            cancelButton.addEventListener('click', () => {
                dialog.style.display = 'none';
                if(modalMode) overlay.style.display = 'none';
                document.removeEventListener('keydown', handleKeydown);
                resolve(null);
            });
            // 添加键盘事件监听器
            const handleKeydown = () => {
                if (event.key === 'Escape') {
                    cancelButton.click();
                } else if (event.key === 'Enter') {
                    submitButton.click();
                }
            };
            document.addEventListener('keydown', handleKeydown);
            // 监听焦点事件,focus冒泡必须第三参数是true
            promptContainer.addEventListener('focus', (event) => {
                focusEl = event.target;
            }, true);
            // 添加点击遮罩层关闭对话框的功能
            if(modalMode){
                overlay.addEventListener('click', () => {
                    // 点击遮罩层关闭弹窗
                    if(clickOverlayClose) cancelButton.click();
                    // 重新获取焦点
                    if(focusEl) focusEl.focus();
                    // 添加抖动类
                    dialog.classList.add('prompt-shake');
                    // 移除抖动类
                    setTimeout(() => {
                        dialog.classList.remove('prompt-shake');
                    }, 500);
                });
            }
        });
    }
    // 弹出对话框表单
    function showPromptForm(message = '', html = '', onSubmit = null, onOpen = null, okName="确定", cancelName="取消", width=400, modalMode=true, clickOverlayClose=false) {
        return new Promise((resolve, reject) => {
            // 初始化窗口
            dialog.style.width = (width || 400) + 'px';
            if(html) promptContainer.innerHTML = html;
            promptText.textContent = message;
            submitButton.textContent = okName || '确定';
            cancelButton.textContent = cancelName || '取消';
            if(!cancelName) cancelButton.style.display = 'none';
            dialog.style.display = 'block';
            if(modalMode) overlay.style.display = 'block';
            if(typeof onOpen === 'function') onOpen(promptContainer);
            focusEl = promptContainer.querySelector(":focus");
            // 监听按钮点击事件
            submitButton.addEventListener('click', () => {
                dialog.style.display = 'none';
                if(modalMode) overlay.style.display = 'none';
                document.removeEventListener('keydown', handleKeydown);
                if(typeof onSubmit === 'function') {
                    resolve(onSubmit(promptContainer));
                } else {
                    resolve(undefined);
                }
            });
            cancelButton.addEventListener('click', () => {
                dialog.style.display = 'none';
                if(modalMode) overlay.style.display = 'none';
                document.removeEventListener('keydown', handleKeydown);
                resolve(null);
            });
            // 添加键盘事件监听器
            const handleKeydown = () => {
                if (event.key === 'Escape') {
                    cancelButton.click();
                } else if (event.key === 'Enter') {
                    submitButton.click();
                }
            };
            document.addEventListener('keydown', handleKeydown);
            // 监听焦点事件,focus冒泡必须第三参数是true
            promptContainer.addEventListener('focus', (event) => {
                focusEl = event.target;
            }, true);
            // 添加点击遮罩层关闭对话框的功能
            if(modalMode){
                overlay.addEventListener('click', () => {
                    // 点击遮罩层关闭弹窗
                    if(clickOverlayClose) cancelButton.click();
                    // 重新获取焦点
                    if(focusEl) focusEl.focus();
                    // 添加抖动类
                    dialog.classList.add('prompt-shake');
                    // 移除抖动类
                    setTimeout(() => {
                        dialog.classList.remove('prompt-shake');
                    }, 500);
                });
            }
        });
    }

    // 输出到全局变量
    window.showPrompt = showPrompt;
    window.showPromptForm = showPromptForm;
})();

注意事项

  1. showPromptshowPromptForm 的区别,见上面的效果图,上面一个是 showPrompt,这个是对 window.prompt 的模拟,下面一个是 showPromptForm,是对 showPrompt 的扩展,可以用于实现复杂的表单功能。
  2. 默认使用模态模式,如果不使用模态(即不使用遮罩层),调用时把 modalMode 参数设置为 false 即可。
  3. 模态窗口的背景色默认和思源保持一致,如果你想修改背景色,可以修改样式.prompt-overlay 的 background-color: var(--b3-mask-background); 这行代码。
  4. 模态窗口的宽度默认是 400px,如果想自定义宽度可通过 width 参数设置。
  5. 如果想支持 点击遮罩层关闭对话框的功能,把 clickOverlayClose 参数设置为 true 即可。
  6. 点确认按钮返回值是字符串,点取消返回值是 null,但 showPromptForm,点确定的返回值是自己定义的。
  7. 按钮文字可通过 cancelNameokName 参数设置。
  8. 更多功能请参考源码。
  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    22026 引用 • 87852 回帖 • 4 关注
  • 教程
    143 引用 • 597 回帖 • 8 关注
  • 代码
    466 引用 • 631 回帖 • 9 关注
  • JavaScript

    JavaScript 一种动态类型、弱类型、基于原型的直译式脚本语言,内置支持类型。它的解释器被称为 JavaScript 引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在 HTML 网页上使用,用来给 HTML 网页增加动态功能。

    728 引用 • 1326 回帖
3 操作
wilsons 在 2024-08-16 13:36:41 更新了该帖
wilsons 在 2024-08-16 09:41:46 更新了该帖
wilsons 在 2024-08-15 18:58:57 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • vincents

    感谢大佬分享 👍 ,下面这样也可以,要安装 open-api 插件,不过简单调用还是你这个方便些。

    const dialog = new openAPI.siyuan.Dialog({
        title: '这里是标题',
        content: `
        <div class="b3-dialog__content">
            <div class="ft__breakword">
                <input type="text" id="ArticleTitle" class="b3-text-field fn__block" style="height: 100%;margin-bottom: 20px;" placeholder='输入标题'>
                <textarea id="ArticleContent" class="b3-text-field fn__block" style="height: 100%;" placeholder='输入内容'></textarea>
            </div>
            <div class="b3-dialog__action" style="padding-right: 0;padding-top: 12px;">
                <button class="b3-button b3-button--cancel" id="CancelBtn">取消</button><div class="fn__space"></div>
                <button class="b3-button b3-button--text" id="ConfirmBtn">确定</button>
            </div>
        </div>
        `,
        width: "500px",
        height: "220px"
    });
    dialog.element.querySelector("#ArticleTitle").focus();
    dialog.element.querySelector("#CancelBtn").addEventListener("click", () => {
        console.log('cancel');
        dialog.destroy();
    });
    dialog.element.querySelector("#ConfirmBtn").addEventListener("click", () => {
        console.log('confirm');
        console.log(dialog.element.querySelector("#ArticleTitle").value);
        console.log(dialog.element.querySelector("#ArticleContent").value);
        dialog.destroy();
    });
    

    dialogdemo.png

    1 回复
  • 其他回帖
  • wilsons

    感谢分享!

    我应该把 prompt api 定义罗列下,这样看起来更清晰

    // 弹出prompt对话框
    // 参数说明
    // message 提示消息,类似标题
    // defaultValue 输入框默认值
    // okName 确认按钮名字
    // cancelName 取消按钮名字
    // width对话框宽度,默认400,高度根据内容自适应
    // modalMode,是否模态窗口,模态窗口,除了当前对话框,其他地方无法点击,false非模态窗,true模态窗
    // clickOverlayClose 是否点击遮罩层关闭对话框,仅在模态窗口下有效
    function showPrompt(message = '', defaultValue = '', okName="确定", cancelName="取消", width=400, modalMode=true, clickOverlayClose=false);
    
    // 弹出自定义prompt对话框
    // 参数说明
    // message 提示消息,类似标题
    // html 对话框自定义表单内容的HTML代码
    // onSubmit 提交时的回调函数,对话框的返回值依赖于这个回调函数的返回值
    // onOpen 打开窗口时的回调函数,可以在打开窗口话进行一些初始化操作,比如某个输入框设为焦点
    // okName 确认按钮名字
    // cancelName 取消按钮名字
    // width 对话框宽度,默认400px,高度根据内容自适应
    // modalMode,是否模态窗口,模态窗口,除了当前对话框,其他地方无法点击,false非模态窗,true模态窗
    // clickOverlayClose 是否点击遮罩层关闭对话框,仅在模态窗口下有效
    function showPromptForm(message = '', html = '', onSubmit = null, onOpen = null, okName="确定", cancelName="取消", width=400, modalMode=true, clickOverlayClose=false);
    
    

推荐标签 标签

  • 锤子科技

    锤子科技(Smartisan)成立于 2012 年 5 月,是一家制造移动互联网终端设备的公司,公司的使命是用完美主义的工匠精神,打造用户体验一流的数码消费类产品(智能手机为主),改善人们的生活质量。

    4 引用 • 31 回帖 • 2 关注
  • 正则表达式

    正则表达式(Regular Expression)使用单个字符串来描述、匹配一系列遵循某个句法规则的字符串。

    31 引用 • 94 回帖
  • Linux

    Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 Unix 的多用户、多任务、支持多线程和多 CPU 的操作系统。它能运行主要的 Unix 工具软件、应用程序和网络协议,并支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

    939 引用 • 940 回帖
  • 区块链

    区块链是分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式。所谓共识机制是区块链系统中实现不同节点之间建立信任、获取权益的数学算法 。

    91 引用 • 751 回帖 • 4 关注
  • App

    App(应用程序,Application 的缩写)一般指手机软件。

    91 引用 • 384 回帖 • 1 关注
  • Python

    Python 是一种面向对象、直译式电脑编程语言,具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。

    541 引用 • 672 回帖 • 1 关注
  • Quicker

    Quicker 您的指尖工具箱!操作更少,收获更多!

    30 引用 • 123 回帖
  • AngularJS

    AngularJS 诞生于 2009 年,由 Misko Hevery 等人创建,后为 Google 所收购。是一款优秀的前端 JS 框架,已经被用于 Google 的多款产品当中。AngularJS 有着诸多特性,最为核心的是:MVC、模块化、自动化双向数据绑定、语义化标签、依赖注入等。2.0 版本后已经改名为 Angular。

    12 引用 • 50 回帖 • 474 关注
  • Ruby

    Ruby 是一种开源的面向对象程序设计的服务器端脚本语言,在 20 世纪 90 年代中期由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)设计并开发。在 Ruby 社区,松本也被称为马茨(Matz)。

    7 引用 • 31 回帖 • 214 关注
  • 面试

    面试造航母,上班拧螺丝。多面试,少加班。

    325 引用 • 1395 回帖
  • TGIF

    Thank God It's Friday! 感谢老天,总算到星期五啦!

    287 引用 • 4484 回帖 • 668 关注
  • abitmean

    有点意思就行了

    30 关注
  • 创造

    你创造的作品可能会帮助到很多人,如果是开源项目的话就更赞了!

    176 引用 • 995 回帖
  • Hibernate

    Hibernate 是一个开放源代码的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,使得 Java 程序员可以随心所欲的使用对象编程思维来操纵数据库。

    39 引用 • 103 回帖 • 706 关注
  • OAuth

    OAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 oAuth 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 oAuth 是安全的。oAuth 是 Open Authorization 的简写。

    36 引用 • 103 回帖 • 1 关注
  • GAE

    Google App Engine(GAE)是 Google 管理的数据中心中用于 WEB 应用程序的开发和托管的平台。2008 年 4 月 发布第一个测试版本。目前支持 Python、Java 和 Go 开发部署。全球已有数十万的开发者在其上开发了众多的应用。

    14 引用 • 42 回帖 • 753 关注
  • JetBrains

    JetBrains 是一家捷克的软件开发公司,该公司位于捷克的布拉格,并在俄国的圣彼得堡及美国麻州波士顿都设有办公室,该公司最为人所熟知的产品是 Java 编程语言开发撰写时所用的集成开发环境:IntelliJ IDEA

    18 引用 • 54 回帖
  • OpenShift

    红帽提供的 PaaS 云,支持多种编程语言,为开发人员提供了更为灵活的框架、存储选择。

    14 引用 • 20 回帖 • 624 关注
  • Netty

    Netty 是一个基于 NIO 的客户端-服务器编程框架,使用 Netty 可以让你快速、简单地开发出一个可维护、高性能的网络应用,例如实现了某种协议的客户、服务端应用。

    49 引用 • 33 回帖 • 19 关注
  • 七牛云

    七牛云是国内领先的企业级公有云服务商,致力于打造以数据为核心的场景化 PaaS 服务。围绕富媒体场景,七牛先后推出了对象存储,融合 CDN 加速,数据通用处理,内容反垃圾服务,以及直播云服务等。

    26 引用 • 222 回帖 • 165 关注
  • MyBatis

    MyBatis 本是 Apache 软件基金会 的一个开源项目 iBatis,2010 年这个项目由 Apache 软件基金会迁移到了 google code,并且改名为 MyBatis ,2013 年 11 月再次迁移到了 GitHub。

    170 引用 • 414 回帖 • 382 关注
  • LaTeX

    LaTeX(音译“拉泰赫”)是一种基于 ΤΕΧ 的排版系统,由美国计算机学家莱斯利·兰伯特(Leslie Lamport)在 20 世纪 80 年代初期开发,利用这种格式,即使使用者没有排版和程序设计的知识也可以充分发挥由 TeX 所提供的强大功能,能在几天,甚至几小时内生成很多具有书籍质量的印刷品。对于生成复杂表格和数学公式,这一点表现得尤为突出。因此它非常适用于生成高印刷质量的科技和数学类文档。

    12 引用 • 53 回帖 • 82 关注
  • Docker

    Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的操作系统上。容器完全使用沙箱机制,几乎没有性能开销,可以很容易地在机器和数据中心中运行。

    490 引用 • 916 回帖 • 1 关注
  • IDEA

    IDEA 全称 IntelliJ IDEA,是一款 Java 语言开发的集成环境,在业界被公认为最好的 Java 开发工具之一。IDEA 是 JetBrains 公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主。

    180 引用 • 400 回帖 • 3 关注
  • RabbitMQ

    RabbitMQ 是一个开源的 AMQP 实现,服务器端用 Erlang 语言编写,支持多种语言客户端,如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    49 引用 • 60 回帖 • 367 关注
  • 职场

    找到自己的位置,萌新烦恼少。

    127 引用 • 1705 回帖 • 1 关注
  • 负能量

    上帝为你关上了一扇门,然后就去睡觉了....努力不一定能成功,但不努力一定很轻松 (° ー °〃)

    88 引用 • 1235 回帖 • 406 关注