C++学习之路(十)C++ 用Qt5实现一个工具箱(增加一个时间戳转换功能)- 示例代码拆分讲解

news2024/11/23 18:56:16

上篇文章,我们用 Qt5 实现了在小工具箱中添加了《JSON数据格式化》功能,还是比较实用的。为了继续丰富我们的工具箱,今天我们就再增加一个平时经常用到的功能吧,就是「 时间戳转换 」功能,而且实现点击按钮后文字进行变更的处理逻辑。下面我们就来看看如何来规划开发一个这样的小功能并且添加到我们的工具箱中吧。

老规矩,先看效果

在这里插入图片描述
在这里插入图片描述

这次我们还增加了一个文本提示(在此输入日期时间或时间戳),当下方文本框输入内容后,该提示就会消失。其实这个模拟的是占位符的效果。

在这里插入图片描述

细心的同学可能已经注意到了,这里的按钮文字变成了 隐藏时间戳转换,再次点击后,界面隐藏并且按钮文字重新变回了 显示时间戳转换

在这里插入图片描述

这次功能规划主要围绕工具箱子功能展开,包含以下功能:

  1. 时间戳与日期时间转换器: 实现将日期时间转换为时间戳,以及将时间戳转换为日期时间的功能。
  2. PlaceholderTextEdit 类: 自定义的文本编辑器类,具有占位符功能。
  3. 按钮文字切换功能: 点击按钮后能够切换按钮的文字,以提供更直观的界面提示。

这些功能集合涵盖了常用的文本处理和格式转换功能,让用户能够方便地进行日期时间与时间戳之间的转换。同时,通过 PlaceholderTextEdit 类和按钮文字切换功能,提高了界面的友好性和用户体验。

伸手党福利时间,上代码:

#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QVBoxLayout>
#include <QMessageBox>
#include <QDebug>
#include <QListWidget>
#include <QClipboard>
#include <QMimeData>
#include <QTextEdit>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDateTime>
#include <QLabel>

#define myApp (dynamic_cast<QApplication *>(QCoreApplication::instance()))

class PlaceholderTextEdit : public QWidget {
Q_OBJECT
public:
    explicit PlaceholderTextEdit(const QString &placeholderText, QWidget *parent = nullptr) : QWidget(parent) {
        auto *layout = new QVBoxLayout(this);

        placeholderLabel = new QLabel(placeholderText, this);
        layout->addWidget(placeholderLabel);

        textEdit = new QTextEdit(this);
        layout->addWidget(textEdit);

        connect(textEdit, &QTextEdit::textChanged, this, &PlaceholderTextEdit::checkPlaceholder);
        checkPlaceholder(); // 初始检查

        setLayout(layout);
    }

    QString getText() const {
        return textEdit->toPlainText();
    }

private slots:
    void checkPlaceholder() {
        placeholderLabel->setVisible(textEdit->toPlainText().isEmpty());
    }

private:
    QLabel *placeholderLabel;
    QTextEdit *textEdit;
};

class DateTimeTimestampConverter : public QWidget {
Q_OBJECT
public:
    explicit DateTimeTimestampConverter(QWidget *parent = nullptr) : QWidget(parent) {
        auto *layout = new QVBoxLayout(this);

        inputTextEdit = new PlaceholderTextEdit("在此输入日期时间或时间戳", this);
        layout->addWidget(inputTextEdit);

        convertToTimestampButton = new QPushButton("日期时间转时间戳", this);
        connect(convertToTimestampButton, &QPushButton::clicked, this, &DateTimeTimestampConverter::convertToTimestamp);
        layout->addWidget(convertToTimestampButton);

        convertToDateTimeButton = new QPushButton("时间戳转日期时间", this);
        connect(convertToDateTimeButton, &QPushButton::clicked, this, &DateTimeTimestampConverter::convertToDateTime);
        layout->addWidget(convertToDateTimeButton);

        outputTextEdit = new QTextEdit(this);
        outputTextEdit->setReadOnly(true);
        layout->addWidget(outputTextEdit);

        setLayout(layout);
    }

private slots:
    void convertToTimestamp() {
        QString inputText = inputTextEdit->getText();
        QDateTime dateTime = QDateTime::fromString(inputText, "yyyy-MM-dd HH:mm:ss");

        if (dateTime.isValid()) {
            qint64 timestamp = dateTime.toSecsSinceEpoch();
            outputTextEdit->setText(QString::number(timestamp));
        } else {
            outputTextEdit->setText("无效的日期时间格式!");
        }
    }

    void convertToDateTime() {
        QString inputText = inputTextEdit->getText();
        bool ok;
        qint64 timestamp = inputText.toLongLong(&ok);

        if (ok) {
            QDateTime dateTime;
            dateTime.setSecsSinceEpoch(timestamp);

            outputTextEdit->setText("时间戳 " + inputText + " 对应的日期时间是:" + dateTime.toString("yyyy-MM-dd HH:mm:ss"));
        } else {
            outputTextEdit->setText("无效的时间戳格式!");
        }
    }

private:
    QPushButton *convertToTimestampButton;
    QPushButton *convertToDateTimeButton;
    QTextEdit *outputTextEdit;
    PlaceholderTextEdit *inputTextEdit;
};

class JsonFormatter : public QWidget {
Q_OBJECT
public:
    explicit JsonFormatter(QWidget *parent = nullptr) : QWidget(parent) {
        auto *layout = new QVBoxLayout(this);

        inputTextEdit = new QTextEdit(this);
        layout->addWidget(inputTextEdit);

        formatButton = new QPushButton("格式化", this);
        connect(formatButton, &QPushButton::clicked, this, &JsonFormatter::formatJson);
        layout->addWidget(formatButton);

        outputTextEdit = new QTextEdit(this);
        outputTextEdit->setReadOnly(true);
        layout->addWidget(outputTextEdit);

        setLayout(layout);
    }

private slots:
    void formatJson() {
        QString inputText = inputTextEdit->toPlainText();
        QJsonParseError error{};
        QJsonDocument jsonDoc = QJsonDocument::fromJson(inputText.toUtf8(), &error);

        if (error.error != QJsonParseError::NoError) {
            outputTextEdit->setText("JSON 解析错误:" + error.errorString());
            return;
        }

        QJsonObject jsonObj = jsonDoc.object();
        QJsonDocument formattedJson(jsonObj);

        outputTextEdit->setText(formattedJson.toJson());
    }

private:
    QTextEdit *inputTextEdit;
    QPushButton *formatButton;
    QTextEdit *outputTextEdit;
};

class ClipboardManager : public QWidget {
Q_OBJECT
public:
    explicit ClipboardManager(QWidget *parent = nullptr) : QWidget(parent) {
        auto *layout = new QVBoxLayout(this);

        listWidget = new QListWidget(this);
        updateList(); // 初始更新列表

        auto *clearButton = new QPushButton("清空记录", this);
        connect(clearButton, &QPushButton::clicked, this, &ClipboardManager::clearClipboard);

        layout->addWidget(listWidget);
        layout->addWidget(clearButton);

        setLayout(layout);

        connect(myApp->clipboard(), &QClipboard::dataChanged, this, &ClipboardManager::updateList);
    }

private slots:
    void updateList() {
        const QClipboard *clipboard = myApp->clipboard();
        const QMimeData *mimeData = clipboard->mimeData();

        if (mimeData->hasText()) {
            const QString clipboardText = mimeData->text();

            if (!clipboardText.isEmpty()) {
                listWidget->addItem(clipboardText);
            }
        }
    }

    void clearClipboard() {
        myApp->clipboard()->clear();
        listWidget->clear();
    }

private:
    QListWidget *listWidget;
};

class MyMainWindow : public QWidget {
Q_OBJECT
public:
    explicit MyMainWindow(QWidget *parent = nullptr) : QWidget(parent) {
        setWindowTitle("天河工具箱");

        auto *layout = new QVBoxLayout(this);

        auto *clipboardButton = new QPushButton("显示管理粘贴板记录");
        clipboardButton->setObjectName("clipboardButton");
        connect(clipboardButton, &QPushButton::clicked, this, &MyMainWindow::toggleClipboardManager);
        clipboardManager = new ClipboardManager(this);
        clipboardManager->hide();
        layout->addWidget(clipboardManager);
        layout->addWidget(clipboardButton);

        auto *jsonFormatButton = new QPushButton("显示格式化 JSON");
        jsonFormatButton->setObjectName("jsonFormatButton");
        connect(jsonFormatButton, &QPushButton::clicked, this, &MyMainWindow::toggleJsonFormatter);
        jsonFormatter = new JsonFormatter(this);
        jsonFormatter->hide();
        layout->addWidget(jsonFormatter);
        layout->addWidget(jsonFormatButton);

        auto *timestampConverterButton = new QPushButton("显示时间戳转换");
        timestampConverterButton->setObjectName("timestampConverterButton");
        connect(timestampConverterButton, &QPushButton::clicked, this, &MyMainWindow::toggleDateTimeTimestampConverter);
        timestampConverter = new DateTimeTimestampConverter(this);
        timestampConverter->hide();
        layout->addWidget(timestampConverter);
        layout->addWidget(timestampConverterButton);

        setLayout(layout);
    }

private slots:

    void toggleClipboardManager() {
        auto* curButton = findChild<QPushButton*>("clipboardButton");
        if (clipboardManager->isHidden()) {
            if (curButton) {
                curButton->setText("隐藏管理粘贴板记录");
            }
            clipboardManager->show();
        } else {
            if (curButton) {
                curButton->setText("显示管理粘贴板记录");
            }
            clipboardManager->hide();
        }
    }

    void toggleJsonFormatter() {
        auto* curButton = findChild<QPushButton*>("jsonFormatButton");
        if (jsonFormatter->isHidden()) {
            if (curButton) {
                curButton->setText("隐藏格式化 JSON");
            }
            jsonFormatter->show();
        } else {
            if (curButton) {
                curButton->setText("显示格式化 JSON");
            }
            jsonFormatter->hide();
        }
    }

    void toggleDateTimeTimestampConverter() {
        auto* curButton = findChild<QPushButton*>("timestampConverterButton");
        if (timestampConverter->isHidden()) {
            if (curButton) {
                curButton->setText("隐藏时间戳转换");
            }
            timestampConverter->show();
        } else {
            if (curButton) {
                curButton->setText("显示时间戳转换");
            }
            timestampConverter->hide();
        }
    }

private:
    ClipboardManager *clipboardManager;
    JsonFormatter *jsonFormatter;
    DateTimeTimestampConverter *timestampConverter;
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);

    MyMainWindow mainWindow;
    mainWindow.show();

    return QApplication::exec();
}

#include "main.moc"

拆分讲解时间到了

1. 逐段解释时间戳与日期时间转换器

1.1 构造函数
explicit DateTimeTimestampConverter(QWidget *parent = nullptr) : QWidget(parent) {
    auto *layout = new QVBoxLayout(this);

    inputTextEdit = new PlaceholderTextEdit("在此输入日期时间或时间戳", this);
    layout->addWidget(inputTextEdit);

    convertToTimestampButton = new QPushButton("日期时间转时间戳", this);
    connect(convertToTimestampButton, &QPushButton::clicked, this, &DateTimeTimestampConverter::convertToTimestamp);
    layout->addWidget(convertToTimestampButton);

    convertToDateTimeButton = new QPushButton("时间戳转日期时间", this);
    connect(convertToDateTimeButton, &QPushButton::clicked, this, &DateTimeTimestampConverter::convertToDateTime);
    layout->addWidget(convertToDateTimeButton);

    outputTextEdit = new QTextEdit(this);
    outputTextEdit->setReadOnly(true);
    layout->addWidget(outputTextEdit);

    setLayout(layout);
}

这段代码是该类的构造函数。它创建了一个垂直布局,并添加了以下几个控件:

  • PlaceholderTextEdit:用于输入日期时间或时间戳,带有占位符功能。
  • convertToTimestampButtonconvertToDateTimeButton:分别用于将输入转换为时间戳和日期时间的按钮。
  • outputTextEdit:显示转换结果的只读文本编辑框。
1.2 convertToTimestamp 槽函数
void convertToTimestamp() {
    QString inputText = inputTextEdit->getText();
    QDateTime dateTime = QDateTime::fromString(inputText, "yyyy-MM-dd HH:mm:ss");

    if (dateTime.isValid()) {
        qint64 timestamp = dateTime.toSecsSinceEpoch();
        outputTextEdit->setText(QString::number(timestamp));
    } else {
        outputTextEdit->setText("无效的日期时间格式!");
    }
}

该槽函数对应"日期时间转时间戳"按钮的点击操作。它获取用户输入的文本,尝试将其解析为日期时间,如果解析成功则将其转换为时间戳,并在 outputTextEdit 中显示结果,否则显示错误消息。

1.3 convertToDateTime 槽函数
void convertToDateTime() {
    QString inputText = inputTextEdit->getText();
    bool ok;
    qint64 timestamp = inputText.toLongLong(&ok);

    if (ok) {
        QDateTime dateTime;
        dateTime.setSecsSinceEpoch(timestamp);
        outputTextEdit->setText("时间戳 " + inputText + " 对应的日期时间是:" + dateTime.toString("yyyy-MM-dd HH:mm:ss"));
    } else {
        outputTextEdit->setText("无效的时间戳格式!");
    }
}

这个槽函数对应"时间戳转日期时间"按钮的点击操作。它获取用户输入的文本,尝试将其解析为时间戳,如果解析成功则将其转换为日期时间,并在 outputTextEdit 中显示结果,否则显示错误消息。


2. 再逐段解释一下 PlaceholderTextEdit 类的代码

2.1 构造函数
explicit PlaceholderTextEdit(const QString &placeholderText, QWidget *parent = nullptr) : QWidget(parent) {
    auto *layout = new QVBoxLayout(this);

    placeholderLabel = new QLabel(placeholderText, this);
    layout->addWidget(placeholderLabel);

    textEdit = new QTextEdit(this);
    layout->addWidget(textEdit);

    connect(textEdit, &QTextEdit::textChanged, this, &PlaceholderTextEdit::checkPlaceholder);
    checkPlaceholder(); // 初始检查

    setLayout(layout);
}

这个构造函数接收一个占位符文本,并创建了一个垂直布局,然后添加了两个控件:

  • placeholderLabel:用于显示占位符文本的标签。
  • textEdit:用于用户输入的文本编辑框。
2.2 getText 方法
QString getText() const {
    return textEdit->toPlainText();
}

getText 方法返回 textEdit 中的文本内容。

2.3 checkPlaceholder 槽函数
void checkPlaceholder() {
    placeholderLabel->setVisible(textEdit->toPlainText().isEmpty());
}

checkPlaceholder 方法根据用户输入的内容是否为空来决定是否显示 placeholderLabel 标签。如果 textEdit 为空,显示占位符文本;如果不为空,隐藏占位符文本。

这个类的主要作用是带有占位符功能的文本编辑框,通过监测用户输入来显示或隐藏占位符文本。


3. 最后逐段解释一下按钮文字切换功能的代码

当你调用 toggleDateTimeTimestampConverter 函数时,它会执行以下操作:

3.1 按钮检索:

auto* curButton = findChild<QPushButton*>("timestampConverterButton");

这行代码使用 findChild 方法查找名称为 “timestampConverterButton” 的 QPushButton 控件。这个按钮的名称可能是在程序中设置的,如下图箭头所指的位置。

在这里插入图片描述

3.2 显示或隐藏转换器:

if (timestampConverter->isHidden()) {
    // 如果 timestampConverter 控件隐藏
    if (curButton) {
        // 如果找到按钮,将按钮的文本设置为“隐藏时间戳转换”
        curButton->setText("隐藏时间戳转换");
    }
    // 显示 timestampConverter 控件
    timestampConverter->show();
} else {
    // 如果 timestampConverter 控件显示
    if (curButton) {
        // 如果找到按钮,将按钮的文本设置为“显示时间戳转换”
        curButton->setText("显示时间戳转换");
    }
    // 隐藏 timestampConverter 控件
    timestampConverter->hide();
}

这个部分检查了 timestampConverter 控件的当前状态。如果它隐藏,那么它会显示,并且如果找到了按钮,按钮的文本会更改为“隐藏时间戳转换”。如果它当前是显示状态,那么它会被隐藏,并且按钮的文本将更改为“显示时间戳转换”。

这段代码能够动态地显示或隐藏 timestampConverter 控件,并且按钮的文本会随着其状态变化而变化,以反映当前控件状态。

讲解完毕,复制代码后,开始运行调试吧~

好了~ 本文就到这里了,感谢您的阅读,每天还有更多的实例学习文章等着你 🎆。别忘了点赞、收藏~ Thanks♪(・ω・)ノ 🍇。

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

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

相关文章

图解系列--Web服务器,Http首部

1.用单台虚拟主机实现多个域名 HTTP/1.1 规范允许一台 HTTP 服务器搭建多个 Web 站点。。比如&#xff0c;提供 Web 托管服务&#xff08;Web Hosting Service&#xff09;的供应商&#xff0c;可以用一台服务器为多位客户服务&#xff0c;也可以以每位客户持有的域名运行各自不…

HbuilderX 项目打包文件过大问题优化

文章目录 HbuilderX 项目打包文件过大问题优化主要操作收效甚微&#xff0c;但又有那么点用的方法使用 gulp 压缩&#xff08;最后一步&#xff09;使用与配置 网上找的 gulp 优化压缩配置还未尝试可能有用的方法 尝试过程中看到的一些优质文章 HbuilderX 项目打包文件过大问题…

C语言每日一题(41)循环队列

力扣 622 循环队列 题目描述 设计你的循环队列实现。 循环队列是一种线性数据结构&#xff0c;其操作表现基于 FIFO&#xff08;先进先出&#xff09;原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。 循环队列的一个好处是我们可以利用这个队列之前…

Mysql8.1.0 安装问题-缺少visual studio 2019x64组件

缺少visual studio x64组件的问题 使用Mysql8以上的安装包mysql-8.1.0-winx64.msi进行安装&#xff0c; 提示缺少visual studio 2019 x64可再发行组件 在微软官网下载vc可再发行程序包 Microsoft Visual C 可再发行程序包最新支持的下载 在Visual Studio 2015、2017、2019 和…

SAP_ABAP_编程基础_字符转换_内存表、jsonString 相互转换

SAP ABAP 顾问&#xff08;开发工程师&#xff09;能力模型_Terry谈企业数字化的博客-CSDN博客文章浏览阅读441次。目标&#xff1a;基于对SAP abap 顾问能力模型的梳理&#xff0c;给一年左右经验的abaper 快速成长为三年经验提供超级燃料&#xff01;https://blog.csdn.net/j…

Linux常用命令——basename命令

在线Linux命令查询工具 basename 打印目录或者文件的基本名称 补充说明 basename命令用于打印目录或者文件的基本名称。basename和dirname命令通常用于shell脚本中的命令替换来指定和指定的输入文件名称有所差异的输出文件名称。 语法 basename(选项)(参数)选项 --help&…

Java核心知识点整理大全21-笔记

目录 18.1.5.1. upstream_module 和健康检测 18.1.5.1. proxy_pass 请求转发 18.1.6. HAProxy 19. 数据库 19.1.1. 存储引擎 19.1.1.1. 概念 19.1.1.2. InnoDB&#xff08;B树&#xff09; 适用场景&#xff1a; 19.1.1.3. TokuDB&#xff08;Fractal Tree-节点带数据&…

Docker容器常用命令

文章目录 启动类命令帮助类命令镜像命令列出本地主机上的镜像在远程仓库中搜索镜像下载镜像保存镜像加载 tar 包为镜像查看占据的空间删除镜像 虚悬镜像命令自动补全新建启动容器启动交互式容器启动守护式容器 列出正在运行的容器容器其他启停操作启动已经停止的容器重启容器停…

【Web】SWPUCTF 2022 新生赛 个人复现

目录 ①webdog1__start ②ez_rce ③ez_sql ④ez_1zpop ⑤file_maste ⑥Power! 挑了部分题&#xff0c;太简单的就没选进来&#xff08;但选进来≠有难度&#xff09; ①webdog1__start 进来没啥东西&#xff0c;右键查看源码 对于0e215962017&#xff0c;md5后也是以…

泗博Modbus转Profinet网关TS-180对水表流量的监控应用

应用场景&#xff1a; 陕西某工程技术有限公司在一民生工程项目中&#xff0c;需要对公园直饮水进行净化保证其水质。直饮水净化装置需根据用水量不定期的维护&#xff0c;通过统计各个净化装置净化的直饮水的流量&#xff0c;来实现提前维护目的。 应用痛点&#xff1a; 项目…

基于IDEA+MySQL+Tomcat开发的宠物管理系统

基于IDEAMySQLTomcat开发的宠物管理系统 项目介绍&#x1f481;&#x1f3fb; 宠物管理系统项目背景介绍 随着社会经济的发展和人们生活水平的提高&#xff0c;宠物已经成为越来越多家庭的重要成员。然而&#xff0c;由于缺乏有效的管理系统&#xff0c;宠物饲养面临着许多挑战…

uniapp中进行地图定位

目录 一、创建map 二、data中声明变量 三、获取当前位置信息&#xff0c;进行定位 四、在methods中写移动图标获取地名地址的方法 五、最终展示效果 一、创建map <!-- 地图展示 --><view class"mymap"><!-- <view class"mymap__map"…

数据扩增(Data Augmentation)、正则化(Regularization)和早停止(Early Stopping)

数据扩增&#xff08;Data Augmentation&#xff09;、正则化&#xff08;Regularization&#xff09;和早停止&#xff08;Early Stopping&#xff09;是深度学习中常用的三种技术&#xff0c;它们有助于提高模型的泛化性能和防止过拟合 数据扩增&#xff08;Data Augmentati…

使用Git客户端向gitee免密推送项目代码(保姆级流程哦)

1.进入Git官网手动下载git的客户端可执行程序 一路next即可 2.找到安装路径下的3.进入git-bash 根据如下的代码一次执行只需要修改对应的username和自己再gitee中绑定的邮箱 4.分发私钥到邮箱 产生私钥的时候回车三次即可&#xff1b;查看私钥如下图及正常&#xff1b; 5.进…

VAE模型及pytorch实现

VAE模型及pytorch实现 VAE模型推导部分最小化KL散度推导代码部分损失函数Encoder部分Decoder部分VAE整体架构 VAE问题参考资料 VAE&#xff08;变分自编码器&#xff09;是一种生成模型&#xff0c;结合了自编码器和概率图模型的思想。它通过学习数据的潜在分布&#xff0c;可以…

Rust语言入门教程(十三) - 重要的枚举类型Option与Result

Option 首先&#xff0c;还是再列出Option的定义, 它用于任何可能为空的变量。 enum Option<T> {Some(T),None, }下面的代码展示了如何创建一个空类型&#xff1a; let mut x: Option<i32> None;注意在<>中我们指定了x如果不为空时的关联数据变量类型&am…

利用Spring Boot构建restful web service的详细流程

本文档构建一个简单的restful webservice&#xff0c; 在官网原文Getting Started | Building a RESTful Web Service (spring.io)的基础上进行操作 文章目录 一、项目创建流程1.1 创建项目1.2 创建资源表示类1.3 创建资源控制类 二、项目运行参考资料 一、项目创建流程 本文的…

手写promise A+、catch、finally、all、allsettled、any、race

目录 手写promise 同步版 1.Promise的构造方法接收一个executor()&#xff0c;在new Promise()时就立刻执行executor回调 2.executor()内部的异步任务被放入宏/微任务队列&#xff0c;等待执行 3.状态与结果的管理 状态只能变更一次 4.then()调用成功/失败回调 catch是…

简单测试大语言模型 Yi-34B 的中日英能力

简单测试大语言模型 Yi-34B 的中日英能力 0. 背景1. 中文测试2. 日文测试3. 英文测试 0. 背景 简单测试一下C-Eval 排行榜第一&#xff08;20231129时点&#xff09;的 Yi-34B 的中日英能力&#xff0c; 1. 中文测试 问题1&#xff0c;回答正确。 问题2&#xff0c;回答正确。…

monorepo多项目管理主流实现方式:1.learn + yarn/npm workspace 2.pnpm

npm域级包 随着npm包越来越多&#xff0c;而且包名也只能是唯一的&#xff0c;如果一个名字被别人占了&#xff0c;那你就不能再使用这个名字&#xff1b;假设我想要开发一个utils包&#xff0c;但是张三已经发布了一个utils包&#xff0c;那我的包名就不能叫utils了&#xff…