Qt-Web混合开发-QtWebChannel实现Qt与Web通信交互-进阶功能(6)

news2025/1/16 5:46:11

Qt-Web混合开发-QtWebChannel实现Qt与Web通信交互-进阶功能🥬

文章目录

  • Qt-Web混合开发-QtWebChannel实现Qt与Web通信交互-进阶功能🥬
    • 1、概述🌽
    • 2、实现效果🍆
    • 3、实现功能🍒
    • 4、关键代码🥝
    • 5、源代码🥔

更多精彩内容
👉个人内容分类汇总 👈
👉Qt - Web混合开发👈

1、概述🌽

  • Qt版本:V5.12.5

  • 注意:windows下webenginewidgets只支持MSVC编译器,不支持MinGW(mingw好像需要自己编译);

  • 当使用QWebEngineView实现Qt + html混合开发时经常遇见的问题就是Qt自身有信号槽,但是和web网页怎么通信,web里又没有信号槽,第一时间想到的是socket通信,但是Qt其实封装了更加简单便捷的通信方式,可使用QtWebChannelqwebchannel.js实现Qt和web通信;

  1. QWebChannel支持的功能:

    1. 将JavaScript函数【绑定到Qt的信号】(类似Qt信号槽);
    2. 在JavaScript中异步调用QWebChannel::registerObject()注册的对象对象的【槽函数】(必须是public slots:声明的);
    3. 在JavaScript中调用QWebChannel::registerObject()注册的对象中带【返回值的槽函数】,并通过回调函数获取Qt槽函数的返回值
    4. 在JavaScript中指定读取/修改QWebChannel::registerObject()注册的对象中使用Q_PROPERTY定义的【属性值】;
    5. 在JavaScript中指定读取QWebChannel::registerObject()注册的对象中使用Q_ENUM标记的【枚举】。
  2. 前面讲了Qt使用 QWebChannel实现与Web中的javascript通信的简单示例,但是如果需要传递复杂数据该怎么实现呢,总不能所有数据都用字符串吧;

  3. 其实可以转换为JSON的数据类型QWebChannel都支持,而JSON支持下列6种数据类型,对应到Qt中可以使用5种类型,其它各种类型的数据,例如QByteArray,QImage、自定义结构体等数据类型都需要自己转换为JSON数据格式再通过QWebChannel传递;

    JSON支持类型Qt中对应的数据类型
    逻辑值(true 或 false)bool
    数字(整数或浮点数)int、int64、double等数字类型都可以
    字符串(在双引号中)QString
    数组(在中括号中)QJsonArray
    对象(在大括号中)QJsonObject
    null

2、实现效果🍆

在这里插入图片描述

3、实现功能🍒

  1. 构建后将html、css、js文件自动拷贝到可执行程序路径下;
  2. web界面和qt界面实现双向通信;
  3. 由于QWebChannel传递数据只有可以转换为【JSON的数据类型】才可以传递,其它类型无法传递,例如QByteArray这些JSON不支持的数据类型,这里演示了可以传递的所有数据类型的使用方式;
  4. Web界面中javascript直接读取Qt中注册对象使用Q_PROPERTY定义的【属性值】;
  5. Web界面中javascript直接读取Qt中注册对象使用使用Q_ENUM标记的【枚举】;
  6. 定义一个带有返回值的槽函数,javascript调用该函数后可以获取【返回值】;

4、关键代码🥝

  • pro文件:INSTALLS 用法
QT += webenginewidgets webchannel   # 使用QWebEngineView和QWebchannel需要加载模块

# 程序编译后需要使用nmake install(msvc)或make install (linux)将web2文件夹拷贝到当前路径下,或者自己手动拷贝
webFile.path = $$path
webFile.files = $$PWD/web2
INSTALLS += webFile      # 将web文件夹拷贝到path路径下,需要配置Custom Process Step: nmake install才生效
  • webClient.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Web客户端</title>
<!--  qwebchannel.js文件一般在Qt安装路径下 D:\Qt\Qt5.12.5\Examples\Qt-5.12.5\webchannel\shared\qwebchannel.js-->
    <script src="qwebchannel.js"></script>
    <script src="main.js"></script>
    <link rel="stylesheet" type="text/css" href="main.css">
</head>
<body>
    <h1 align="center">Web客户端程序 </h1>
    <div align="center">
        <textarea id="textAreaId" name="textArea"></textarea> </br>

        <button id="but_bool" class="button" onclick="butClick_bool()">bool类型</button>
        <button id="but_double" class="button" onclick="butClick_double()">double类型</button>
        <button id="but_str" class="button" onclick="butClick_str()">string类型</button>
        <button id="but_arr" class="button" onclick="butClick_arr()">array类型</button>
        <button id="but_obj" class="button" onclick="butClick_obj()">object类型</button> </br>
        
        <button id="but_Property" class="button" onclick="butClick_Property()">读取并修改Qt对象中的属性</button>
        <button id="but_enum" class="button" onclick="butClick_enum()">读取Q_ENUM标记的枚举</button>
        <button id="but_return" class="button" onclick="butClick_return()">调用带返回值的Qt函数</button>
    </div>
</body>
</html>

  • main.js
/**
 * 程序启动立即初始化
 */
window.onload = function()
{
    if(typeof qt != "undefined")
    {
        window.channel = new QWebChannel(qt.webChannelTransport, function(channel)
        {
            // 获取Qt注册的对象,Qt中registerObject注册的字符串
            window.core = channel.objects.CoreId;

            // 将函数showText和Qt信号toWeb***()绑定
            core.toWebBool.connect(function(msg)
            {
                showText(msg);
            });
            core.toWebDouble.connect(function(msg)
            {
                showText(msg);
            });
            core.toWebString.connect(function(msg)
            {
                showText(msg);
            });
            core.toWebJsonArray.connect(function(msg)
            {
                showText(msg);
            });
            core.toWebJsonObject.connect(function(msg)
            {
                showText(msg);
            });
        });
    }
    else
    {
        alert("qt对象未获取到!");
    }
}

/**
 * 显示Qt发给Web的信息
 * @param {*} msg 
 */
function showText(msg)
{
    var textEdit = document.getElementById("textAreaId");
    if(textEdit)
    {
        // 由于typeof 不能分辨出数组和对象,所以这里使用构造器来判断
        if(msg.constructor === Array)         // 数组类型
        {
            textEdit.value = textEdit.value + "Array:" + msg+ '\n';                    // 追加信息
        }
        else if(msg.constructor === Object)  // 对象类型
        {
            var str = msg.key1 + " " + msg.key2+ " " + msg.key3;
            textEdit.value = textEdit.value + "Object:" + str+ '\n';                   // 追加信息
        }
        else
        {
            textEdit.value = textEdit.value + typeof msg  + ":" + msg+ '\n';           // 追加信息
        }
        
        textEdit.scrollTop = textEdit.scrollHeight;             // 滚动条一直再最下方
    }
}

/**
 * html中按键点击时调用这个函数将信号发送给Qt
 */
function butClick_bool()
{
    core.on_toQtBool(true);    
}
function butClick_double()
{
    core.on_toQtDouble(123.321);    
}
function butClick_str()
{
    core.on_toQtString("Web 按键点击");    
}
function butClick_arr()
{
    var arr = [1, 2, "123"];
    core.on_toQtJsonArray(arr);   
}
function butClick_obj()
{
    var obj = {
        key1 : 123,
        key2 : 321.1,
        key3 : "abc"
    };
    core.on_toQtJsonObject(obj);   
}

/**
 * 点击按键后直接读取Qt中注册对象Core中使用Q_PROPERTY定义的属性值并修改
 */
function butClick_Property()
{
    var textEdit = document.getElementById("textAreaId");
    if(textEdit)
    {
        textEdit.value = textEdit.value + "Qt对象属性:" + core.value + '\n';              // 读取Qt中使用Q_PROPERTY定义的属性值
        textEdit.scrollTop = textEdit.scrollHeight;             // 滚动条一直再最下方
        core.value++;                                                                     // 修改Qt中使用Q_PROPERTY定义的属性值
    }
}

/**
 * 直接访问Qt中使用Q_ENUM标记的枚举
 */
function butClick_enum()
{
    var textEdit = document.getElementById("textAreaId");
    if(textEdit)
    {
        textEdit.value = textEdit.value + "Qt枚举值:" + core.CoreEnum.Value1 + '\n';  
        textEdit.scrollTop = textEdit.scrollHeight;             // 滚动条一直再最下方
    }
}

/**
 * 在javascript中调用Qt函数后【获取返回值】
 */
function butClick_return()
{
    core.on_returnValue(123, function(returnValue)  // 这里使用回调函数获取返回值
    {
        var textEdit = document.getElementById("textAreaId");
        if(textEdit)
        {
            textEdit.value = textEdit.value + "Qt函数返回值:" + returnValue + '\n';  
            textEdit.scrollTop = textEdit.scrollHeight;             // 滚动条一直再最下方
        }
    });
}
  • widget.h:注意,这里的 Core 类是Qt和Js通信的关键;
#ifndef WIDGET_H
#define WIDGET_H

#include <QJsonArray>
#include <QJsonObject>
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    void showText(QString str);

private slots:
    void on_toQtBool(bool value)             ;
    void on_toQtDouble(double value)         ;
    void on_toQtString(QString value)        ;
    void on_toQtJsonArray(QJsonArray value)  ;
    void on_toQtJsonObject(QJsonObject value);

    void on_but_bool_clicked();

    void on_but_double_clicked();

    void on_but_str_clicked();

    void on_but_array_clicked();

    void on_but_object_clicked();

private:
    Ui::Widget *ui;
};

/**
 * @brief  Qt和Web端交互的中介单例类
 */
class Core : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)  // 定义一个属性,javascript可以读取属性值

public:
    enum CoreEnum
    {
        Value1 = 100,
        Value2
    };
    Q_ENUM(CoreEnum)    // 使用Q_ENUM标记的枚举,javascript可以直接访问

public:
    static Core* getInstance()
    {
        static Core core;
        return &core;
    }

    int value() {return m_value;}
    void setValue(int v) {m_value = v;}

signals:
    void valueChanged();
    /**
     * @brief     Qt发送给Web的信号
     * @param str
     */
    void toWebBool(bool value);
    void toWebDouble(double value);
    void toWebString(QString value);
    void toWebJsonArray(QJsonArray value);
    void toWebJsonObject(QJsonObject value);

    /**
     * @brief     Web发送给Qt的信号
     * @param str
     */
    void toQtBool(bool value);
    void toQtDouble(double value);
    void toQtString(QString value);
    void toQtJsonArray(QJsonArray value);
    void toQtJsonObject(QJsonObject value);

public slots:
    /**
     * @brief     Web端需要调用Qt槽函数来传递,必须声明为public slots,否则web找不到
     * @param str
     */
    void on_toQtBool(bool value)              {emit toQtBool(value);}
    void on_toQtDouble(double value)          {emit toQtDouble(value);}
    void on_toQtString(QString value)         {emit toQtString(value);}
    void on_toQtJsonArray(QJsonArray value)   {emit toQtJsonArray(value);}
    void on_toQtJsonObject(QJsonObject value) {emit toQtJsonObject(value);}

    /**
     * @brief        定义一个带有返回值的槽函数,javascript调用该函数后可以获取返回值
     * @param value
     * @return
     */
    QString on_returnValue(int value);
private:
    int m_value = 10;
};

#endif // WIDGET_H

  • widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QJsonDocument>
#include <QTime>
#include <qdir.h>
#include <qwebchannel.h>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->setWindowTitle(QString("使用QtWebChannel实现Qt与Web通信交互(2),进阶功能 - V%1").arg(APP_VERSION));  // 设置窗口标题

    QWebChannel* channel = new QWebChannel(this);
    channel->registerObject("CoreId", Core::getInstance());  // 向QWebChannel注册用于Qt和Web交互的对象。

    ui->webEngineView->page()->setWebChannel(channel);       // 将与webEngineView要使用的web通道实例设置为channel
    ui->webEngineView->setUrl(QDir("./web2/webClient.html").absolutePath());

    // 绑定槽函数,接收web界面中javascript传递过来的信号
    connect(Core::getInstance(), &Core::toQtBool, this, &Widget::on_toQtBool);
    connect(Core::getInstance(), &Core::toQtDouble, this, &Widget::on_toQtDouble);
    connect(Core::getInstance(), &Core::toQtString, this, &Widget::on_toQtString);
    connect(Core::getInstance(), &Core::toQtJsonArray, this, &Widget::on_toQtJsonArray);
    connect(Core::getInstance(), &Core::toQtJsonObject, this, &Widget::on_toQtJsonObject);
}

Widget::~Widget()
{
    delete ui;
}

/**
 * @brief       显示发送的数据
 * @param str
 */
void Widget::showText(QString str)
{
    ui->textEdit->append(QString("发送:%1").arg(str));
}

/**
 * @brief       显示Web发送给Qt的数据
 * @param value
 */
void Widget::on_toQtBool(bool value)
{
    ui->textEdit->append(QString("Bool类型数据:%1").arg(value ? "true" : "false"));
}

void Widget::on_toQtDouble(double value)
{
    ui->textEdit->append(QString("double类型数据:%1").arg(value));
}

void Widget::on_toQtString(QString value)
{
    ui->textEdit->append(QString("QString类型数据:%1").arg(value));
}

void Widget::on_toQtJsonArray(QJsonArray value)
{
    QJsonDocument doc;
    doc.setArray(value);
    ui->textEdit->append(QString("QJsonArray类型数据:%1").arg(doc.toJson().data()));
}

void Widget::on_toQtJsonObject(QJsonObject value)
{
    QJsonDocument doc;
    doc.setObject(value);
    ui->textEdit->append(QString("QJsonArray类型数据:%1").arg(doc.toJson().data()));
}


/**
 * @brief 发送Bool类型数据
 */
void Widget::on_but_bool_clicked()
{
    static bool value = true;
    value = !value;
    emit Core::getInstance()->toWebBool(value);

    showText(QString("%1").arg(value ? "true" : "false"));
}

/**
 * @brief 发送double类型数据
 */
void Widget::on_but_double_clicked()
{
    double value = QTime::currentTime().msec() / 50.0;
    emit Core::getInstance()->toWebDouble(value);

    showText(QString("%1").arg(value));
}

/**
 * @brief 发送字符串类型数据
 */
void Widget::on_but_str_clicked()
{
    double value = QTime::currentTime().msec() / 50.0;
    emit Core::getInstance()->toWebString(QString("%1").arg(value));

    showText(QString("%1").arg(value));
}

/**
 * @brief 发送Json数组类型数据
 */
void Widget::on_but_array_clicked()
{
    double value = QTime::currentTime().msec() / 50.0;
    QJsonArray array = {value, value / 10.0, QString("%1").arg(value / 20.0)};
    emit Core::getInstance()->toWebJsonArray(array);

    QJsonDocument doc;
    doc.setArray(array);
    showText(QString("%1").arg(doc.toJson().data()));
}

/**
 * @brief 发送json对象类型数据
 */
void Widget::on_but_object_clicked()
{
    double value = QTime::currentTime().msec() / 50.0;
    QJsonObject obj;
    obj.insert("key1", value);
    obj.insert("key2", value / 30.0);
    obj.insert("key3", QString("%1").arg(value / 40.0));
    emit Core::getInstance()->toWebJsonObject(obj);

    QJsonDocument doc;
    doc.setObject(obj);
    showText(QString("%1").arg(doc.toJson().data()));
}

/**
 * @brief        带返回值的槽函数,将返回值传递给javascript
 * @param value
 * @return
 */
QString Core::on_returnValue(int value)
{
    qDebug() << "调用Qt槽函数,并返回值";
    return QString("调用成功:%1").arg(value);
}

5、源代码🥔

  • gitee
  • github

🌙`、、`ヽ`ヽ`、、ヽヽ、`、ヽ`ヽ`ヽヽ`
ヽ`、`ヽ`、ヽ``、ヽ`ヽ`、ヽヽ`ヽ、ヽ
`ヽ、ヽヽ`ヽ`、``ヽ`ヽ、ヽ、ヽ`ヽ`ヽ
、ヽ`ヽ`、ヽヽ``、ヽ`、ヽヽ 🚶‍♀ ヽ``ヽ`

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/93949.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android基础学习(二十二)—— View的事件分发(1)

一、View的层级关系 二、View的事件分发机制 1、MotionEvent ——点击事件 点击事件用MotionEvent来表示 ACTION_DOWN&#xff1a;手指刚接触屏幕 ACTION_MOVE&#xff1a;手指在屏幕上移动 ACTION_UP&#xff1a;手指从屏幕上松开的一瞬间 点击事件的事件分发&#xff0…

OM6621系列国产M4F内核低功耗BLE5.1大内存SoC蓝牙芯片

目录OM6621系列简介OM6621P系列芯片特性应用领域OM6621系列简介 随着5G与物联网时代的到来&#xff0c;智慧城市、电动出行、智能家居、可穿戴设备等应用高速发展&#xff0c;低功耗蓝牙技术在近几年智能化浪潮中的地位也尤为重要。OM6621系列的开发即是为解决国内低功耗蓝牙应…

Linux安装docker 保姆级教程

一、docker介绍 Docker 是 2014 年最为火爆的技术之一&#xff0c;几乎所有的程序员都听说过它。Docker 是一种“轻量级”容器技术&#xff0c;它几乎动摇了传统虚拟化技术的地位&#xff0c;现在国内外已经有越来越多的公司开始逐步使用 Docker 来替换现有的虚拟化平台了。 二…

图为科技深圳人工智能产业协会重磅推出边缘计算机全新概念

人工智能作为提升区域竞争力的重要战略&#xff0c;全国各地都在推动发展&#xff0c;人工智能是未来科技创新发展的风向标&#xff0c;也是产业变革升级的关键驱动力&#xff0c;我国在《“十四五”数字经济发展规划》及《工业互联网创新发展行动计划(2021-2023年)》中&#x…

Linux基础(4)-进程管理

该文章主要为完成实训任务&#xff0c;详细实现过程及结果见【参考文章】 参考文章&#xff1a;https://howard2005.blog.csdn.net/article/details/127066383?spm1001.2014.3001.5502 文章目录一、查看进程1. 进程查看命令 - ps2. Liunx进程状态3. 观察进程变化命令 - top4. …

b站黑马的Vue快速入门案例代码——【axios+Vue】天知道(天气信息查询功能)

目录 目标效果&#xff1a; 更换的新接口接口文档&#xff1a; 天知道新的Get请求接口:http://ajax-api.itheima.net/api/weather html文件中注意因为接口更换&#xff0c;要修改原代码为如下红字部分&#xff1a; 重点原理&#xff1a; &#xff08;1&#xff09;v-on可以…

环形链表问题

文章目录环形链表问题1.环形链表题干思路延申问题总结2. 环形链表 II题干思路环形链表问题 环形链表就是一个链表没有结束的位置&#xff0c;链表的最后一个节点它会指向链表中的某一个节点形成一个环。 拿力扣的两到题目来看 1.环形链表 题干 给你一个链表的头节点 head …

JavaScript JSON解析

最近在uniapp中遇到了一个bug&#xff0c;排查后是json解析的问题。对uniapp开发比较熟悉的&#xff0c;应该会知道uni.navigateTo 这个API方法。这是官方提供用于跳转页面的方法。 有时候我们在跳转页面时会想传递一些参数&#xff0c;通常采用这样的方式 navigateTo(url, r…

oauth2.0--基础--6.1--SSO的实现原理

oauth2.0–基础–6.1–SSO的实现原理 1、什么是SSO 1.1、概念 在一个 多系统共存 的环境下&#xff0c;用户在一处登录后&#xff0c;就不用在其他系统中登录&#xff0c;就可以访问其他系统的资源。用户环境 浏览器&#xff1a;只能同一个浏览器&#xff0c;不会出现A浏览器…

zabbix部署【各模块超详细】

目录 安装zabbix 部署zabbix 配置zabbix 1. 修改语言 2. 监控linux端 3. 修改中文乱码 报警功能 报警音报警 邮件报警 脚本报警 邮件通知内容 图形模块 创建图形 创建聚合图形 percona mysql模板 nginx模板 克隆主机 网络发现 自动注册 主被动模式 &#x1f341;如果对你有帮助…

Handsontable复制列标题内容的功能

Handsontable复制列标题内容的功能 添加了通过使用3个新的上下文菜单选项复制列标题内容的功能&#xff1a;“使用标题复制”、“使用组标题复制”和“仅复制标题”。 添加了4个用于以编程方式复制列标题的新API方法&#xff1a;“copyCellsOnly()”、“copyWithColumnHeaders(…

vscode jupyter配置远程服务器开发

背景说明&#xff1a;本地vscode中使用jupyter编写本地python代码很方便&#xff0c;各种快捷键用的飞起。但是要做线上大数据分析时。在集群环境中搭建一个jupyter。使用网页端编写程序非常不习惯&#xff0c;所以想到能不能将线上的jupyter接口开出来&#xff0c;使用vscode远…

js-有关时间

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Date 有关Date 返回格式 Sun Oct 10 2021 00:00:00 GMT0800 (中国标准时间) new Date() 无参数 获取当前时间new Date(value) 传入时间戳 传入一个时间戳 一个 Unix 时间戳&#xff08;U…

JVM运行流程/运行时数据区

JVM运行流程 程序在执行之前先要把 java代码 转换成 字节码文件 (.class文件), JVM 首先需要把字节码通过一定的方式 类加载器 (ClassLoader) 把文件加载到内存中 运行时数据区 (Runtime Data Area) , 而字节码文件是 JVM 的一套指令集规范, 并不能直接交给底层操作系统去执行…

【大数据技术Hadoop+Spark】Hive数据仓库架构、优缺点、数据模型介绍(图文解释 超详细)

一、Hive简介 Hive起源于Facebook&#xff0c;Facebook公司有着大量的日志数据&#xff0c;而Hadoop是实现了MapReduce模式开源的分布式并行计算的框架&#xff0c;可轻松处理大规模数据。然而MapReduce程序对熟悉Java语言的工程师来说容易开发&#xff0c;但对于其他语言使用…

Anaconda为虚拟环境安装第三方库与Spyder等软件的方法

本文介绍在Anaconda中&#xff0c;为Python的虚拟环境安装第三方库与Spyder等配套软件的方法。 在文章Anaconda中Python虚拟环境的创建、使用与删除&#xff08;https://blog.csdn.net/zhebushibiaoshifu/article/details/128334614&#xff09;中&#xff0c;我们介绍了在Anac…

提前做好网络安全分析,运维真轻松(二)

背景 某汽车总部已部署NetInside流量分析系统&#xff0c;使用流量分析系统提供实时和历史原始流量。汽车配件电子图册系统是某汽车集团的重要业务系统。本次分析重点针对汽车配件电子图册系统进行预见性分析&#xff0c;以供安全取证、性能分析、网络质量监测以及深层网络分析…

FRP搭建内网穿透

前言 内网穿透方式很多&#xff0c;可以用公网IP进行端口映射&#xff0c;DDNS等。现在我有个云服务器&#xff0c;使用它做中转作为内网穿透的工具。 可以在这个网址了解下原理基础&#xff1a;内网穿透工具的原理与开发实战 FRP内网穿透 FRP是一个内网穿透的反向代理应用…

电压放大器工作原理及特点是什么

很多人虽然经常使用电压放大器&#xff0c;但是对于电压放大器的工作原理以及特点是什么都不清楚&#xff0c;下面就来为大家讲解。 什么是电压放大器&#xff1f; 电压放大器是一种能够增加信号电压的装置。对于弱信号&#xff0c;通常采用多级放大级联方式分直接耦合、阻容耦…

关于机器人状态估计(10)-VSLAM与VIO的3D建图,重定位与世界观综述

近期我国迎来了cov海啸&#xff0c;其实我也不知道我羊了没有&#xff0c;但并没有什么不舒服同时因为我没有测&#xff0c;那自然是没有羊&#xff0c;或者是薛定谔的羊。 近年另外一块工作的综述&#xff0c;这篇科普的同时&#xff0c;也会包含部分有价值的信息。 一. 摘要…