Files
SatelliteControl/RecordQuery.cpp
zhuo.li 1ad08b6ed7 卫星终端控制软件_V1.0.4
1、增加号码记录保存,最多保存10条,保存最新的十条;
2、解决方位俯仰显示位置不对的bug
3、优化直方图/方位图的显示
2025-10-14 15:14:38 +08:00

636 lines
23 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "RecordQuery.h"
#include <QCoreApplication>
#include <QMessageBox>
#include <filesystem>
#include "ui_RecordQuery.h"
#define LIMIT_NUMBER_COUNT 10 //限制号码条目数量
// 返回列名
const char* columnName(NumberKind k) {
switch (k) {
case NumberKind::Call: return "callnumber";
case NumberKind::USMsg: return "u_s_msgnumber";
case NumberKind::BdMsg: return "bd_msgnumber";
}
return "callnumber";
}
RecordQuery::RecordQuery(QWidget *parent)
: QWidget(parent)
, ui(new Ui::RecordQuery)
{
ui->setupUi(this);
InitDatabase();
m_logger = spdlog::get("logger");
QDateTime current = QDateTime::currentDateTime();
ui->dteBeginTime->setDateTime(current.addYears(-1));
QDate today = QDate::currentDate();
QTime endOfDay(23, 59, 59);
ui->dteEndTime->setDateTime(QDateTime(today, endOfDay));
}
RecordQuery::~RecordQuery()
{
delete ui;
}
void RecordQuery::InitDatabase(){
const std::string db_path =
(QCoreApplication::applicationDirPath() + "/record.db").toStdString();
// 1. 无条件打开/创建数据库文件SQLite 会在不存在时创建)
m_db = std::make_shared<sqlite3pp::database>(db_path.c_str());
// 2. 始终执行 IF NOT EXISTS确保表存在
const char* sql = R"(CREATE TABLE IF NOT EXISTS record (
id INTEGER PRIMARY KEY,
begin_time TEXT NOT NULL,
channel INTEGER,
type INTEGER,
direction INTEGER,
number TEXT,
call_duration INTEGER,
content TEXT
);)";
sqlite3pp::command cmd(*m_db, sql);
const int rc = cmd.execute();
if (rc != SQLITE_OK) {
QMessageBox::information(this, "错误", "数据表Record创建失败");
}
//电话记录表格
ensureSchema(*m_db);
ensureKeepTop20Triggers(*m_db);
}
// ------------------------ 架构初始化(表 + 唯一索引) ------------------------
void RecordQuery::ensureSchema(sqlite3pp::database& db) {
// 创建表(如果不存在)
{
const char* sql =
"CREATE TABLE IF NOT EXISTS numberRecord ("
" id INTEGER PRIMARY KEY,"
" callnumber TEXT,"
" u_s_msgnumber TEXT,"
" bd_msgnumber TEXT"
");";
sqlite3pp::command cmd(db, sql);
cmd.execute();
}
// 为每列创建“非空唯一索引”,保证同一类号码不重复
{
sqlite3pp::command idx1(
db,
"CREATE UNIQUE INDEX IF NOT EXISTS idx_callnumber_unique "
"ON numberRecord(callnumber) "
"WHERE callnumber IS NOT NULL;"
);
idx1.execute();
}
{
sqlite3pp::command idx2(
db,
"CREATE UNIQUE INDEX IF NOT EXISTS idx_us_msgnumber_unique "
"ON numberRecord(u_s_msgnumber) "
"WHERE u_s_msgnumber IS NOT NULL;"
);
idx2.execute();
}
{
sqlite3pp::command idx3(
db,
"CREATE UNIQUE INDEX IF NOT EXISTS idx_bd_msgnumber_unique "
"ON numberRecord(bd_msgnumber) "
"WHERE bd_msgnumber IS NOT NULL;"
);
idx3.execute();
}
}
// ------------------------ 触发器每类仅保留最新N条-----------------------
void RecordQuery::ensureKeepTop20Triggers(sqlite3pp::database& db) {
// 对 callnumber 生效的触发器
{
QString qsql = QStringLiteral(
"CREATE TRIGGER IF NOT EXISTS trg_keep_callnumber "
"AFTER INSERT ON numberRecord "
"BEGIN "
" DELETE FROM numberRecord "
" WHERE callnumber IS NOT NULL "
" AND id NOT IN ("
" SELECT id FROM numberRecord "
" WHERE callnumber IS NOT NULL "
" ORDER BY id DESC LIMIT %1"
" );"
"END;"
).arg(LIMIT_NUMBER_COUNT);
QByteArray ba = qsql.toUtf8();
sqlite3pp::command cmd(db, ba.constData());
cmd.execute();
}
// 对 u_s_msgnumber 生效的触发器
{
QString qsql = QStringLiteral(
"CREATE TRIGGER IF NOT EXISTS trg_keep_us "
"AFTER INSERT ON numberRecord "
"BEGIN "
" DELETE FROM numberRecord "
" WHERE u_s_msgnumber IS NOT NULL "
" AND id NOT IN ("
" SELECT id FROM numberRecord "
" WHERE u_s_msgnumber IS NOT NULL "
" ORDER BY id DESC LIMIT %1"
" );"
"END;"
).arg(LIMIT_NUMBER_COUNT);
QByteArray ba = qsql.toUtf8();
sqlite3pp::command cmd(db, ba.constData());
cmd.execute();
}
// 对 bd_msgnumber 生效的触发器
{
QString qsql = QStringLiteral(
"CREATE TRIGGER IF NOT EXISTS trg_keep_bd "
"AFTER INSERT ON numberRecord "
"BEGIN "
" DELETE FROM numberRecord "
" WHERE bd_msgnumber IS NOT NULL "
" AND id NOT IN ("
" SELECT id FROM numberRecord "
" WHERE bd_msgnumber IS NOT NULL "
" ORDER BY id DESC LIMIT %1"
" );"
"END;"
).arg(LIMIT_NUMBER_COUNT);
QByteArray ba = qsql.toUtf8();
sqlite3pp::command cmd(db, ba.constData());
cmd.execute();
}
}
// ------------------------ 业务函数:保存 / 查询 / 清空 ------------------------
// 保存号码(若已存在则替换为最新一条),依赖唯一索引与触发器
bool RecordQuery::saveNumber(NumberKind kind, const QString& number) {
if (number.isEmpty()) return false;
const char* col = columnName(kind);
std::string sql = std::string("INSERT OR REPLACE INTO numberRecord (") + col + ") VALUES (?);";
sqlite3pp::command cmd(*m_db, sql.c_str());
// 转为 UTF-8并按 sqlite3pp 需要的重载传入
const QByteArray ba = number.toUtf8();
// 注意sqlite3pp 的 bind 索引从 1 开始,表示第一个参数
int rc = cmd.bind(1, ba.constData(), ba.size(), sqlite3pp::copy);
if (rc != SQLITE_OK) return false;
return cmd.execute() == SQLITE_OK;
}
// 查询某类号码按“新到旧”id DESC。触发器已保证最多20条
QStringList RecordQuery::queryNumbers(NumberKind kind, int limit) {
const char* col = columnName(kind);
std::string sql =
"SELECT " + std::string(col) +
" FROM numberRecord WHERE " + std::string(col) + " IS NOT NULL "
" ORDER BY id DESC LIMIT ?;";
sqlite3pp::query qry(*m_db, sql.c_str());
qry.bind(1, limit);
QStringList out;
for (auto v : qry) {
std::string val;
v.getter() >> val;
out << QString::fromStdString(val);
}
return out;
}
// 清空某一类号码(删除该列非空的记录)
bool RecordQuery::clearNumbers(NumberKind kind) {
const char* col = columnName(kind);
std::string sql = "DELETE FROM numberRecord WHERE " + std::string(col) + " IS NOT NULL;";
sqlite3pp::command cmd(*m_db, sql.c_str());
return cmd.execute() == SQLITE_OK;
}
// ------------------------ UI集成QComboBox 下拉列表 ------------------------
// - 第一项固定为“清空记录”
// - 其余为最近号码(新到旧)
// - 用户选择第一项 => 弹出确认框,确认后清空并刷新
void RecordQuery::refreshNumberCombo(QComboBox* combo, NumberKind kind) {
int limit = LIMIT_NUMBER_COUNT; //
const auto lst = queryNumbers(kind, limit);
combo->clear();
if(!lst.isEmpty())
{
combo->addItem(QStringLiteral("清空记录")); // 虚拟操作项
for (const auto& s : lst) combo->addItem(s);
combo->setCurrentIndex(1); // 不默认选中任何项
}
}
void RecordQuery::setupNumberCombo(QObject* owner, QComboBox* combo, NumberKind kind) {
refreshNumberCombo(combo, kind);
QObject::connect(combo, QOverload<int>::of(&QComboBox::activated), owner,
[combo, kind, this](int index) {
if (index == 0) {
auto ret = QMessageBox::question(
combo,
QStringLiteral("确认"),
QStringLiteral("是否清空该类号码记录?"),
QMessageBox::Yes | QMessageBox::No
);
if (ret == QMessageBox::Yes) {
if (!clearNumbers(kind)) {
QMessageBox::warning(combo, QStringLiteral("提示"), QStringLiteral("清空失败"));
}
refreshNumberCombo(combo, kind);
} else {
combo->setCurrentIndex(-1);
}
} else if (index > 0) {
// 选中某个号码(可把值写回到对应的 QLineEdit
const QString number = combo->itemText(index);
// 示例:
// if (auto lineEdit = combo->parent()->findChild<QLineEdit*>("yourLineEditObjectName")) {
// lineEdit->setText(number);
// }
}
}
);
}
void RecordQuery::InsertRecord(const Record& record) {
std::string sql = "INSERT INTO record(begin_time,channel,type,direction,number,call_duration,content) VALUES (?,?,?,?,?,?,?);";
sqlite3pp::command cmd(*m_db, sql.c_str());
cmd.binder() << record.beginTime.toString("yyyy-MM-dd hh:mm:ss").toStdString()
<< int(record.channel) << int(record.type) << int(record.direction)
<< record.number.toStdString() << int(record.callDuration) << record.content.toStdString();
cmd.execute();
}
void RecordQuery::InsertRecords(const std::vector<Record>& data){
for(const auto& r : data){
InsertRecord(r);
}
}
void RecordQuery::UpdateRecordList(const std::vector<DbRecord>& records){
ui->tblRecordList->clear();
QStringList column_labels;
column_labels << "开始时间"
<< "信道"
<< "通信类型"
<< "方向"
<< "号码"
<< "通话时长"
<< "消息内容";
// 设置列表行数和列数
int rowCount = int(records.size());
ui->tblRecordList->setRowCount(rowCount); // 设置行数
ui->tblRecordList->setColumnCount(column_labels.size()); // 设置列数
// 设置水平表头
ui->tblRecordList->setHorizontalHeaderLabels(column_labels);
QStringList row_labels;
for(int i = 0 ;i < records.size(); ++i){
row_labels << QString::asprintf("%d",m_currentPageIndex*m_limit + i+1);
}
ui->tblRecordList->setVerticalHeaderLabels(row_labels);
// 设置垂直表头
// self.ui.tblTestList.setVerticalHeaderLabels([str(v) for v in
// range(1,len(self.test_list)+1)]) 设置列宽
int column_index = 0;
ui->tblRecordList->setColumnWidth(column_index, 180); // 开始时间
column_index = column_index + 1;
ui->tblRecordList->setColumnWidth(column_index, 60); // 信道
column_index = column_index + 1;
ui->tblRecordList->setColumnWidth(column_index, 80); // 通信类型
column_index = column_index + 1;
ui->tblRecordList->setColumnWidth(column_index, 60); // 方向
column_index = column_index + 1;
ui->tblRecordList->setColumnWidth(column_index, 120); // 号码
column_index = column_index + 1;
ui->tblRecordList->setColumnWidth(column_index, 80); // 通话时长
column_index = column_index + 1;
ui->tblRecordList->setColumnWidth(column_index, 400); // 消息内容
column_index = column_index + 1;
//插入所有项
int rowIndex = 0;
for (int index = 0; index < records.size(); index++) {
const DbRecord& r = records[index];
int column_index = 0;
// 开始时间
auto item = new QTableWidgetItem(QString::fromStdString(r.beginTime));
item->setTextAlignment(Qt::AlignCenter);
ui->tblRecordList->setItem(rowIndex, column_index, item);
column_index = column_index + 1;
// 信道
item = new QTableWidgetItem(r.channel == int(Channel::Uhf)?"UHF":(r.channel== int(Channel::S)?"S":"北斗"));
item->setTextAlignment(Qt::AlignCenter);
ui->tblRecordList->setItem(rowIndex, column_index, item);
column_index = column_index + 1;
// 通信类型
item = new QTableWidgetItem(r.type == int(CommType::Call) ? "通话" : "短信息");
item->setTextAlignment(Qt::AlignCenter);
ui->tblRecordList->setItem(rowIndex, column_index, item);
column_index = column_index + 1;
// 方向
QString s;
if(r.type == int(CommType::Call)){
s = (r.direction==int(CommDirection::Receive)?"来电":"去电");
}else{
s = (r.direction==int(CommDirection::Receive)?"接收":"发送");
}
item = new QTableWidgetItem(s);
item->setTextAlignment(Qt::AlignCenter);
ui->tblRecordList->setItem(rowIndex, column_index, item);
column_index = column_index + 1;
// 号码
item = new QTableWidgetItem(QString::fromStdString(r.number));
item->setTextAlignment(Qt::AlignCenter);
ui->tblRecordList->setItem(rowIndex, column_index, item);
column_index = column_index + 1;
// 通话时长,根据时长按x时x分x秒显示
QString callDurationStr = "";
if(r.type == int(CommType::Call))
{
QTime time(0, 0, 0);
time = time.addSecs(r.callDuration);
if (r.callDuration >= 3600) {
callDurationStr = QString("%1 时 %2 分 %3 秒")
.arg(time.hour())
.arg(time.minute())
.arg(time.second());
} else if (r.callDuration >= 60) {
callDurationStr = QString("%1 分 %2 秒")
.arg(time.minute())
.arg(time.second());
} else {
callDurationStr = QString("%1 秒").arg(time.second());
}
}
item = new QTableWidgetItem(callDurationStr);
item->setTextAlignment(Qt::AlignCenter);
ui->tblRecordList->setItem(rowIndex, column_index, item);
column_index = column_index + 1;
// 消息内容
item = new QTableWidgetItem(QString::fromStdString(r.content));
ui->tblRecordList->setItem(rowIndex, column_index, item);
column_index = column_index + 1;
rowIndex++;
}
}
QString RecordQuery::getQueryCondition(bool isCountQuery){
QString sql;
if(isCountQuery){
sql = "SELECT COUNT(*) from record";
}else{
sql = "SELECT * from record";
}
QString cond;
if(ui->chbTime->isChecked()){
QString begin_date_time = ui->dteBeginTime->dateTime().toString("yyyy-MM-dd hh:mm:ss");
QString end_date_time = ui->dteEndTime->dateTime().toString("yyyy-MM-dd hh:mm:ss");
cond += QString("begin_time >= \'%1\' and begin_time <= \'%2\'").arg(begin_date_time).arg(end_date_time);
}
if(ui->chbChannel->isChecked() && !(ui->chbChannelUHF->isChecked() && ui->chbChannelS->isChecked() && ui->chbChannelBeiDou->isChecked())){
QVector<QString> values;
if(ui->chbChannelUHF->isChecked()){
values.push_back(QString::asprintf("%d",int(Channel::Uhf)));
}
if(ui->chbChannelS->isChecked()){
values.push_back(QString::asprintf("%d",int(Channel::S)));
}
if(ui->chbChannelBeiDou->isChecked()){
values.push_back(QString::asprintf("%d",int(Channel::BeiDou)));
}
if(!cond.isEmpty()){
cond += " and ";
}
cond += QString("channel in (%1)").arg(values.toList().join(","));
}
if(ui->chbType->isChecked() && !(ui->chbCall->isChecked() && ui->chbMessage->isChecked())){
QVector<QString> values;
if(ui->chbCall->isChecked()){
values.push_back(QString::asprintf("%d",int(CommType::Call)));
}
if(ui->chbMessage->isChecked()){
values.push_back(QString::asprintf("%d",int(CommType::ShortMessage)));
}
if(!cond.isEmpty()){
cond += " and ";
}
cond += QString("type in (%1)").arg(values.toList().join(","));
}
if(ui->chbDirection->isChecked() && !(ui->chbSend->isChecked() && ui->chbReceive->isChecked())){
QVector<QString> values;
if(ui->chbSend->isChecked()){
values.push_back(QString::asprintf("%d",int(CommDirection::Send)));
}
if(ui->chbReceive->isChecked()){
values.push_back(QString::asprintf("%d",int(CommDirection::Receive)));
}
if(!cond.isEmpty()){
cond += " and ";
}
cond += QString("direction in (%1)").arg(values.toList().join(","));
}
if(ui->chbNumber->isChecked() && !ui->cmbNumber->currentText().isEmpty()){
if(!cond.isEmpty()){
cond += " and ";
}
cond += QString("number == (%1)").arg(ui->cmbNumber->currentText());
}
if(!cond.isEmpty()){
sql += QString(" where %1").arg(cond);
}
return sql;
}
void RecordQuery::getQueryRecords(sqlite3pp::query&qry, std::vector<DbRecord>& records){
for (sqlite3pp::query::iterator i = qry.begin(); i != qry.end(); ++i) {
DbRecord r;
r.id = (*i).get<int64_t>(0);
r.beginTime = (*i).get<char const*>(1);
r.channel = (*i).get<int>(2);
r.type = (*i).get<int>(3);
r.direction = (*i).get<int>(4);
r.number = (*i).get<char const*>(5);
r.callDuration = (*i).get<int>(6);
r.content = (*i).get<char const*>(7);
records.push_back(r);
}
}
//--------------------------------------------------------------------------------------
// 槽函数
//--------------------------------------------------------------------------------------
void RecordQuery::on_btnQuery_clicked()
{
if(ui->chbChannel->isChecked() && !ui->chbChannelUHF->isChecked() && !ui->chbChannelS->isChecked() && !ui->chbChannelBeiDou->isChecked()){
QMessageBox::information(this, "错误", "已经选择了通过信道类型过滤,请选择具体信道");
return;
}
if(ui->chbType->isChecked() && !ui->chbCall->isChecked() && !ui->chbMessage->isChecked()){
QMessageBox::information(this, "错误", "已经选择了通过通信类型过滤,请选择具体通信类型");
return;
}
if(ui->chbDirection->isChecked() && !ui->chbSend->isChecked() && !ui->chbReceive->isChecked()){
QMessageBox::information(this, "错误", "已经选择了通过通信方向过滤,请选择具体通信方向");
return;
}
QString sql = getQueryCondition(true);
sqlite3pp::query countQry(*m_db, sql.toStdString().c_str());
int64_t count = 0;
for (sqlite3pp::query::iterator i = countQry.begin(); i != countQry.end(); ++i) {
count = (*i).get<int64_t>(0);
}
m_currentPageIndex = 0;
if(count == 0){
m_maxPageIndex = 1;
}else{
m_maxPageIndex = count % m_limit == 0 ? count/m_limit : (count/m_limit+1);
}
//spbGoToPage控件上显示的页索引从1开始
ui->spbGoToPage->setValue(m_currentPageIndex+1);
ui->spbGoToPage->setMinimum(1);
ui->spbGoToPage->setMaximum(m_maxPageIndex);
ui->spbGoToPage->setSuffix(QString(" / %1").arg(m_maxPageIndex));
sql = getQueryCondition();
sql += QString(" LIMIT %1 , %2").arg(0).arg(m_limit);
std::vector<DbRecord> v;
sqlite3pp::query qry(*m_db, sql.toStdString().c_str());
getQueryRecords(qry,v);
UpdateRecordList(v);
}
void RecordQuery::on_btnFirstPage_clicked()
{
m_currentPageIndex = 0;
ui->spbGoToPage->setValue(m_currentPageIndex+1);
QString sql = QString("%1 LIMIT %2 , %3").arg(getQueryCondition()).arg(0).arg(m_limit);
std::vector<DbRecord> v;
sqlite3pp::query qry(*m_db, sql.toStdString().c_str());
getQueryRecords(qry,v);
UpdateRecordList(v);
}
void RecordQuery::on_btnPreviousPage_clicked()
{
if(m_currentPageIndex == 0){
return;
}
m_currentPageIndex -= 1;
ui->spbGoToPage->setValue(m_currentPageIndex+1);
QString sql = QString("%1 LIMIT %2 , %3").arg(getQueryCondition()).arg(m_currentPageIndex*m_limit).arg(m_limit);
std::vector<DbRecord> v;
sqlite3pp::query qry(*m_db, sql.toStdString().c_str());
getQueryRecords(qry,v);
UpdateRecordList(v);
}
void RecordQuery::on_btnNextPage_clicked()
{
if(m_currentPageIndex + 1 >= m_maxPageIndex){
return;
}
m_currentPageIndex += 1;
ui->spbGoToPage->setValue(m_currentPageIndex+1);
QString sql = QString("%1 LIMIT %2 , %3").arg(getQueryCondition()).arg(m_currentPageIndex*m_limit).arg(m_limit);
std::vector<DbRecord> v;
sqlite3pp::query qry(*m_db, sql.toStdString().c_str());
getQueryRecords(qry,v);
UpdateRecordList(v);
}
void RecordQuery::on_btnLastPage_clicked()
{
m_currentPageIndex = m_maxPageIndex-1;
ui->spbGoToPage->setValue(m_currentPageIndex+1);
QString sql = QString("%1 LIMIT %2 , %3").arg(getQueryCondition()).arg(m_currentPageIndex*m_limit).arg(m_limit);
std::vector<DbRecord> v;
sqlite3pp::query qry(*m_db, sql.toStdString().c_str());
getQueryRecords(qry,v);
UpdateRecordList(v);
}
void RecordQuery::on_spbGoToPage_valueChanged(int arg1)
{
m_currentPageIndex = arg1-1;
ui->spbGoToPage->setValue(m_currentPageIndex+1);
QString sql = QString("%1 LIMIT %2 , %3").arg(getQueryCondition()).arg(m_currentPageIndex*m_limit).arg(m_limit);
std::vector<DbRecord> v;
sqlite3pp::query qry(*m_db, sql.toStdString().c_str());
getQueryRecords(qry,v);
UpdateRecordList(v);
}
void RecordQuery::on_tblRecordList_currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous)
{
int currentRow = ui->tblRecordList->currentRow();
if(currentRow == -1){
ui->txtCurrentItemMessageContent->clear();
}else{
ui->txtCurrentItemMessageContent->setText(ui->tblRecordList->item(currentRow,6)->text());//NOTE:注意此处的列数
}
}
void RecordQuery::on_chbTime_stateChanged(int arg1)
{
ui->dteBeginTime->setEnabled(arg1 == Qt::CheckState::Checked);
ui->dteEndTime->setEnabled(arg1 == Qt::CheckState::Checked);
}
void RecordQuery::on_chbChannel_stateChanged(int arg1)
{
ui->chbChannelUHF->setEnabled(arg1 == Qt::CheckState::Checked);
ui->chbChannelS->setEnabled(arg1 == Qt::CheckState::Checked);
ui->chbChannelBeiDou->setEnabled(arg1 == Qt::CheckState::Checked);
}
void RecordQuery::on_chbType_stateChanged(int arg1)
{
ui->chbCall->setEnabled(arg1 == Qt::CheckState::Checked);
ui->chbMessage->setEnabled(arg1 == Qt::CheckState::Checked);
}
void RecordQuery::on_chbDirection_stateChanged(int arg1)
{
ui->chbSend->setEnabled(arg1 == Qt::CheckState::Checked);
ui->chbReceive->setEnabled(arg1 == Qt::CheckState::Checked);
}
void RecordQuery::on_chbNumber_stateChanged(int arg1)
{
ui->cmbNumber->setEnabled(arg1 == Qt::CheckState::Checked);
}