Qt WORD/PDF(三)使用 QAxObject 对 Word 替换(QML)

news2024/12/21 22:20:54

关于QT Widget 其它文章请点击这里:     QT Widget

国际站点 GitHub:     https://github.com/chenchuhan
国内站点 Gitee :      https://gitee.com/chuck_chee

姊妹篇:     

Qt WORD/PDF(一)使用 QtPdfium库实现 PDF 操作
Qt WORD/PDF(二)使用 QtPdfium库实现 PDF 预览、打印等
Qt WORD/PDF(三)使用 QAxObject 对 Word 替换(QML)
Qt WORD/PDF(四)使用 QAxObject 对 Word 替换(QWidget)


一、QAxObject 简介

QAxObject 是 Qt 提供的一个类,它用于与 COM(Component Object Model)对象进行交互。COM 是一种微软的技术,广泛用于各种应用程序之间的通信,尤其在 Windows 平台上,很多软件和系统组件都是基于 COM 构建的。QAxObject 类提供了一个 Qt 风格的接口,简化了与这些 COM 对象的交互。

本文主要使用 QAxObject 操作 word 文档,使用键值对,对模板文件进行替换操作,导出相应的文档,特别适合输出报告。

本文采用 QML + C++ 分离的方式:

QML 界面:用户通过 QML 界面输入键值对,选择模板文件并选择保存路径。
C++ 后端:当用户确认选择后,C++ 后端通过 QAxObject 与 Word 应用进行交互,打开模板文件并进行占位符替换,生成的新 Word 文档被保存到用户选择的位置。

环境:

QT5.15.2 + MSVC2019 + QML

二、演示

在这里插入图片描述

基本流程:

输入替换键值对——>选择模板文件——>选择输出文件夹——>开始替换并导出;

三、代码与分析

完整代码

OfficeExporter.cpp:

#include "OfficeExporter.h"
#include <QFile>
#include <QTextStream>
#include <QAxObject>
#include <QDebug>

OfficeExporter::OfficeExporter(QObject *parent)
    : QObject(parent) {}

bool OfficeExporter::exportWord(const QString &filePath, const QString &content) {
    // 使用 Qt-Office 或 QTextDocument API 生成文档
    QFile file(filePath);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        return false;
    }

    QTextStream stream(&file);
    stream << content;
    file.close();
    return true;
}

//测试:创造和保存
bool OfficeExporter::createAndSaveWordDocument(const QString &filePath, const QString &content)
{
    // 创建 Word 应用程序对象
    QAxObject *wordApp = new QAxObject("Word.Application");
    if (!wordApp->isNull()) {
        wordApp->setProperty("Visible", false); // 隐藏 Word 窗口

        // 创建一个新的文档
        QAxObject *documents = wordApp->querySubObject("Documents");
        QAxObject *document = documents->querySubObject("Add()");

        // 获取文档中的选择区域(光标位置)
        QAxObject *selection = wordApp->querySubObject("Selection");

        // 插入内容
        selection->dynamicCall("TypeText(const QString&)", content);
        selection->dynamicCall("TypeParagraph()");  // 换行

        // 保存文件
        document->dynamicCall("SaveAs(const QString&)", filePath);

        // 关闭文档
        document->dynamicCall("Close()");
        wordApp->dynamicCall("Quit()");
        delete wordApp;

        return true;
    }

    delete wordApp;
    return false;
}

//测试:替换
bool OfficeExporter::createFromTemplate(const QString &templatePath, const QString &outputPath, const QVariantMap &data)  //const QMap<QString, QString> &placeholder)
{
    qDebug() << "Received data:" << data;

    // 转换 QVariantMap 为 QMap<QString, QString>
    QMap<QString, QString> placeholders;
    for (auto it = data.begin(); it != data.end(); ++it) {
        placeholders[it.key()] = it.value().toString();
    }

    qDebug() << "Template Path:" << templatePath;
    qDebug() << "Output Path:" << outputPath;


    // 检查文件路径
    if (!QFile::exists(templatePath)) {
        qDebug() << "Template file does not exist:" << templatePath;
        return false;
    }

    qDebug() << "QFile::exists ok" ;

    // 创建 Word 应用程序对象
    QAxObject *wordApp = new QAxObject("Word.Application");
    if (wordApp->isNull()) {
        qDebug() << "Failed to initialize Word.Application.";
        delete wordApp;
        return false;
    }

    qDebug() << "wordApp ok" ;
    wordApp->setProperty("Visible", false); // 隐藏 Word 窗口

    // 打开模板文件
    QAxObject *documents = wordApp->querySubObject("Documents");
    QAxObject *document = documents->querySubObject("Open(const QString&)", templatePath);

    // 查找占位符并替换
    //使用 Find.Execute 查找占位符,使用 TypeText 方法替换为新内容
    QAxObject *selection = wordApp->querySubObject("Selection");

    qDebug() << "selection ok" ;

    // 获取 Find 对象
    QAxObject *find = selection->querySubObject("Find");

    qDebug() << "start placeholde";
    // 遍历占位符键值对, 替换未成功,则有问题
    for(auto it = placeholders.begin(); it != placeholders.end(); ++it) {
        QString placeholder = it.key();
        QString newContent = it.value();

        // 重置光标到文档开头
//        selection->dynamicCall("HomeKey(Unit:=6)"); // Move to the start of the document
        //适应于多目标的查找,全部替换
        bool isFound = true;

        //可替换多个,且重复的
        while (isFound) {
            // 查找目标文本并替换
//            isFound = find->dynamicCall("Execute(const QString&)", placeholder).toBool();
            isFound = find->dynamicCall("Execute(QString, bool, bool, bool, bool, bool, bool, int)",
                                        placeholder,  // 要查找的字符串
                                        false,        // 区分大小写
                                        false,        // 完整单词
                                        false,        // 使用通配符
                                        false,        // 忽略标点符号
                                        false,        // 忽略空格
                                        true,         // 向前查找
                                        1).toBool();   // 查找范围:整个文档

            if (isFound) {
                // 替换文本
                selection->dynamicCall("TypeText(const QString&)", newContent);
            }
        }
    }

    qDebug() << "All Find operation succeed!";

    document->dynamicCall("SaveAs(const QString&)", outputPath);
    // 关闭文档
    document->dynamicCall("Close()");
    wordApp->dynamicCall("Quit()");

    delete wordApp;
    return true;
}

OfficeExporter.h:

#ifndef OFFICEEXPORTER_H
#define OFFICEEXPORTER_H

#include <QObject>
#include <QString>

class OfficeExporter : public QObject {
    Q_OBJECT
public:
    explicit OfficeExporter(QObject *parent = nullptr);

    Q_INVOKABLE bool exportWord(const QString &filePath, const QString &content);

    // 创建并保存 Word 文档
    Q_INVOKABLE bool createAndSaveWordDocument(const QString &filePath, const QString &content);

    Q_INVOKABLE bool createFromTemplate(const QString &templatePath, const QString &outputPath,  const QVariantMap &data);

    bool replaceMultiple(const QString &templatePath, const QString &outputPath, const QMap<QString, QString> &placeholders);

};
#endif // OFFICEEXPORTER_H

pro 中需要增加对 QAxObject 的支持

QT       += core gui axcontainer 

简要分析

这段代码展示了如何使用 Qt 和 QAxObject 类与 Word 进行交互,主要分为三个功能:导出 Word 文档创建并保存 Word 文档、以及从模板生成并替换占位符内容

1. exportWord 函数

该函数将内容保存为纯文本文件。它并不直接涉及 Word 的操作,而是将内容以纯文本形式写入文件。它通过 QFileQTextStream 写入文本内容。

bool OfficeExporter::exportWord(const QString &filePath, const QString &content) {
    QFile file(filePath);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        return false;
    }
    QTextStream stream(&file);
    stream << content;
    file.close();
    return true;
}

2. createAndSaveWordDocument 函数

此函数使用 QAxObject 创建一个新的 Word 文档,插入给定内容,并保存为指定路径。通过 QAxObject 创建 Word 应用程序并与之交互:

  • Word.Application 通过 QAxObject 实现。
  • 插入文本通过 Selection 对象的 TypeText 方法进行。
  • 保存文件通过 SaveAs 方法,关闭文档并退出 Word。
bool OfficeExporter::createAndSaveWordDocument(const QString &filePath, const QString &content) {
    QAxObject *wordApp = new QAxObject("Word.Application");
    if (!wordApp->isNull()) {
        wordApp->setProperty("Visible", false); // 隐藏 Word 窗口

        QAxObject *documents = wordApp->querySubObject("Documents");
        QAxObject *document = documents->querySubObject("Add()");

        QAxObject *selection = wordApp->querySubObject("Selection");
        selection->dynamicCall("TypeText(const QString&)", content);
        selection->dynamicCall("TypeParagraph()");

        document->dynamicCall("SaveAs(const QString&)", filePath);
        document->dynamicCall("Close()");
        wordApp->dynamicCall("Quit()");
        delete wordApp;

        return true;
    }

    delete wordApp;
    return false;
}

3. createFromTemplate 函数

此函数通过加载一个现有的 Word 模板文件,并在文档中查找并替换占位符。主要步骤包括:

  • 通过 Find 对象查找占位符。
  • 使用 TypeText 方法替换占位符为给定数据。
  • 使用 Execute 方法执行查找,并通过 for 循环迭代器确保替换所有出现的占位符。
bool OfficeExporter::createFromTemplate(const QString &templatePath, const QString &outputPath, const QVariantMap &data) {
    // Convert QVariantMap to QMap<QString, QString>
    QMap<QString, QString> placeholders;
    for (auto it = data.begin(); it != data.end(); ++it) {
        placeholders[it.key()] = it.value().toString();
    }

    // Open template file
    QAxObject *wordApp = new QAxObject("Word.Application");
    if (wordApp->isNull()) {
        delete wordApp;
        return false;
    }

    wordApp->setProperty("Visible", false);
    QAxObject *documents = wordApp->querySubObject("Documents");
    QAxObject *document = documents->querySubObject("Open(const QString&)", templatePath);

    QAxObject *selection = wordApp->querySubObject("Selection");
    QAxObject *find = selection->querySubObject("Find");

    for (auto it = placeholders.begin(); it != placeholders.end(); ++it) {
        QString placeholder = it.key();
        QString newContent = it.value();

        bool isFound = true;
        while (isFound) {
            isFound = find->dynamicCall("Execute(QString, bool, bool, bool, bool, bool, bool, int)",
                                        placeholder, false, false, false, false, false, true, 1).toBool();
            if (isFound) {
                selection->dynamicCall("TypeText(const QString&)", newContent);
            }
        }
    }

    document->dynamicCall("SaveAs(const QString&)", outputPath);
    document->dynamicCall("Close()");
    wordApp->dynamicCall("Quit()");
    delete wordApp;

    return true;
}

流程:

  1. 创建 Word 应用程序:每个函数中都通过 QAxObject("Word.Application") 来创建 Word 应用程序的 COM 对象,随后可以通过该对象访问文档、光标(Selection)等。
  2. 插入和替换文本
    • 插入文本是通过 TypeText 方法来实现的。这个方法会在光标位置插入指定的文本。
    • createFromTemplate 函数中,使用 Find 对象查找占位符,替换文本的过程是循环执行的,直到文档中的所有占位符都被替换为新内容。
  3. 操作文档
    • SaveAs 方法用于保存文档,Close 方法用于关闭文档,Quit 方法退出 Word 应用程序。
    • Word.ApplicationVisible 属性设置为 false 使得 Word 在后台运行,用户看不到界面。

main.qml:

import QtQuick.Window 2.12
import QtQuick.Dialogs 1.3
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import com.example.OfficeExporter 1.0

Window {
    visible: true
    width: 640
    height: 480

    title: qsTr("Word Export Demo")

    OfficeExporter {
        id: exporter
    }

    ColumnLayout {
        anchors.fill: parent
        spacing: 10
        anchors.margins: 10

        // 表单标题
        Text {
            text: "请输入键值对:"
            font.pixelSize: 20
            anchors.horizontalCenter:  parent.horizontalCenter
        }

        // 键值对输入区
        ListView {
            id: listView
            anchors.horizontalCenter:  parent.horizontalCenter
            Layout.fillWidth: true
            Layout.fillHeight: true
            model: ListModel {
                    ListElement { key: "[A]"; value: "柯布" }
                    ListElement { key: "[B]"; value: "阿瑟" }
                    ListElement { key: "[C]"; value: "杜拉" }
                    ListElement { key: "[D]"; value: "伊姆斯" }
                }

            delegate: RowLayout {
                spacing: 10
                TextField {
                    id: keyField
                    placeholderText: "键 (Key)"
                    text: model.key
                    Layout.fillWidth: true
                    onTextChanged: model.key = text
                }
                TextField {
                    id: valueField
                    placeholderText: "值 (Value)"
                    text: model.value
                    Layout.fillWidth: true
                    onTextChanged: model.value = text
                }
                Button {
                    text: "删除"
                    onClicked: model.remove(index)
                }
            }
        }

        // 添加键值对按钮
        Button {
            text: "添加键值对"
            Layout.alignment: Qt.AlignHCenter
            onClicked: listView.model.append({key: "", value: ""})
        }

        // 生成文档按钮
        Button {
            text: "选择模板并导出 Word 文档"
            Layout.alignment: Qt.AlignHCenter
            onClicked: {
                dialog.open();
            }
        }

        Item {
            width: 1
            height: 1
        }
    }

    FileDialog {
        id:     dialog
        title:  "选择 Word 模板文件"
        selectExisting: true
        nameFilters: [ "Word 文档 (*.docx)", "All files (*)" ]
        onAccepted: {
            fileDialog.open();
        }
    }

    //templatePath.slice(8), 通常是为了去掉路径中的前缀部分,比如 file:///
    FileDialog {
        id: fileDialog
        title: "选择保存路径"
        selectExisting: false
        nameFilters: [ "Word 文档 (*.docx)", "All files (*)" ]
        onAccepted: {

            let data = {};
            for (let i = 0; i < listView.model.count; i++) {
                let item = listView.model.get(i);
                data[item.key] = item.value;
            }
            console.log("用户输入的键值对: ", JSON.stringify(data));

            var templatePath = dialog.fileUrl.toString();
            var filePath = fileUrl.toString();
            if (!filePath.endsWith(".docx")) {
                filePath += ".docx";
            }
            if ( exporter.createFromTemplate(templatePath.slice(8), filePath.slice(8), data)) {
                console.log("Word 文档已导出到:" + filePath);
            } else {
                console.log("导出失败");
            }
        }
    }
}

这段代码结合了 Qt 的 QMLC++ 后端,目的是让用户通过图形界面操作生成 Word 文档。它允许用户通过键值对形式的输入替换 Word 模板中的占位符,并将替换后的内容保存为 Word 文件。

QML 界面结构

(1) OfficeExporter 实例

在 QML 中,OfficeExporter 是一个 C++ 类的 QML 组件

OfficeExporter {    id: exporter}

(2) 输入区:键值对

通过 ListViewTextField,用户可以输入一组键值对。model 用于管理键值对的列表,delegate 负责定义如何显示每一对键值。用户可以修改键值对的内容,并且可以删除不需要的项。

ListView {
    model: ListModel {
        ListElement { key: "[A]"; value: "柯布" }
        ListElement { key: "[B]"; value: "阿瑟" }
        ListElement { key: "[C]"; value: "杜拉" }
        ListElement { key: "[D]"; value: "伊姆斯" }
    }
    delegate: RowLayout {
        TextField {
            text: model.key
            onTextChanged: model.key = text
        }
        TextField {
            text: model.value
            onTextChanged: model.value = text
        }
        Button {
            text: "删除"
            onClicked: model.remove(index)
        }
    }
}

(3) 调用c++替换程序

最终通过 createFromTemplate 接口,输入键值对,调用 C++ 后端程序

if ( exporter.createFromTemplate(templatePath.slice(8), filePath.slice(8), data)) {
	···
}

3. 工作流程总结

  1. QML 界面:用户通过 QML 界面输入键值对,选择模板文件并选择保存路径。
  2. C++ 后端:当用户确认选择后,C++ 后端通过 QAxObject 与 Word 应用进行交互,打开模板文件并进行占位符替换。
  3. 生成 Word 文档:生成的新 Word 文档被保存到用户选择的位置。

注意:

  1. pro 工程文件中加入 : QT += axcontainer 模块
  2. c++中注册: qmlRegisterType(“com.example.OfficeExporter”, 1, 0, “OfficeExporter”);
  3. 另外在 Word 中不方便预览和打印,可结合姊妹篇的(一)和(二),输出为PDF后预览和打印会方便很多!

关于QGC地面站其它文章请点击这里:     QT Widget

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

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

相关文章

RAG基础知识及综述学习

RAG基础知识及综述学习 前言1.RAG 模块1.1 检索器&#xff08;Retriever&#xff09;1.2 检索融合&#xff08;Retrieval Fusion&#xff09;1.3 生成器&#xff08;Generator&#xff09; 2.构建检索器&#xff08;Retriever&#xff09;2.1 分块语料库2.2 编码文本块2.3 构建…

移动网络(2,3,4,5G)设备TCP通讯调试方法

背景&#xff1a; 当设备是移动网络设备连接云平台的时候&#xff0c;如果服务器没有收到网络数据&#xff0c;移动物联设备发送不知道有没有有丢失数据的时候&#xff0c;需要一个抓取设备出来的数据和服务器下发的数据的方法。 1.服务器系统是很成熟的&#xff0c;一般是linu…

深入剖析MyBatis的架构原理

架构设计 简要画出 MyBatis 的架构图 >> ​​ Mybatis 的功能架构分为哪三层&#xff1f; API 接口层 提供给外部使用的接口 API&#xff0c;开发人员通过这些本地 API 来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。MyBatis 和数据库的…

android opencv导入进行编译

1、直接新建module进行导入&#xff0c;选择opencv的sdk 导入module模式&#xff0c;选择下载好的sdk&#xff0c;修改module name为OpenCV490。 有报错直接解决报错&#xff0c;没报错直接运行成功。 2、解决错误&#xff0c;同步成功 一般报错是gradle版本问题较多。我的报…

智能座舱进阶-应用框架层-Jetpack主要组件

Jetpack的分类 1. DataBinding&#xff1a;以声明方式将可观察数据绑定到界面元素&#xff0c;通常和ViewModel配合使用。 2. Lifecycle&#xff1a;用于管理Activity和Fragment的生命周期&#xff0c;可帮助开发者生成更易于维护的轻量级代码。 3. LiveData: 在底层数据库更…

设计模式-访问者设计模式

介绍 访问者模式&#xff08;Visitor&#xff09;&#xff0c;表示一个作用于某对象结构中的各元素的操作&#xff0c;它使你可以在不改变个元素的类的前提下定义作用于这些元素的新操作。 问题&#xff1a;在一个机构里面有两种员工&#xff0c;1.Teacher 2.Engineer 员…

springmvc的拦截器,全局异常处理和文件上传

拦截器: 拦截不符合规则的&#xff0c;放行符合规则的。 等价于过滤器。 拦截器只拦截controller层API接口。 如何定义拦截器。 定义一个类并实现拦截器接口 public class MyInterceptor implements HandlerInterceptor {public boolean preHandle(HttpServletRequest reque…

宿舍管理系统(源码+数据库+报告)

356基于SpringBoot的宿舍管理系统&#xff0c;系统包含两种角色&#xff1a;管理员、用户,系统分为前台和后台两大模块 二、项目技术 编程语言&#xff1a;Java 数据库&#xff1a;MySQL 项目管理工具&#xff1a;Maven 前端技术&#xff1a;Vue 后端技术&#xff1a;SpringBo…

基于 HC_SR04的超声波测距数码管显示(智能小车超声波避障部分)

超声波测距模块HC-SR04 1、产品特色 ①典型工作用电压&#xff1a;5V ②超小静态工作电流&#xff1a;小于 5mA ③感应角度(R3 电阻越大,增益越高,探测角度越大)&#xff1a; R3 电阻为 392,不大于 15 度 R3 电阻为 472, 不大于 30 度 ④探测距离(R3 电阻可调节增益,即调节探测…

(OCPP服务器)SteVe编译搭建全过程

注意&#xff1a;建议使用3.6.0&#xff0c;我升级到3.7.1&#xff0c;并没有多什么新功能&#xff0c;反而电表的实时数据只能看到累计电能了&#xff0c;我回退了就正常&#xff0c;数据库是兼容的&#xff0c;java版本换位java11&#xff0c;其他不变就好 背景&#xff1a;…

搭建Tomcat(四)---Servlet容器

目录 引入 Servlet容器 一、优化MyTomcat ①先将MyTomcat的main函数搬过来&#xff1a; ②将getClass()函数搬过来 ③创建容器 ④连接ServletConfigMapping和MyTomcat 连接&#xff1a; ⑤完整的ServletConfigMapping和MyTomcat方法&#xff1a; a.ServletConfigMappin…

Iris简单实现Go web服务器

package mainimport ("github.com/kataras/iris" )func main() {app : iris.New() // 实例一个iris对象//配置路由app.Get("/", func(ctx iris.Context) {ctx.WriteString("Hello Iris")})app.Get("/aa", func(ctx iris.Context) {ct…

MySql 中的解决某列中多个字段查询是否存在指定某个值, FIND_IN_SET 用法。

简言&#xff1a;今天公司数据库里面有个列是多个数据拼接而成的比如&#xff1a;**“,131113,749932833,749932825,749932826,749932827,749932828,749932829,”**想要通过sql 查找749932833值的列&#xff0c;很多同学第一想到的就是like 模糊匹配&#xff0c;模糊匹配不能保…

Git实用指南(精简版)

目录 读者须知 Git是什么 Git的原理 文件在Git中的几种状态 快速上手 结尾 读者须知 本文章适合从未接触过git,或者需要深度学习Git的用户进行阅读. 文末有详细的文档,读者可以前往Github下载阅读!!三克油 Git是什么 简单来说,Git是一个代码备份工具,你可以使用指令对…

jmeter 接口性能测试 学习笔记

目录 说明工具准备工具配置jmeter 界面汉化配置汉化步骤汉化结果图 案例1&#xff1a;测试接口接口准备线程组添加线程组配置线程组值线程数&#xff08;Number of Threads&#xff09;Ramp-Up 时间&#xff08;Ramp-Up Period&#xff09;循环次数&#xff08;Loop Count&…

小红书关键词搜索采集 | AI改写 | 无水印下载 | 多维表格 | 采集同步飞书

小红书关键词搜索采集 | AI改写 | 无水印下载 | 多维表格 | 采集同步飞书 一、下载影刀&#xff1a; https://www.winrobot360.com/share/activity?inviteUserUuid595634970300317698 二、加入应用市场 https://www.yingdao.com/share/accede/?inviteKeyb2d3f22a-fd6c-4a…

Unbuntu下怎么生成SSL自签证书?

环境&#xff1a; WSL2 Unbuntu 22.04 问题描述&#xff1a; Unbuntu下怎么生成SSL自签证书&#xff1f; 解决方案&#xff1a; 生成自签名SSL证书可以使用OpenSSL工具&#xff0c;这是一个广泛使用的命令行工具&#xff0c;用于创建和管理SSL/TLS证书。以下是生成自签名…

通过阿里云 Milvus 与 PAI 搭建高效的检索增强对话系统

背景介绍 阿里云向量检索服务Milvus版&#xff08;简称阿里云Milvus&#xff09;是一款云上全托管服务&#xff0c;确保了了与开源Milvus的100%兼容性&#xff0c;并支持无缝迁移。在开源版本的基础上增强了可扩展性&#xff0c;能提供大规模 AI 向量数据的相似性检索服务。相…

打靶记录22——Tomato

靶机&#xff1a; https://download.vulnhub.com/tomato/Tomato.ova 难度&#xff1a; 低 目标&#xff1a; 获得 Root 权限 Flag 攻击方法&#xff1a; 主机发现端口扫描信息收集路径爬取源码分析文件包含写入日志 /var/log/auth.log内核漏洞枚举 les.sh本地提权 主机…

三维引擎cesium学习经验

三维引擎cesium学习经验&#xff1a; 1、初始化viewer对象 2、对entity的操作&#xff1a;添加&#xff0c;隐藏&#xff0c;修改&#xff0c;去除&#xff0c;居中显示 3、去除掉entity的双击事件 4、获取当前视角高度 5、获取经纬度在屏幕上的位置 6、获取三维场景屏幕中心点…