账单记录器迁移。

This commit is contained in:
2026-02-27 12:29:27 +08:00
commit 21b9cc0590
54 changed files with 4195 additions and 0 deletions

17
.clang-format Normal file
View File

@@ -0,0 +1,17 @@
BasedOnStyle: LLVM
IndentWidth: 4
PointerAlignment: Left
AccessModifierOffset: -4
BreakBeforeBraces: Custom
BraceWrapping:
AfterFunction: true
AfterClass: true
Cpp11BracedListStyle: true
ReflowComments: true
SpacesBeforeTrailingComments: 3
TabWidth: 4
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ColumnLimit: 130
AllowShortBlocksOnASingleLine: Never
AllowShortFunctionsOnASingleLine: None
AllowShortEnumsOnASingleLine: false

42
.gitignore vendored Normal file
View File

@@ -0,0 +1,42 @@
# Prerequisites
*.d
.idea
cmake-build-*
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
build
*.user
compile_commands.json
.vs
out
.cache
CMakeLists.txt.*
build*

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "ofen"]
path = ofen
url = https://www.sinxmiao.cn/taynpg/ofen

183
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,183 @@
{
"files.autoSave": "onFocusChange",
"editor.fontSize": 14,
"cmake.configureOnOpen": true,
"editor.fontFamily": "'Maple Mono NL NF CN Light', 'Maple Mono NL NF CN Light', 'Maple Mono NL NF CN Light'",
"cmake.debugConfig": {
"console": "externalTerminal",
"setupCommands": [
{
"description": "-gdb-set charset utf-8",
"text": "-gdb-set charset UTF-8"
},
{
"description": "Enable gdb pretty-printing",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
//"visualizerFile": "${workspaceRoot}/.vscode/qt6.natvis",
"args": []
},
"cmake.environment": {
//"EXTER_LIB_DIR": "C:/local"
},
"cmake.configureSettings": {
"CMAKE_PREFIX_PATH": "C:\\Qt\\Qt5.14.2\\5.14.2\\msvc2017_64",
//"CMAKE_TOOLCHAIN_FILE": "${env:TT_VCPKG}"
},
"cmake.configureArgs": [
"-Wno-dev"
],
"cmake.options.statusBarVisibility": "visible",
"cmake.generator": "Ninja",
"C_Cpp.default.compileCommands": "${workspaceRoot}/build/compile_commands.json",
"C_Cpp.default.cppStandard": "c++17",
"C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools",
"editor.inlayHints.enabled": "off",
"editor.unicodeHighlight.allowedLocales": {
"ja": true,
"zh-hant": true,
"zh-hans": true
},
"files.associations": {
"string": "cpp",
"any": "cpp",
"array": "cpp",
"atomic": "cpp",
"bit": "cpp",
"bitset": "cpp",
"cctype": "cpp",
"charconv": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"codecvt": "cpp",
"compare": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"forward_list": "cpp",
"list": "cpp",
"map": "cpp",
"set": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"source_location": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"format": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"numbers": "cpp",
"ostream": "cpp",
"queue": "cpp",
"ranges": "cpp",
"semaphore": "cpp",
"shared_mutex": "cpp",
"span": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"text_encoding": "cpp",
"thread": "cpp",
"cfenv": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp",
"variant": "cpp",
"ios": "cpp",
"locale": "cpp",
"xfacet": "cpp",
"xhash": "cpp",
"xiosbase": "cpp",
"xlocale": "cpp",
"xlocbuf": "cpp",
"xlocinfo": "cpp",
"xlocmes": "cpp",
"xlocmon": "cpp",
"xlocnum": "cpp",
"xloctime": "cpp",
"xmemory": "cpp",
"xstring": "cpp",
"xtr1common": "cpp",
"xutility": "cpp",
"coroutine": "cpp",
"csignal": "cpp",
"future": "cpp",
"stdfloat": "cpp",
"regex": "cpp",
"stack": "cpp",
"valarray": "cpp",
"filesystem": "cpp",
"flat_map": "cpp",
"flat_set": "cpp",
"stacktrace": "cpp",
"singleapplication": "cpp",
"strstream": "cpp",
"barrier": "cpp",
"complex": "cpp",
"csetjmp": "cpp",
"cuchar": "cpp",
"expected": "cpp",
"fstream": "cpp",
"generator": "cpp",
"latch": "cpp",
"print": "cpp",
"scoped_allocator": "cpp",
"spanstream": "cpp",
"syncstream": "cpp",
"typeindex": "cpp"
},
"editor.tokenColorCustomizations": {
"textMateRules": [
{
"scope": "googletest.failed",
"settings": {
"foreground": "#f00"
}
},
{
"scope": "googletest.passed",
"settings": {
"foreground": "#0f0"
}
},
{
"scope": "googletest.run",
"settings": {
"foreground": "#0f0"
}
}
]
}
}

92
CMakeLists.txt Normal file
View File

@@ -0,0 +1,92 @@
cmake_minimum_required(VERSION 3.16)
project(SimpleAccount VERSION 0.1 LANGUAGES CXX)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(MSVC)
add_compile_options(/utf-8)
endif()
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE}/)
add_definitions(-DFMT_HEADER_ONLY)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network Sql)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network Sql)
set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication")
add_subdirectory(ofen/SingleApplication)
include_directories(ofen)
set(PROJECT_SOURCES
main.cpp
mainwidget.cpp
mainwidget.h
mainwidget.ui
SqlOpr.h SqlOpr.cpp
filterform.h filterform.cpp filterform.ui
recordedit.cpp recordedit.h recordedit.ui
repayment.ui repayment.cpp repayment.h
resource/SimpleAccount.rc res.qrc
resource/qss.qrc
statistic.h statistic.cpp
util.h util.cpp
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(SimpleAccount
MANUAL_FINALIZATION
${PROJECT_SOURCES}
util.h util.cpp
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET SimpleAccount APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
if(ANDROID)
add_library(SimpleAccount SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(SimpleAccount
${PROJECT_SOURCES}
)
endif()
endif()
target_link_libraries(SimpleAccount PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Sql Qt${QT_VERSION_MAJOR}::Network)
target_link_libraries(SimpleAccount PRIVATE SingleApplication::SingleApplication)
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
if(${QT_VERSION} VERSION_LESS 6.1.0)
set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.SimpleAccount)
endif()
set_target_properties(SimpleAccount PROPERTIES
${BUNDLE_ID_OPTION}
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
include(GNUInstallDirs)
install(TARGETS SimpleAccount
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(SimpleAccount)
endif()

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# SimpleAccount
记账工具。

495
SqlOpr.cpp Normal file
View File

@@ -0,0 +1,495 @@
#include "SqlOpr.h"
#include <QFile>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>
#include <QVariant>
ACTSqlOpr::~ACTSqlOpr()
{
if (db_.isOpen()) {
db_.close();
}
}
bool ACTSqlOpr::OpenDb(const std::string& path)
{
dbPath_ = path;
db_ = QSqlDatabase::addDatabase("QSQLITE", "account_connection");
db_.setDatabaseName(QString::fromStdString(path));
if (!db_.open()) {
lastErr_ = fmt::format("无法打开数据库:{}", db_.lastError().text().toStdString());
return false;
}
if (!generateDefaultTB()) {
return false;
}
return true;
}
bool ACTSqlOpr::UpdateAccount(AccountRecord& record)
{
QSqlQuery query(db_);
query.prepare("UPDATE account_records SET money = ?, pay_type = ?, dt = ?, "
"thing = ?, classify = ?, remark = ?, addition_file = ? WHERE id = ?");
query.addBindValue(record.money);
query.addBindValue(QString::fromStdString(record.payType));
query.addBindValue(QString::fromStdString(record.dt));
query.addBindValue(QString::fromStdString(record.thing));
query.addBindValue(QString::fromStdString(record.classify));
query.addBindValue(QString::fromStdString(record.remark));
query.addBindValue(QString::fromStdString(record.additionFile));
query.addBindValue(record.id);
if (!query.exec()) {
lastErr_ = fmt::format("执行更新失败: {}", query.lastError().text().toStdString());
return false;
}
return true;
}
QSqlDatabase& ACTSqlOpr::GetDb()
{
return db_;
}
const std::string& ACTSqlOpr::GetLastErr()
{
return lastErr_;
}
bool ACTSqlOpr::AppendAccount(AccountRecord& record)
{
QSqlQuery query(db_);
query.prepare("INSERT INTO account_records "
"(money, pay_type, dt, thing, classify, remark, addition_file) "
"VALUES (?, ?, ?, ?, ?, ?, ?)");
query.addBindValue(record.money);
query.addBindValue(QString::fromStdString(record.payType));
query.addBindValue(QString::fromStdString(record.dt));
query.addBindValue(QString::fromStdString(record.thing));
query.addBindValue(QString::fromStdString(record.classify));
query.addBindValue(QString::fromStdString(record.remark));
query.addBindValue(QString::fromStdString(record.additionFile));
if (!query.exec()) {
lastErr_ = fmt::format("执行插入失败: {}", query.lastError().text().toStdString());
return false;
}
record.id = query.lastInsertId().toInt();
return true;
}
bool ACTSqlOpr::DeleteAccount(int32_t id)
{
QSqlQuery query(db_);
query.prepare("DELETE FROM account_records WHERE id = ?");
query.addBindValue(id);
if (!query.exec()) {
lastErr_ = fmt::format("执行删除失败: {}", query.lastError().text().toStdString());
return false;
}
return true;
}
bool ACTSqlOpr::GetAccountList(AccountRecordList& ret)
{
QSqlQuery query(db_);
query.prepare("SELECT id, money, pay_type, dt, thing, classify, remark, addition_file "
"FROM account_records ORDER BY dt DESC");
if (!query.exec()) {
lastErr_ = fmt::format("查询失败: {}", query.lastError().text().toStdString());
return false;
}
ret.clear();
while (query.next()) {
AccountRecord record;
record.id = query.value(0).toInt();
record.money = query.value(1).toInt();
record.payType = query.value(2).toString().toStdString();
record.dt = query.value(3).toString().toStdString();
record.thing = query.value(4).toString().toStdString();
record.classify = query.value(5).toString().toStdString();
record.remark = query.value(6).toString().toStdString();
record.additionFile = query.value(7).toString().toStdString();
ret.push_back(record);
}
return true;
}
bool ACTSqlOpr::generateDefaultTB()
{
QSqlQuery query(db_);
QString sql = "CREATE TABLE IF NOT EXISTS account_records ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"money INTEGER NOT NULL, "
"pay_type TEXT NOT NULL, "
"dt TEXT NOT NULL, "
"thing TEXT, "
"classify TEXT, "
"remark TEXT, "
"addition_file TEXT)";
if (!query.exec(sql)) {
lastErr_ = fmt::format("创建表失败: {}", query.lastError().text().toStdString());
return false;
}
return true;
}
ACTSqlOpr::ACTSqlOpr()
{
}
ComSqlOpr::ComSqlOpr(QSqlDatabase& db) : db_(db)
{
generateDefaultTB();
CheckContent();
}
bool ComSqlOpr::GetClassifyList(ClassifyRecordList& ret)
{
QSqlQuery query(db_);
query.prepare("SELECT id, key, value, type, mark FROM common_records WHERE key = '分类'");
if (!query.exec()) {
lastErr_ = fmt::format("查询失败: {}", query.lastError().text().toStdString());
return false;
}
ret.clear();
while (query.next()) {
CommonRecord record;
record.id = query.value(0).toInt();
record.key = query.value(1).toString().toStdString();
record.value = query.value(2).toString().toStdString();
record.type = query.value(3).toString().toStdString();
record.mark = query.value(4).toString().toStdString();
ret.push_back(record);
}
return true;
}
bool ComSqlOpr::GetItem(CommonRecord& ret, const std::string& key)
{
QSqlQuery query(db_);
query.prepare("SELECT id, key, value, type, mark FROM common_records "
"WHERE key = ? ORDER BY id DESC LIMIT 1");
query.addBindValue(QString::fromStdString(key));
if (!query.exec()) {
lastErr_ = fmt::format("查询失败: {}", query.lastError().text().toStdString());
return false;
}
if (query.next()) {
ret.id = query.value(0).toInt();
ret.key = query.value(1).toString().toStdString();
ret.value = query.value(2).toString().toStdString();
ret.type = query.value(3).toString().toStdString();
ret.mark = query.value(4).toString().toStdString();
return true;
}
lastErr_ = fmt::format("未找到key为'{}'的记录", key);
return false;
}
bool ComSqlOpr::UpdateItem(CommonRecord& ret)
{
if (ret.id <= 0) {
lastErr_ = "无效的记录ID";
return false;
}
QSqlQuery query(db_);
query.prepare("UPDATE common_records SET "
"key = ?, value = ?, type = ?, mark = ? "
"WHERE id = ?");
query.addBindValue(QString::fromStdString(ret.key));
query.addBindValue(QString::fromStdString(ret.value));
query.addBindValue(QString::fromStdString(ret.type));
query.addBindValue(QString::fromStdString(ret.mark));
query.addBindValue(ret.id);
if (!query.exec()) {
lastErr_ = fmt::format("更新失败: {}", query.lastError().text().toStdString());
return false;
}
return true;
}
bool ComSqlOpr::InserItem(CommonRecord& ret)
{
QSqlQuery query(db_);
query.prepare("INSERT INTO common_records (key, value, type, mark) "
"VALUES (?, ?, ?, ?)");
query.addBindValue(QString::fromStdString(ret.key));
query.addBindValue(QString::fromStdString(ret.value));
query.addBindValue(QString::fromStdString(ret.type));
query.addBindValue(QString::fromStdString(ret.mark));
if (!query.exec()) {
lastErr_ = fmt::format("插入失败: {}", query.lastError().text().toStdString());
return false;
}
ret.id = query.lastInsertId().toInt();
return true;
}
bool ComSqlOpr::DeleteItem(const std::string& value)
{
QSqlQuery query(db_);
query.prepare("DELETE FROM common_records WHERE value = ? and key = '分类'");
query.addBindValue(QString::fromStdString(value));
if (!query.exec()) {
lastErr_ = fmt::format("删除失败: {}", query.lastError().text().toStdString());
return false;
}
if (query.numRowsAffected() == 0) {
lastErr_ = fmt::format("未找到value为'{}'的记录", value);
return false;
}
return true;
}
bool ComSqlOpr::CheckContent()
{
if (!db_.transaction()) {
lastErr_ = fmt::format("开始事务失败: {}", db_.lastError().text().toStdString());
return false;
}
const std::vector<std::tuple<std::string, std::string>> defaultCategories = {{"分类", "默认"}};
for (const auto& [key, val] : defaultCategories) {
if (!CheckAndInsert(key, val, "", "")) {
db_.rollback();
return false;
}
}
if (!db_.commit()) {
lastErr_ = fmt::format("提交事务失败: {}", db_.lastError().text().toStdString());
return false;
}
return true;
}
bool ComSqlOpr::CheckClassifyExist(const std::string& value)
{
QSqlQuery query(db_);
query.prepare("SELECT 1 FROM common_records WHERE value = ? and key = '分类'");
query.addBindValue(QString::fromStdString(value));
if (!query.exec()) {
lastErr_ = fmt::format("查询失败: {}", query.lastError().text().toStdString());
return false;
}
return query.next();
}
const std::string& ComSqlOpr::GetLastErr()
{
return lastErr_;
}
bool ComSqlOpr::CheckAndInsert(const std::string& key, const std::string& value, const std::string& type, const std::string& mark)
{
QSqlQuery query(db_);
query.prepare("SELECT 1 FROM common_records WHERE key = ? LIMIT 1");
query.addBindValue(QString::fromStdString(key));
if (!query.exec()) {
lastErr_ = fmt::format("查询失败: {}", query.lastError().text().toStdString());
return false;
}
if (query.next()) {
return true; // 记录已存在
}
query.prepare("INSERT INTO common_records (key, value, type, mark) VALUES (?, ?, ?, ?)");
query.addBindValue(QString::fromStdString(key));
query.addBindValue(QString::fromStdString(value));
query.addBindValue(QString::fromStdString(type));
query.addBindValue(QString::fromStdString(mark));
if (!query.exec()) {
lastErr_ = fmt::format("插入失败: {}", query.lastError().text().toStdString());
return false;
}
return true;
}
bool ComSqlOpr::generateDefaultTB()
{
QSqlQuery query(db_);
QString sql = "CREATE TABLE IF NOT EXISTS common_records ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"key TEXT NOT NULL, "
"value TEXT, "
"type TEXT, "
"mark TEXT)";
if (!query.exec(sql)) {
lastErr_ = fmt::format("创建表失败: {}", query.lastError().text().toStdString());
return false;
}
return true;
}
RepaySqlOpr::RepaySqlOpr(QSqlDatabase& db) : db_(db)
{
generateDefaultTB();
}
bool RepaySqlOpr::GetRepayResult(RepayRecordList& ret, int32_t accID)
{
ret.clear();
lastErr_.clear();
if (!db_.isOpen()) {
lastErr_ = "Database is not open";
return false;
}
QSqlQuery query(db_);
query.prepare("SELECT id, accID, money, dt, remark FROM repay_record WHERE accID = :accID");
query.bindValue(":accID", accID);
if (!query.exec()) {
lastErr_ = query.lastError().text().toStdString();
return false;
}
while (query.next()) {
RepayRecord record;
record.id = query.value("id").toInt();
record.accID = query.value("accID").toInt();
record.money = query.value("money").toInt();
record.dt = query.value("dt").toString().toStdString();
record.remark = query.value("remark").toString().toStdString();
ret.push_back(record);
}
return true;
}
bool RepaySqlOpr::InsertRepayRecord(RepayRecord& ret)
{
lastErr_.clear();
if (!db_.isOpen()) {
lastErr_ = "Database is not open";
return false;
}
QSqlQuery query(db_);
query.prepare("INSERT INTO repay_record (accID, money, dt, remark) "
"VALUES (:accID, :money, :dt, :remark)");
query.bindValue(":accID", ret.accID);
query.bindValue(":money", ret.money);
query.bindValue(":dt", QString::fromStdString(ret.dt));
query.bindValue(":remark", QString::fromStdString(ret.remark));
if (!query.exec()) {
lastErr_ = query.lastError().text().toStdString();
return false;
}
// Get the auto-generated id
ret.id = query.lastInsertId().toInt();
return true;
}
bool RepaySqlOpr::updateRepayRecord(RepayRecord& ret)
{
lastErr_.clear();
if (!db_.isOpen()) {
lastErr_ = "Database is not open";
return false;
}
QSqlQuery query(db_);
query.prepare("UPDATE repay_record SET accID = :accID, money = :money, "
"dt = :dt, remark = :remark WHERE id = :id");
query.bindValue(":accID", ret.accID);
query.bindValue(":money", ret.money);
query.bindValue(":dt", QString::fromStdString(ret.dt));
query.bindValue(":remark", QString::fromStdString(ret.remark));
query.bindValue(":id", ret.id);
if (!query.exec()) {
lastErr_ = query.lastError().text().toStdString();
return false;
}
return query.numRowsAffected() > 0;
}
bool RepaySqlOpr::deleteRepayRecord(int32_t id)
{
lastErr_.clear();
if (!db_.isOpen()) {
lastErr_ = "Database is not open";
return false;
}
QSqlQuery query(db_);
query.prepare("DELETE FROM repay_record WHERE id = :id");
query.bindValue(":id", id);
if (!query.exec()) {
lastErr_ = query.lastError().text().toStdString();
return false;
}
return query.numRowsAffected() > 0;
}
const std::string& RepaySqlOpr::GetLastErr()
{
return lastErr_;
}
bool RepaySqlOpr::generateDefaultTB()
{
lastErr_.clear();
if (!db_.isOpen()) {
lastErr_ = "Database is not open";
return false;
}
QSqlQuery query(db_);
QString createTable = "CREATE TABLE IF NOT EXISTS repay_record ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"accID INTEGER NOT NULL, "
"money INTEGER NOT NULL, "
"dt TEXT NOT NULL, "
"remark TEXT)";
if (!query.exec(createTable)) {
lastErr_ = query.lastError().text().toStdString();
return false;
}
return true;
}

111
SqlOpr.h Normal file
View File

@@ -0,0 +1,111 @@
#ifndef SQL_OPR_H
#define SQL_OPR_H
#include <QSqlDatabase>
#include <cstdint>
#include <fmt/format.h>
#include <string>
#include <vector>
struct AccountRecord {
int32_t id{};
int32_t money{};
std::string payType{};
std::string dt;
std::string thing;
std::string classify;
std::string remark;
std::string additionFile{};
};
struct CommonRecord {
int32_t id{};
std::string key;
std::string value;
std::string type;
std::string mark;
};
struct RepayRecord {
int32_t id{};
int32_t accID{};
int32_t money{};
std::string dt;
std::string remark;
};
using ClassifyRecordList = std::vector<CommonRecord>;
using AccountRecordList = std::vector<AccountRecord>;
using RepayRecordList = std::vector<RepayRecord>;
class ACTSqlOpr
{
public:
ACTSqlOpr();
~ACTSqlOpr();
public:
bool OpenDb(const std::string& path);
bool GetAccountList(AccountRecordList& ret);
bool AppendAccount(AccountRecord& ret);
bool DeleteAccount(int32_t id);
bool UpdateAccount(AccountRecord& ret);
QSqlDatabase& GetDb();
const std::string& GetLastErr();
private:
bool generateDefaultTB();
private:
QSqlDatabase db_;
std::string dbPath_{};
std::string lastErr_{};
};
class ComSqlOpr
{
public:
ComSqlOpr(QSqlDatabase& db);
public:
bool GetClassifyList(ClassifyRecordList& ret);
bool GetItem(CommonRecord& ret, const std::string& key);
bool UpdateItem(CommonRecord& ret);
bool InserItem(CommonRecord& ret);
bool DeleteItem(const std::string& value);
bool CheckContent();
bool CheckClassifyExist(const std::string& value);
const std::string& GetLastErr();
private:
bool CheckAndInsert(const std::string& key, const std::string& value, const std::string& type, const std::string& mark);
private:
bool generateDefaultTB();
private:
QSqlDatabase& db_;
std::string lastErr_{};
};
class RepaySqlOpr
{
public:
RepaySqlOpr(QSqlDatabase& db);
public:
bool GetRepayResult(RepayRecordList& ret, int32_t accID);
bool InsertRepayRecord(RepayRecord& ret);
bool updateRepayRecord(RepayRecord& ret);
bool deleteRepayRecord(int32_t id);
const std::string& GetLastErr();
private:
bool generateDefaultTB();
private:
QSqlDatabase& db_;
std::string lastErr_{};
};
#endif // SQL_OPR_H

439
filterform.cpp Normal file
View File

@@ -0,0 +1,439 @@
#include "filterform.h"
#include "recordedit.h"
#include "ui_filterform.h"
#include <QDesktopServices>
#include <QDir>
#include <QMessageBox>
#include <QRegularExpressionValidator>
#include <QShowEvent>
#include <algorithm>
#include <unordered_map>
#include <QDateTime>
FilterForm::FilterForm(QWidget* parent, std::unique_ptr<ACTSqlOpr>& sqlOpr, std::unique_ptr<ComSqlOpr>& comSqlOpr,
std::unique_ptr<RepaySqlOpr>& repaySqlOpr)
: QDialog(parent), ui(new Ui::FilterForm), sqlOpr_(sqlOpr), comSqlOpr_(comSqlOpr), repaySqlOpr_(repaySqlOpr)
{
ui->setupUi(this);
statistic_ = std::make_shared<Statistic>(repaySqlOpr_);
Init();
setWindowTitle("结果");
}
FilterForm::~FilterForm()
{
delete ui;
}
int FilterForm::exec()
{
ShowResult();
if (!over_) {
return QDialog::Rejected;
}
return QDialog::exec();
}
void FilterForm::Init()
{
auto* lay = new QVBoxLayout(this);
tw_ = new QTableWidget(this);
QStringList headers;
headers << "ID" << "类型" << "分类" << "金额" << "日期" << "内容" << "备注";
tw_->setColumnCount(headers.size());
tw_->setHorizontalHeaderLabels(headers);
tw_->setSelectionBehavior(QAbstractItemView::SelectRows);
tw_->setColumnWidth(0, 50);
tw_->setColumnWidth(1, 100);
tw_->setColumnWidth(2, 100);
tw_->setColumnWidth(3, 100);
tw_->setColumnWidth(4, 150);
tw_->setColumnWidth(5, 300);
tw_->setColumnWidth(6, 100);
lay->addWidget(tw_);
ui->widget->setLayout(lay);
ui->edCashIn->setReadOnly(true);
ui->edCashOut->setReadOnly(true);
ui->edCreditIn->setReadOnly(true);
ui->edCreditOut->setReadOnly(true);
ui->edCreditCash->setReadOnly(true);
tw_->setContextMenuPolicy(Qt::CustomContextMenu);
connect(tw_, &QTableWidget::customContextMenuRequested, this, &FilterForm::ShowContextMenu);
}
void FilterForm::ShowResult()
{
over_ = false;
AccountRecordList list;
if (!sqlOpr_->GetAccountList(list)) {
QMessageBox::warning(this, "错误", QString::fromStdString(sqlOpr_->GetLastErr()));
return;
}
result_.clear();
if (!Filter(list, result_)) {
QMessageBox::warning(this, "错误", "筛选失败");
return;
}
if (result_.empty()) {
QMessageBox::warning(this, "提示", "没有符合条件的记录");
return;
}
for (const auto& item : result_) {
tw_->insertRow(tw_->rowCount());
int row = tw_->rowCount() - 1;
auto* i1 = new QTableWidgetItem(QString::number(item.id));
i1->setFlags(i1->flags() & ~Qt::ItemIsEditable);
auto* i2 = new QTableWidgetItem(QString::fromStdString(item.payType));
i2->setFlags(i2->flags() & ~Qt::ItemIsEditable);
auto* i3 = new QTableWidgetItem(QString::fromStdString(item.classify));
i3->setFlags(i3->flags() & ~Qt::ItemIsEditable);
auto* i4 = new QTableWidgetItem(QString::number(item.money / 100.0));
i4->setFlags(i4->flags() & ~Qt::ItemIsEditable);
auto* i5 = new QTableWidgetItem(QString::fromStdString(item.dt));
i5->setFlags(i5->flags() & ~Qt::ItemIsEditable);
auto* i6 = new QTableWidgetItem(QString::fromStdString(item.thing));
i6->setFlags(i6->flags() & ~Qt::ItemIsEditable);
auto* i7 = new QTableWidgetItem(QString::fromStdString(item.remark));
i7->setFlags(i7->flags() & ~Qt::ItemIsEditable);
tw_->setItem(row, 0, i1);
tw_->setItem(row, 1, i2);
tw_->setItem(row, 2, i3);
tw_->setItem(row, 3, i4);
tw_->setItem(row, 4, i5);
tw_->setItem(row, 5, i6);
tw_->setItem(row, 6, i7);
}
CalcShow();
over_ = true;
}
void FilterForm::CalcShow()
{
statistic_->Calculate(result_);
auto* d = SharedData::instance();
ui->edCashIn->setText(QString::number(d->ttCashIn_ / 100.0));
ui->edCashOut->setText(QString::number(d->ttCashOut_ / 100.0));
ui->edCreditIn->setText(QString::number(d->ttCreditIn_ / 100.0));
ui->edCreditOut->setText(QString::number(d->ttCreditOut_ / 100.0));
ui->edCreditCash->setText(QString::number(d->ttCreditCash_ / 100.0));
ui->edCreditPay->setText(QString::number(d->ttCashPay_ / 100.0));
auto ret1 = GetClassifyCash(result_, true);
auto ret2 = GetClassifyCash(result_, false);
ui->lwCashCl->clear();
for (const auto& item : ret1) {
ui->lwCashCl->addItem(QString("%1 => %2/%3")
.arg(QString::fromStdString(item.first))
.arg(QString::number(item.second.first / 100.0))
.arg(QString::number(item.second.second / 100.0)));
}
ui->lwCreditCl->clear();
for (const auto& item : ret2) {
ui->lwCreditCl->addItem(QString("%1 => %2/%3")
.arg(QString::fromStdString(item.first))
.arg(QString::number(item.second.first / 100.0))
.arg(QString::number(item.second.second / 100.0)));
}
}
int32_t FilterForm::rePayValue(int accID)
{
RepayRecordList reuslt;
if (!repaySqlOpr_->GetRepayResult(reuslt, accID)) {
return 0;
}
int32_t sum = 0;
for (const auto& item : reuslt) {
sum += item.money;
}
return sum;
}
bool FilterForm::Filter(const AccountRecordList& list, AccountRecordList& result)
{
auto* d = SharedData::instance();
result.clear();
for (const auto& item : list) {
if (d->flType) {
if (item.payType != d->type.toStdString()) {
continue;
}
}
if (d->flKeys) {
auto qkeys = d->key.toUpper();
auto skeys = QString::fromStdString(item.thing).toUpper();
if (!skeys.contains(qkeys)) {
continue;
}
}
switch (d->filter) {
case CashFilter::FIL_BETWEEN: {
if (item.money < d->lowMoney || item.money > d->highMoney) {
continue;
}
break;
}
case CashFilter::FIL_HIGHER: {
if (item.money <= d->highMoney) {
continue;
}
break;
}
case CashFilter::FIL_LOWER: {
if (item.money >= d->lowMoney) {
continue;
}
break;
}
default:
break;
}
if (d->flClassify) {
auto qkeys = d->classify.toUpper();
auto skeys = QString::fromStdString(item.classify).toUpper();
if (!skeys.contains(qkeys)) {
continue;
}
}
if (d->flDays) {
auto days = d->days;
QDateTime datetime = QDateTime::fromString(QString::fromStdString(item.dt), "yyyy-MM-dd hh:mm:ss");
QDateTime now = QDateTime::currentDateTime();
if (datetime.daysTo(now) > days) {
continue;
}
}
result.push_back(item);
}
return true;
}
std::vector<std::pair<std::string, std::pair<uint32_t, uint32_t>>> FilterForm::GetClassifyCash(const AccountRecordList& list,
bool isCash)
{
std::vector<std::pair<std::string, std::pair<uint32_t, uint32_t>>> ret;
std::unordered_map<std::string, std::pair<uint32_t, uint32_t>> classifyMap;
for (const auto& item : list) {
if (isCash) {
if (item.payType == "现金支出") {
if (!classifyMap.count(item.classify)) {
classifyMap[item.classify] = {0, 0};
}
classifyMap[item.classify].first += item.money;
} else if (item.payType == "现金收入") {
if (!classifyMap.count(item.classify)) {
classifyMap[item.classify] = {0, 0};
}
classifyMap[item.classify].second += item.money;
}
} else {
if (item.payType == "信用支出") {
if (!classifyMap.count(item.classify)) {
classifyMap[item.classify] = {0, 0};
}
classifyMap[item.classify].first += item.money;
} else if (item.payType == "信用收入") {
if (!classifyMap.count(item.classify)) {
classifyMap[item.classify] = {0, 0};
}
classifyMap[item.classify].second += item.money;
} else if (item.payType == "信用借款") {
// 信用借款
if (!classifyMap.count(item.classify)) {
classifyMap[item.classify] = {0, 0};
}
classifyMap[item.classify].first += item.money;
}
}
}
for (const auto& item : classifyMap) {
ret.emplace_back(item.first, item.second);
}
std::sort(ret.begin(), ret.end(),
[](const std::pair<std::string, std::pair<uint32_t, uint32_t>>& a,
const std::pair<std::string, std::pair<uint32_t, uint32_t>>& b) {
auto at = a.second.first + a.second.second;
auto bt = b.second.first + b.second.second;
return at > bt;
});
return ret;
}
void FilterForm::ShowContextMenu(const QPoint& pos)
{
QList<QTableWidgetItem*> selectedItems = tw_->selectedItems();
if (selectedItems.isEmpty()) {
return;
}
QMenu menu(this);
if (selectedItems.size() != 7) {
return;
}
QAction* acFile = menu.addAction("查看附件");
int id = selectedItems[0]->text().toInt();
QString fileName;
for (const auto& item : result_) {
if (item.id == id) {
fileName = QString::fromStdString(item.additionFile);
break;
}
}
TransMenu(id, menu, selectedItems[1]);
connect(acFile, &QAction::triggered, [this, selectedItems, id, fileName]() {
if (fileName.isEmpty()) {
QMessageBox::warning(this, "警告", "附件文件名为空");
return;
}
QString mediaDir = QString::fromStdString(Util::GetMediaDir());
QString filePath = QDir(mediaDir).filePath(fileName);
if (!QFile::exists(filePath)) {
QMessageBox::critical(this, "错误", QString("文件不存在: %1").arg(filePath));
return;
}
if (!QDesktopServices::openUrl(QUrl::fromLocalFile(filePath))) {
QMessageBox::critical(nullptr, "错误", "无法打开图片文件");
}
});
QAction* acEdit = menu.addAction("编辑");
connect(acEdit, &QAction::triggered, [this, id, selectedItems]() {
RecordEdit* re = new RecordEdit(this, comSqlOpr_, repaySqlOpr_);
AccountRecord* recPtr = nullptr;
for (auto& item : result_) {
if (item.id == id) {
re->record_ = item;
recPtr = &item;
break;
}
}
re->exec();
// headers << "ID" << "类型" << "分类" << "金额" << "日期" << "内容" << "备注";
if (!re->modify_) {
return;
}
if (!sqlOpr_->UpdateAccount(re->record_)) {
QMessageBox::warning(this, "错误", QString::fromStdString(sqlOpr_->GetLastErr()));
return;
}
selectedItems[1]->setText(QString::fromStdString(re->record_.payType));
selectedItems[2]->setText(QString::fromStdString(re->record_.classify));
selectedItems[3]->setText(QString::number(re->record_.money / 100.0));
selectedItems[4]->setText(QString::fromStdString(re->record_.dt));
selectedItems[5]->setText(QString::fromStdString(re->record_.thing));
selectedItems[6]->setText(QString::fromStdString(re->record_.remark));
*recPtr = re->record_;
CalcShow();
});
QAction* acDelete = menu.addAction("删除");
connect(acDelete, &QAction::triggered, [this, id, selectedItems]() {
if (QMessageBox::question(this, "确认删除", "确定删除记录吗?") != QMessageBox::Yes) {
return;
}
if (!sqlOpr_->DeleteAccount(id)) {
QMessageBox::warning(this, "错误", QString::fromStdString(sqlOpr_->GetLastErr()));
return;
}
// 从结果列表中移除记录
for (auto it = result_.begin(); it != result_.end(); ++it) {
if (it->id == id) {
result_.erase(it);
break;
}
}
// 从表格中移除行
tw_->removeRow(selectedItems[0]->row());
CalcShow();
});
menu.exec(tw_->viewport()->mapToGlobal(pos));
}
void FilterForm::TransMenu(int id, QMenu& menu, QTableWidgetItem* item)
{
QString payType;
for (const auto& item : result_) {
if (item.id == id) {
payType = QString::fromStdString(item.payType);
break;
}
}
if (payType.isEmpty()) {
QMessageBox::warning(this, "警告", QString("未找到ID为%1的支付类型。").arg(id));
return;
}
// 根据支付类型执行不同操作
QAction* acFile = nullptr;
QString pur;
if (payType == "现金支出") {
acFile = menu.addAction("转为信用支出");
pur = "信用支出";
} else if (payType == "现金收入") {
acFile = menu.addAction("转为信用收入");
pur = "信用收入";
} else if (payType == "信用支出") {
acFile = menu.addAction("转为现金支出");
pur = "现金支出";
} else if (payType == "信用收入") {
acFile = menu.addAction("转为现金收入");
pur = "现金收入";
}
if (acFile == nullptr) {
return;
}
connect(acFile, &QAction::triggered, [this, id, pur, item]() {
// 执行支付类型转换逻辑
if (QMessageBox::question(this, "确认转换", QString("确定将ID为%1的记录转为%2吗?").arg(id).arg(pur),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
// 1. 更新数据库中的支付类型
bool done = false;
for (auto& item : result_) {
if (item.id != id) {
continue;
}
item.payType = pur.toStdString();
if (!sqlOpr_->UpdateAccount(item)) {
QMessageBox::critical(this, "错误", "更新数据库失败");
return;
}
done = true;
break;
}
if (done) {
item->setText(pur);
CalcShow();
QMessageBox::information(this, "成功", QString("ID为%1的记录已成功转为%2。").arg(id).arg(pur));
}
}
});
}

52
filterform.h Normal file
View File

@@ -0,0 +1,52 @@
#ifndef FILTERFORM_H
#define FILTERFORM_H
#include "SqlOpr.h"
#include "statistic.h"
#include "util.h"
#include <QDialog>
#include <QMenu>
#include <QTableWidget>
#include <vector>
namespace Ui {
class FilterForm;
}
class FilterForm : public QDialog
{
Q_OBJECT
public:
explicit FilterForm(QWidget* parent, std::unique_ptr<ACTSqlOpr>& sqlOpr, std::unique_ptr<ComSqlOpr>& comSqlOpr,
std::unique_ptr<RepaySqlOpr>& repaySqlOpr);
~FilterForm();
int exec() override;
public:
static bool Filter(const AccountRecordList& list, AccountRecordList& result);
static std::vector<std::pair<std::string, std::pair<uint32_t, uint32_t>>> GetClassifyCash(const AccountRecordList& list,
bool isCash);
private:
void Init();
private:
void ShowResult();
void ShowContextMenu(const QPoint& pos);
void TransMenu(int id, QMenu& menu, QTableWidgetItem* item);
void CalcShow();
int32_t rePayValue(int accID);
private:
bool over_{};
QTableWidget* tw_{};
AccountRecordList result_;
std::shared_ptr<Statistic> statistic_;
std::unique_ptr<ACTSqlOpr>& sqlOpr_;
std::unique_ptr<ComSqlOpr>& comSqlOpr_;
std::unique_ptr<RepaySqlOpr>& repaySqlOpr_;
Ui::FilterForm* ui;
};
#endif // FILTERFORM_H

248
filterform.ui Normal file
View File

@@ -0,0 +1,248 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FilterForm</class>
<widget class="QDialog" name="FilterForm">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1472</width>
<height>710</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>1.清单</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QWidget" name="widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="btnPrePage">
<property name="text">
<string>上一页</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLineEdit" name="edCurPage">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>80</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edTotalPage">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>80</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnNextPage">
<property name="text">
<string>下一页</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="title">
<string>2.统计</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>总计现金支出:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edCashOut"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>总计现金收入:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edCashIn"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>总计信用支出:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edCreditOut"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>总计信用收入:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edCreditIn"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>总计信用借款:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edCreditCash"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>总计信用还款:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edCreditPay"/>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_7">
<property name="text">
<string>现金分类占比(支出/收入):</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="lwCashCl"/>
</item>
<item>
<widget class="QLabel" name="label_8">
<property name="text">
<string>信用分类占比(支出/收入):</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="lwCreditCl"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

31
main.cpp Normal file
View File

@@ -0,0 +1,31 @@
#include "mainwidget.h"
#include <QApplication>
#include <QFile>
#include <SingleApplication>
int main(int argc, char* argv[])
{
SingleApplication a(argc, argv);
// a.setStyle("fusion");
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
MainWidget w;
QObject::connect(&a, &SingleApplication::instanceStarted, &w, [&w]() {
w.showNormal();
w.raise();
w.activateWindow();
});
QFile file(":/qss/flatgray.css");
if (file.open(QFile::ReadOnly)) {
a.setStyleSheet(file.readAll());
}
w.show();
return a.exec();
}

344
mainwidget.cpp Normal file
View File

@@ -0,0 +1,344 @@
#include "mainwidget.h"
#include "./ui_mainwidget.h"
#include "filterform.h"
#include <QDir>
#include <QFileDialog>
#include <QInputDialog>
#include <QMessageBox>
#include <QPainter>
#include <QProcess>
MainWidget::MainWidget(QWidget* parent) : QDialog(parent), ui(new Ui::MainWidget)
{
ui->setupUi(this);
BaseInit();
Init();
RefreshData();
setWindowTitle("账单记录器V1.0.5");
Calculate();
auto size = Util::GetMainSize();
resize(size.first, size.second);
setWindowFlags(windowFlags() & ~Qt::WindowMaximizeButtonHint);
setWindowIcon(QIcon("://resource/ico.ico"));
}
MainWidget::~MainWidget()
{
delete ui;
}
void MainWidget::Init()
{
Util::Init();
auto dbPath = Util::GetWorkDir() + "/account.db";
sqlOpr_ = std::make_unique<ACTSqlOpr>();
if (!sqlOpr_->OpenDb(dbPath)) {
QMessageBox::warning(this, "错误", QString::fromStdString(sqlOpr_->GetLastErr()));
return;
}
comSqlOpr_ = std::make_unique<ComSqlOpr>(sqlOpr_->GetDb());
repaySqlOpr_ = std::make_unique<RepaySqlOpr>(sqlOpr_->GetDb());
statistic_ = std::make_shared<Statistic>(repaySqlOpr_);
auto mediaDir = Util::GetMediaDir();
if (!QDir(QString::fromStdString(mediaDir)).exists()) {
QDir().mkdir(QString::fromStdString(mediaDir));
}
ui->timeEdit->setDisplayFormat("HH:mm:ss");
ui->rbNoLimit->setChecked(true);
ui->btnRecord->setStyleSheet("background-color: #b1a5ccff;");
ui->dateEdit->setDate(QDate::currentDate());
ui->timeEdit->setTime(QTime::currentTime());
ui->cbClassify->setEditable(true);
ui->edCash->setValidator(new QDoubleValidator(0.0, 10000000.0, 2, this));
ui->edHighCash->setValidator(new QDoubleValidator(0.0, 10000000.0, 2, this));
ui->edLowCash->setValidator(new QDoubleValidator(0.0, 10000000.0, 2, this));
ui->edDays->setValidator(new QIntValidator(0, 10000000, this));
ui->lbTotal->setStyleSheet("QLabel {"
"font-size: 20px;"
"color: blue;"
"}");
ui->lbCurCash->setStyleSheet("QLabel {"
"color: blue;"
"}");
ui->lbCredit->setStyleSheet("QLabel {"
"color: blue;"
"}");
connect(ui->btnClassifyAdd, &QPushButton::clicked, this, &MainWidget::AddClassify);
connect(ui->btnClassifyDel, &QPushButton::clicked, this, &MainWidget::DelClassify);
connect(ui->btnRecord, &QPushButton::clicked, this, &MainWidget::Record);
connect(ui->btnSet, &QPushButton::clicked, this, [this]() {
QProcess process;
auto editor = Util::GetEditor();
QStringList args;
args << QString::fromStdString(Util::GetConfigPath());
process.startDetached(QString::fromStdString(editor), args);
process.waitForStarted();
});
connect(ui->btnSearch, &QPushButton::clicked, this, &MainWidget::Search);
connect(ui->btnSetNow, &QPushButton::clicked, this, [this]() {
ui->dateEdit->setDate(QDate::currentDate());
ui->timeEdit->setTime(QTime::currentTime());
});
connect(ui->btnAddFile, &QPushButton::clicked, this, &MainWidget::SelectImg);
connect(ui->btnClearFile, &QPushButton::clicked, this, [this]() { ui->edFile->clear(); });
}
bool MainWidget::RefreshData()
{
if (!RefreshClassify()) {
return false;
}
return true;
}
bool MainWidget::Calculate()
{
AccountRecordList list;
if (!sqlOpr_->GetAccountList(list)) {
QMessageBox::warning(this, "错误", QString::fromStdString(sqlOpr_->GetLastErr()));
return false;
}
// 计算账户余额等信息
statistic_->Calculate(list);
auto* d = SharedData::instance();
auto curCash = static_cast<int32_t>(Util::GetCurCash() * 100);
auto curSave = static_cast<int32_t>(Util::GetCurSave() * 100);
auto curRemain = d->ttCashIn_ - d->ttCashOut_ + curCash - d->ttCashPay_ + d->ttCreditCash_;
ui->lbCurCash->setText(QString::number(curRemain / 100.0));
auto creditOutTotal = d->ttCreditOut_ - d->ttCashPay_ + d->ttCreditCash_;
QString credit = QString("%1/%2").arg(creditOutTotal / 100.0, 0, 'f', 2).arg(d->ttCreditIn_ / 100.0, 0, 'f', 2);
ui->lbCredit->setText(credit);
auto youHave = curRemain + d->ttCreditIn_ - (d->ttCreditOut_ + d->ttCreditCash_ - d->ttCashPay_);
ui->lbTotal->setText("" + QString::number(youHave / 100.0));
return true;
}
void MainWidget::closeEvent(QCloseEvent* event)
{
auto size = this->size();
Util::SetMainSize(size.width(), size.height());
QWidget::closeEvent(event);
}
void MainWidget::BaseInit()
{
ui->cbCashType->addItem("现金支出");
ui->cbCashType->addItem("现金收入");
ui->cbCashType->addItem("信用支出");
ui->cbCashType->addItem("信用收入");
ui->cbCashType->addItem("信用借款");
ui->cbCashType->setCurrentIndex(0);
}
bool MainWidget::RefreshClassify()
{
ClassifyRecordList classifyList;
if (!comSqlOpr_->GetClassifyList(classifyList)) {
QMessageBox::warning(this, "错误", QString::fromStdString(comSqlOpr_->GetLastErr()));
return false;
}
ui->cbClassify->clear();
for (const auto& classify : classifyList) {
ui->cbClassify->addItem(QString::fromStdString(classify.value));
}
if (!classifyList.empty()) {
ui->cbClassify->setCurrentIndex(0);
}
return true;
}
void MainWidget::SelectImg()
{
QString imagePath =
QFileDialog::getOpenFileName(this, "选择图片", QDir::homePath(), "图片文件 (*.jpg *.jpeg *.png *.bmp *.gif)");
if (imagePath.isEmpty()) {
return;
}
ui->edFile->setText(imagePath);
}
bool MainWidget::AddClassify()
{
// 弹出输入对话框
bool ok;
QString text = QInputDialog::getText(this, tr("添加分类"), tr("请输入分类名称:"), QLineEdit::Normal, "", &ok);
text = text.trimmed();
if (!ok || text.isEmpty()) {
return false;
}
// 先检查是否已存在
if (comSqlOpr_->CheckClassifyExist(text.toStdString())) {
QMessageBox::warning(this, tr("错误"), tr("分类已存在"));
return false;
}
CommonRecord record;
record.key = "分类";
record.value = text.toStdString();
record.type = "";
if (!comSqlOpr_->InserItem(record)) {
QMessageBox::warning(this, tr("错误"), tr("添加分类失败"));
return false;
}
RefreshClassify();
return true;
}
bool MainWidget::DelClassify()
{
auto curClassify = ui->cbClassify->currentText();
if (curClassify == "默认") {
QMessageBox::warning(this, "错误", "默认分类无法删除");
return false;
}
if (QMessageBox::question(this, "删除分类", QString("确定删除分类<%1>吗?").arg(curClassify)) != QMessageBox::Yes) {
return false;
}
if (!comSqlOpr_->DeleteItem(curClassify.toStdString())) {
QMessageBox::warning(this, "错误", QString::fromStdString(comSqlOpr_->GetLastErr()));
return false;
}
RefreshClassify();
return true;
}
void MainWidget::Record()
{
// 确认添加
if (QMessageBox::question(this, "确认添加", "确定添加记录吗?") != QMessageBox::Yes) {
return;
}
// 款项分类
auto curType = ui->cbCashType->currentText();
// 基础分类
auto curClassify = ui->cbClassify->currentText();
// 记录事项
auto thing = ui->lineEdit->text().trimmed();
if (thing.isEmpty()) {
QMessageBox::warning(this, "错误", "记录事项不能为空");
return;
}
// 时间日期
auto date = ui->dateEdit->date();
auto time = ui->timeEdit->time();
QDateTime dateTime(date, time);
auto dateTimeStr = dateTime.toString("yyyy-MM-dd HH:mm:ss");
// 金额
// 查看附件
auto attach = ui->edFile->text().trimmed();
if (!attach.isEmpty()) {
QFile file(attach);
if (!file.exists()) {
QMessageBox::warning(this, "错误", "附件文件不存在");
return;
}
}
AccountRecord record;
record.money = Util::CashInt(ui->edCash->text().toDouble());
record.thing = thing.toStdString();
record.dt = dateTimeStr.toStdString();
record.classify = curClassify.toStdString();
record.payType = curType.toStdString();
if (!attach.isEmpty()) {
record.additionFile = Util::NewUUIDName(attach).toStdString();
}
if (!sqlOpr_->AppendAccount(record)) {
QMessageBox::warning(this, "错误", QString::fromStdString(sqlOpr_->GetLastErr()));
return;
}
if (!attach.isEmpty()) {
QFile::copy(attach, QString::fromStdString(Util::GetMediaDir() + "/" + record.additionFile));
};
Calculate();
QMessageBox::information(this, "提示", "添加成功");
}
void MainWidget::Search()
{
auto* d = SharedData::instance();
if (ui->ckType->isChecked()) {
d->flType = true;
d->type = ui->cbCashType->currentText();
} else {
d->flType = false;
}
if (ui->ckKey->isChecked()) {
d->flKeys = true;
d->key = ui->edKey->text().trimmed();
if (d->key.isEmpty()) {
QMessageBox::warning(this, "错误", "搜索关键字不能为空");
return;
}
} else {
d->flKeys = false;
}
if (ui->ckDays->isChecked()) {
d->flDays = true;
if (ui->edDays->text().isEmpty()) {
QMessageBox::warning(this, "错误", "天数不能为空");
return;
}
d->days = ui->edDays->text().toInt();
} else {
d->flDays = false;
}
if (ui->rbBetween->isChecked()) {
d->filter = CashFilter::FIL_BETWEEN;
if (ui->edLowCash->text().isEmpty() || ui->edHighCash->text().isEmpty()) {
QMessageBox::warning(this, "错误", "金额范围不能为空");
return;
}
d->lowMoney = static_cast<int>(ui->edLowCash->text().toDouble() * 100);
d->highMoney = static_cast<int>(ui->edHighCash->text().toDouble() * 100);
} else if (ui->rbHigh->isChecked()) {
d->filter = CashFilter::FIL_HIGHER;
if (ui->edHighCash->text().isEmpty()) {
QMessageBox::warning(this, "错误", "金额高范围不能为空");
return;
}
d->highMoney = static_cast<int>(ui->edHighCash->text().toDouble() * 100);
} else if (ui->rbLow->isChecked()) {
d->filter = CashFilter::FIL_LOWER;
if (ui->edLowCash->text().isEmpty()) {
QMessageBox::warning(this, "错误", "金额低范围不能为空");
return;
}
d->lowMoney = static_cast<int>(ui->edLowCash->text().toDouble() * 100);
} else {
d->filter = CashFilter::FIL_NO_LIMIT;
}
if (ui->ckClassify->isChecked()) {
d->flClassify = true;
d->classify = ui->cbClassify->currentText();
} else {
d->flClassify = false;
}
FilterForm* filterForm = new FilterForm(this, sqlOpr_, comSqlOpr_, repaySqlOpr_);
filterForm->exec();
Calculate();
}

50
mainwidget.h Normal file
View File

@@ -0,0 +1,50 @@
#ifndef MAINWIDGET_H
#define MAINWIDGET_H
#include "SqlOpr.h"
#include "util.h"
#include "statistic.h"
#include <QDialog>
#include <memory>
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWidget;
}
QT_END_NAMESPACE
class MainWidget : public QDialog
{
Q_OBJECT
public:
MainWidget(QWidget* parent = nullptr);
~MainWidget();
public:
void Init();
private:
bool RefreshData();
bool Calculate();
void closeEvent(QCloseEvent* event) override;
private:
void BaseInit();
bool RefreshClassify();
void SelectImg();
private:
bool AddClassify();
bool DelClassify();
void Record();
void Search();
private:
std::shared_ptr<Statistic> statistic_;
std::unique_ptr<ACTSqlOpr> sqlOpr_;
std::unique_ptr<ComSqlOpr> comSqlOpr_;
std::unique_ptr<RepaySqlOpr> repaySqlOpr_;
Ui::MainWidget* ui;
};
#endif // MAINWIDGET_H

341
mainwidget.ui Normal file
View File

@@ -0,0 +1,341 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWidget</class>
<widget class="QDialog" name="MainWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>687</width>
<height>237</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWidget</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="lbTotal">
<property name="text">
<string>NULL</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>剩余总现金(元):</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lbCurCash">
<property name="text">
<string>NULL</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>信用收支(元)(支出/收入):</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lbCredit">
<property name="text">
<string>NULL</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>款项类型:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cbCashType">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QDateEdit" name="dateEdit"/>
</item>
<item>
<widget class="QTimeEdit" name="timeEdit"/>
</item>
<item>
<widget class="QPushButton" name="btnSetNow">
<property name="text">
<string>设置为现在</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnClassifyAdd">
<property name="text">
<string>分类增</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnClassifyDel">
<property name="text">
<string>分类删</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnSet">
<property name="text">
<string>设定</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>记录事项:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit"/>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>金额(元):</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edCash">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cbClassify">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnRecord">
<property name="text">
<string>记录</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QCheckBox" name="ckType">
<property name="text">
<string>款项筛选</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="ckDays">
<property name="text">
<string>最近天数</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QCheckBox" name="ckKey">
<property name="text">
<string>关键字筛</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QCheckBox" name="ckClassify">
<property name="text">
<string>分类筛选</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QLabel" name="label_9">
<property name="text">
<string>金额小:</string>
</property>
</widget>
</item>
<item row="0" column="5">
<widget class="QLineEdit" name="edLowCash"/>
</item>
<item row="0" column="6">
<widget class="QLabel" name="label_10">
<property name="text">
<string>金额大:</string>
</property>
</widget>
</item>
<item row="0" column="7">
<widget class="QLineEdit" name="edHighCash">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="rbHigh">
<property name="text">
<string>金额大于</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QRadioButton" name="rbLow">
<property name="text">
<string>金额小于</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QRadioButton" name="rbBetween">
<property name="text">
<string>金额之间</string>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QRadioButton" name="rbNoLimit">
<property name="text">
<string>金额不限</string>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QLabel" name="label_2">
<property name="text">
<string>关键字:</string>
</property>
</widget>
</item>
<item row="1" column="5">
<widget class="QLineEdit" name="edKey"/>
</item>
<item row="1" column="6">
<widget class="QLabel" name="label_7">
<property name="text">
<string>天范围:</string>
</property>
</widget>
</item>
<item row="1" column="7">
<widget class="QLineEdit" name="edDays"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QPushButton" name="btnClearFile">
<property name="text">
<string>清除</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnAddFile">
<property name="text">
<string>添加附件图</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edFile"/>
</item>
<item>
<widget class="QPushButton" name="btnSearch">
<property name="text">
<string>筛选查找</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

1
ofen Submodule

Submodule ofen added at 55f0045958

133
recordedit.cpp Normal file
View File

@@ -0,0 +1,133 @@
#include "recordedit.h"
#include "repayment.h"
#include "ui_recordedit.h"
#include "util.h"
#include <QDateTime>
#include <QMessageBox>
RecordEdit::RecordEdit(QWidget* parent, std::unique_ptr<ComSqlOpr>& comSqlOpr, std::unique_ptr<RepaySqlOpr>& repaySqlOpr)
: QDialog(parent), ui(new Ui::RecordEdit), comSqlOpr_(comSqlOpr), repaySqlOpr_(repaySqlOpr)
{
ui->setupUi(this);
ui->cbCalssify->setEditable(true);
ui->timeEdit->setDisplayFormat("HH:mm:ss");
ui->edCash->setValidator(new QDoubleValidator(0.0, 10000000.0, 2, this));
ui->edId->setReadOnly(true);
connect(ui->btnModify, &QPushButton::clicked, this, &RecordEdit::Modify);
connect(ui->btnCancel, &QPushButton::clicked, this, &RecordEdit::reject);
connect(ui->btnCurDate, &QPushButton::clicked, this, [this]() {
auto curContent = ui->pedMark->toPlainText();
curContent += "\n";
auto curDt = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
curContent += curDt;
ui->pedMark->setPlainText(curContent);
});
connect(ui->btnYiHuanQing, &QPushButton::clicked, this, [this]() {
auto curContent = ui->pedMark->toPlainText();
if (curContent.contains("已还清")) {
return;
}
curContent += "\n";
curContent += "已还清。";
ui->pedMark->setPlainText(curContent);
});
connect(ui->btnRepay, &QPushButton::clicked, this, [this]() {
QString info;
info.append(QString::number(record_.id));
info.append(" ");
info.append(QString::fromStdString(record_.payType));
info.append(" ");
info.append(QString::fromStdString(record_.dt));
info.append(" ");
info.append(QString::fromStdString(record_.classify));
auto* payment = new Repayment(this, repaySqlOpr_);
payment->SetInformation(info);
payment->SetRecord(record_);
payment->exec();
});
setWindowTitle("编辑");
}
RecordEdit::~RecordEdit()
{
delete ui;
}
int RecordEdit::exec()
{
modify_ = false;
ShowData();
if (!over_) {
return QDialog::Rejected;
}
return QDialog::exec();
}
void RecordEdit::ShowData()
{
over_ = false;
ClassifyRecordList classifyList;
if (!comSqlOpr_->GetClassifyList(classifyList)) {
QMessageBox::warning(this, "错误", QString::fromStdString(comSqlOpr_->GetLastErr()));
return;
}
ui->cbCalssify->clear();
for (const auto& classify : classifyList) {
ui->cbCalssify->addItem(QString::fromStdString(classify.value));
}
if (!classifyList.empty()) {
ui->cbCalssify->setCurrentText(QString::fromStdString(record_.classify));
}
ui->edId->setText(QString::number(record_.id));
ui->cbType->addItem("现金支出");
ui->cbType->addItem("现金收入");
ui->cbType->addItem("信用支出");
ui->cbType->addItem("信用收入");
ui->cbType->addItem("信用借款");
QDateTime datetime = QDateTime::fromString(QString::fromStdString(record_.dt), "yyyy-MM-dd hh:mm:ss");
ui->dateEdit->setDate(datetime.date());
ui->timeEdit->setTime(datetime.time());
ui->edCash->setText(QString::number(record_.money / 100.0));
ui->pedContent->setPlainText(QString::fromStdString(record_.thing));
ui->pedMark->setPlainText(QString::fromStdString(record_.remark));
ui->cbType->setCurrentText(QString::fromStdString(record_.payType));
if (record_.payType == "现金支出" || record_.payType == "现金收入") {
ui->btnRepay->setEnabled(false);
} else {
ui->btnRepay->setEnabled(true);
}
over_ = true;
}
void RecordEdit::GetData()
{
record_.classify = ui->cbCalssify->currentText().toStdString();
record_.payType = ui->cbType->currentText().toStdString();
QDateTime datetime;
datetime.setDate(ui->dateEdit->date());
datetime.setTime(ui->timeEdit->time());
auto strMoney = ui->edCash->text();
record_.money = Util::CashInt(strMoney.toDouble());
record_.dt = datetime.toString("yyyy-MM-dd hh:mm:ss").toStdString();
record_.thing = ui->pedContent->toPlainText().toStdString();
record_.remark = ui->pedMark->toPlainText().toStdString();
}
void RecordEdit::Modify()
{
if (QMessageBox::question(this, "确认修改", "确定修改记录吗?") != QMessageBox::Yes) {
return;
}
modify_ = true;
GetData();
accept();
}

39
recordedit.h Normal file
View File

@@ -0,0 +1,39 @@
#ifndef RECORDEDIT_H
#define RECORDEDIT_H
#include "SqlOpr.h"
#include <QDialog>
namespace Ui {
class RecordEdit;
}
class RecordEdit : public QDialog
{
Q_OBJECT
public:
explicit RecordEdit(QWidget* parent, std::unique_ptr<ComSqlOpr>& comSqlOpr, std::unique_ptr<RepaySqlOpr>& repaySqlOpr);
~RecordEdit();
public:
int exec() override;
public:
bool modify_ = false;
AccountRecord record_;
private:
void ShowData();
void GetData();
void Modify();
private:
bool autoChange_{false};
bool over_{};
std::unique_ptr<RepaySqlOpr>& repaySqlOpr_;
std::unique_ptr<ComSqlOpr>& comSqlOpr_;
Ui::RecordEdit* ui;
};
#endif // RECORDEDIT_H

206
recordedit.ui Normal file
View File

@@ -0,0 +1,206 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RecordEdit</class>
<widget class="QDialog" name="RecordEdit">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>305</width>
<height>386</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>序号:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edId"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>类型:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cbType">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>分类:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cbCalssify">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>金额:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edCash"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label_7">
<property name="text">
<string>日期:</string>
</property>
</widget>
</item>
<item>
<widget class="QDateEdit" name="dateEdit"/>
</item>
<item>
<widget class="QTimeEdit" name="timeEdit"/>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>内容:</string>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="pedContent"/>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>备注:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QPushButton" name="btnYiHuanQing">
<property name="text">
<string>已还清</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnRepay">
<property name="text">
<string>还款</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnCurDate">
<property name="text">
<string>当前时间</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPlainTextEdit" name="pedMark"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnModify">
<property name="text">
<string>修改</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnCancel">
<property name="text">
<string>取消</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

225
repayment.cpp Normal file
View File

@@ -0,0 +1,225 @@
#include "repayment.h"
#include "ui_repayment.h"
#include "util.h"
#include <QDateTime>
#include <QInputDialog>
#include <QMenu>
#include <QMessageBox>
Repayment::Repayment(QWidget* parent, std::unique_ptr<RepaySqlOpr>& repaySqlOpr)
: QDialog(parent), ui(new Ui::Repayment), repaySqlOpr_(repaySqlOpr)
{
ui->setupUi(this);
Init();
setWindowTitle("还款情况");
resize(800, 600);
}
Repayment::~Repayment()
{
delete ui;
}
int Repayment::exec()
{
ui->lbInfo->setText(info_);
ShowData();
if (!over_) {
return QDialog::Rejected;
}
return QDialog::exec();
}
void Repayment::SetInformation(const QString& info)
{
info_ = info;
}
void Repayment::SetRecord(const AccountRecord& record)
{
record_ = record;
}
void Repayment::ShowData()
{
over_ = false;
RepayRecordList reuslt;
if (!repaySqlOpr_->GetRepayResult(reuslt, record_.id)) {
QMessageBox::critical(this, "错误", "获取还款记录失败");
return;
}
tw_->setRowCount(reuslt.size());
for (int row = 0; row < reuslt.size(); ++row) {
QTableWidgetItem* item = new QTableWidgetItem(QString::number(reuslt[row].id));
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
tw_->setItem(row, 0, item);
item = new QTableWidgetItem(QString::number(reuslt[row].accID));
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
tw_->setItem(row, 1, item);
item = new QTableWidgetItem(QString::number(Util::CashDouble(reuslt[row].money)));
tw_->setItem(row, 2, item);
item = new QTableWidgetItem(QString::fromStdString(reuslt[row].dt));
tw_->setItem(row, 3, item);
item = new QTableWidgetItem(QString::fromStdString(reuslt[row].remark));
tw_->setItem(row, 4, item);
}
ui->edPay->setText(QString::number(Util::CashDouble(record_.money)) + "");
// 统计还了多少钱
BasicAnasys(reuslt);
over_ = true;
}
void Repayment::Init()
{
ui->edPay->setReadOnly(true);
ui->edResult->setReadOnly(true);
auto* lay = new QVBoxLayout(this);
tw_ = new QTableWidget(this);
QStringList headers;
headers << "ID" << "记录ID" << "金额" << "日期" << "备注";
tw_->setColumnCount(headers.size());
tw_->setHorizontalHeaderLabels(headers);
tw_->setSelectionBehavior(QAbstractItemView::SelectRows);
tw_->setColumnWidth(0, 50);
tw_->setColumnWidth(1, 50);
tw_->setColumnWidth(2, 100);
tw_->setColumnWidth(3, 150);
tw_->setColumnWidth(4, 100);
lay->addWidget(tw_);
ui->widget->setLayout(lay);
tw_->setContextMenuPolicy(Qt::CustomContextMenu);
connect(tw_, &QTableWidget::customContextMenuRequested, this, &Repayment::ShowContextMenu);
connect(ui->btnSave, &QPushButton::clicked, this, &Repayment::Save);
}
void Repayment::ShowContextMenu(const QPoint& pos)
{
QList<QTableWidgetItem*> selectedItems = tw_->selectedItems();
if (selectedItems.isEmpty()) {
QAction* acAdd = new QAction("添加", this);
QMenu menu(this);
connect(acAdd, &QAction::triggered, this, [this]() {
bool ok = false;
double amount =
QInputDialog::getDouble(this, "录入金额", "请输入金额:", 0.0, 0.0, 1000000.0, 2, &ok, Qt::WindowFlags(), 1.0);
if (!ok || amount <= 0) {
return;
}
int row = tw_->rowCount();
tw_->insertRow(row);
QString currentDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
auto* nId = new QTableWidgetItem("");
nId->setFlags(nId->flags() & ~Qt::ItemIsEditable);
tw_->setItem(row, 0, nId);
auto* rIdItem = new QTableWidgetItem(QString::number(record_.id));
rIdItem->setFlags(rIdItem->flags() & ~Qt::ItemIsEditable);
tw_->setItem(row, 1, rIdItem);
tw_->setItem(row, 2, new QTableWidgetItem(QString::number(amount, 'f', 2)));
auto* dateCur = new QTableWidgetItem(currentDateTime);
dateCur->setFlags(dateCur->flags() & ~Qt::ItemIsEditable);
tw_->setItem(row, 3, dateCur);
tw_->setItem(row, 4, new QTableWidgetItem(""));
tw_->resizeRowToContents(row);
tw_->scrollToItem(tw_->item(row, 0));
});
menu.addAction(acAdd);
menu.exec(tw_->viewport()->mapToGlobal(pos));
return;
}
if (selectedItems.size() == 5) {
QAction* acDel = new QAction("删除", this);
QMenu menu(this);
connect(acDel, &QAction::triggered, this, [this, selectedItems]() {
auto strId = selectedItems[0]->text();
if (strId.isEmpty()) {
return;
}
int id = strId.toInt();
// 确认
if (QMessageBox::No == QMessageBox::question(this, "删除", "确定删除吗?")) {
return;
}
if (!repaySqlOpr_->deleteRepayRecord(id)) {
QMessageBox::critical(this, "错误", "删除失败");
return;
}
tw_->removeRow(selectedItems[0]->row());
});
menu.addAction(acDel);
menu.exec(tw_->viewport()->mapToGlobal(pos));
}
}
void Repayment::Save()
{
int rowCount = tw_->rowCount();
if (rowCount < 1) {
return;
}
for (int row = 0; row < rowCount; ++row) {
auto* id = tw_->item(row, 0);
auto* rId = tw_->item(row, 1);
auto* amount = tw_->item(row, 2);
auto* date = tw_->item(row, 3);
auto* note = tw_->item(row, 4);
auto idText = id->text();
if (idText.isEmpty()) {
RepayRecord rd;
rd.dt = date->text().toStdString();
rd.accID = record_.id;
rd.money = Util::CashInt(amount->text().toDouble());
rd.remark = note->text().toStdString();
if (!repaySqlOpr_->InsertRepayRecord(rd)) {
QMessageBox::critical(this, "错误", "添加还款记录失败");
return;
}
id->setText(QString::number(rd.id));
} else {
RepayRecord rd;
rd.id = idText.toInt();
rd.dt = date->text().toStdString();
rd.accID = record_.id;
rd.money = Util::CashInt(amount->text().toDouble());
rd.remark = note->text().toStdString();
if (!repaySqlOpr_->updateRepayRecord(rd)) {
QMessageBox::critical(this, "错误", "更新还款记录失败");
return;
}
}
}
QMessageBox::information(this, "提示", "保存成功");
}
void Repayment::BasicAnasys(const RepayRecordList& rList)
{
int32_t total{};
for (const auto& rd : rList) {
total += rd.money;
}
if (total >= record_.money) {
ui->edResult->setText("剩余" + QString::number(Util::CashDouble(total - record_.money)) + "元,已还清。");
} else {
ui->edResult->setText("剩余" + QString::number(Util::CashDouble(record_.money - total)) + "元,未还清。");
}
}

41
repayment.h Normal file
View File

@@ -0,0 +1,41 @@
#ifndef REPAYMENT_H
#define REPAYMENT_H
#include "SqlOpr.h"
#include <QDialog>
#include <QTableWidget>
namespace Ui {
class Repayment;
}
class Repayment : public QDialog
{
Q_OBJECT
public:
explicit Repayment(QWidget* parent, std::unique_ptr<RepaySqlOpr>& repaySqlOpr);
~Repayment();
public:
int exec() override;
void SetInformation(const QString& info);
void SetRecord(const AccountRecord& record);
private:
void ShowData();
void Init();
void ShowContextMenu(const QPoint& pos);
void Save();
void BasicAnasys(const RepayRecordList& rList);
private:
bool over_{};
QString info_;
Ui::Repayment* ui;
AccountRecord record_;
QTableWidget* tw_{};
std::unique_ptr<RepaySqlOpr>& repaySqlOpr_;
};
#endif // REPAYMENT_H

86
repayment.ui Normal file
View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Repayment</class>
<widget class="QDialog" name="Repayment">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>476</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>统计</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="lbInfo">
<property name="text">
<string>NULL</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>总白条:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edPay"/>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>结果:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edResult"/>
</item>
<item>
<widget class="QPushButton" name="btnSave">
<property name="text">
<string>保存</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>详情</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QWidget" name="widget" native="true"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

5
res.qrc Normal file
View File

@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>resource/ico.ico</file>
</qresource>
</RCC>

View File

@@ -0,0 +1 @@
IDI_APP_ICON ICON "ico.ico"

BIN
resource/ico.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

28
resource/qss.qrc Normal file
View File

@@ -0,0 +1,28 @@
<RCC>
<qresource prefix="/">
<file>qss/flatgray.css</file>
<file>qss/flatgray/add_bottom.png</file>
<file>qss/flatgray/add_left.png</file>
<file>qss/flatgray/add_right.png</file>
<file>qss/flatgray/add_top.png</file>
<file>qss/flatgray/arrow_bottom.png</file>
<file>qss/flatgray/arrow_left.png</file>
<file>qss/flatgray/arrow_right.png</file>
<file>qss/flatgray/arrow_top.png</file>
<file>qss/flatgray/branch_close.png</file>
<file>qss/flatgray/branch_open.png</file>
<file>qss/flatgray/calendar_nextmonth.png</file>
<file>qss/flatgray/calendar_prevmonth.png</file>
<file>qss/flatgray/checkbox_checked.png</file>
<file>qss/flatgray/checkbox_checked_disable.png</file>
<file>qss/flatgray/checkbox_parcial.png</file>
<file>qss/flatgray/checkbox_parcial_disable.png</file>
<file>qss/flatgray/checkbox_unchecked.png</file>
<file>qss/flatgray/checkbox_unchecked_disable.png</file>
<file>qss/flatgray/menu_checked.png</file>
<file>qss/flatgray/radiobutton_checked.png</file>
<file>qss/flatgray/radiobutton_checked_disable.png</file>
<file>qss/flatgray/radiobutton_unchecked.png</file>
<file>qss/flatgray/radiobutton_unchecked_disable.png</file>
</qresource>
</RCC>

694
resource/qss/flatgray.css Normal file
View File

@@ -0,0 +1,694 @@
QPalette{background:#FFFFFF;}*{outline:0px;color:#57595B;}
QGraphicsView{
border:1px solid #B6B6B6;
qproperty-backgroundBrush:#FFFFFF;
}
QWidget[form="true"],QLabel[frameShape="1"]{
border:1px solid #B6B6B6;
border-radius:0px;
}
QWidget[form="bottom"]{
background:#E4E4E4;
}
QWidget[form="bottom"] .QFrame{
border:1px solid #57595B;
}
QWidget[form="bottom"] QLabel,QWidget[form="title"] QLabel{
border-radius:0px;
color:#57595B;
background:none;
border-style:none;
}
QWidget[form="title"],QWidget[nav="left"],QWidget[nav="top"] QAbstractButton{
border-style:none;
border-radius:0px;
padding:5px;
color:#57595B;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4);
}
QWidget[nav="top"] QAbstractButton:hover,QWidget[nav="top"] QAbstractButton:pressed,QWidget[nav="top"] QAbstractButton:checked{
border-style:solid;
border-width:0px 0px 2px 0px;
padding:4px 4px 2px 4px;
border-color:#575959;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6);
}
QWidget[nav="left"] QAbstractButton{
border-radius:0px;
color:#57595B;
background:none;
border-style:none;
}
QWidget[nav="left"] QAbstractButton:hover{
color:#FFFFFF;
background-color:#575959;
}
QWidget[nav="left"] QAbstractButton:checked,QWidget[nav="left"] QAbstractButton:pressed{
color:#57595B;
border-style:solid;
border-width:0px 0px 0px 2px;
padding:4px 4px 4px 2px;
border-color:#575959;
background-color:#FFFFFF;
}
QWidget[video="true"] QLabel{
color:#57595B;
border:1px solid #B6B6B6;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4);
}
QWidget[video="true"] QLabel:focus{
border:1px solid #575959;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6);
}
QLineEdit:read-only{
background-color:#E4E4E4;
}
QLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{
border:1px solid #B6B6B6;
border-radius:3px;
padding:2px;
background:none;
selection-background-color:#575959;
selection-color:#FFFFFF;
}
QLineEdit:focus,QTextEdit:focus,QPlainTextEdit:focus,QSpinBox:focus,QDoubleSpinBox:focus,QComboBox:focus,QDateEdit:focus,QTimeEdit:focus,QDateTimeEdit:focus,QLineEdit:hover,QTextEdit:hover,QPlainTextEdit:hover,QSpinBox:hover,QDoubleSpinBox:hover,QComboBox:hover,QDateEdit:hover,QTimeEdit:hover,QDateTimeEdit:hover{
border:1px solid #B6B6B6;
}
QLineEdit[echoMode="2"]{
lineedit-password-character:9679;
}
.QFrame{
border:1px solid #B6B6B6;
border-radius:3px;
}
.QGroupBox{
border:1px solid #B6B6B6;
border-radius:5px;
margin-top:9px;
}
.QGroupBox::title{
subcontrol-origin:margin;
position:relative;
left:10px;
}
.QPushButton,.QToolButton{
border-style:none;
border:1px solid #B6B6B6;
color:#57595B;
padding:5px;
min-height:15px;
border-radius:5px;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4);
}
.QPushButton:hover,.QToolButton:hover{
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6);
}
.QPushButton:pressed,.QToolButton:pressed{
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4);
}
.QToolButton::menu-indicator{
image:None;
}
QToolButton#btnMenu,QPushButton#btnMenu_Min,QPushButton#btnMenu_Max,QPushButton#btnMenu_Close{
border-radius:3px;
color:#57595B;
padding:3px;
margin:0px;
background:none;
border-style:none;
}
QToolButton#btnMenu:hover,QPushButton#btnMenu_Min:hover,QPushButton#btnMenu_Max:hover{
color:#FFFFFF;
margin:1px 1px 2px 1px;
background-color:rgba(51,127,209,230);
}
QPushButton#btnMenu_Close:hover{
color:#FFFFFF;
margin:1px 1px 2px 1px;
background-color:rgba(238,0,0,128);
}
QRadioButton::indicator{
width:15px;
height:15px;
}
QRadioButton::indicator::unchecked{
image:url(:/qss/flatgray/radiobutton_unchecked.png);
}
QRadioButton::indicator::unchecked:disabled{
image:url(:/qss/flatgray/radiobutton_unchecked_disable.png);
}
QRadioButton::indicator::checked{
image:url(:/qss/flatgray/radiobutton_checked.png);
}
QRadioButton::indicator::checked:disabled{
image:url(:/qss/flatgray/radiobutton_checked_disable.png);
}
QGroupBox::indicator,QTreeView::indicator,QListView::indicator,QTableView::indicator{
padding:0px 0px 0px 0px;
}
QCheckBox::indicator,QGroupBox::indicator,QTreeView::indicator,QListView::indicator,QTableView::indicator{
width:13px;
height:13px;
}
QCheckBox::indicator:unchecked,QGroupBox::indicator:unchecked,QTreeView::indicator:unchecked,QListView::indicator:unchecked,QTableView::indicator:unchecked{
image:url(:/qss/flatgray/checkbox_unchecked.png);
}
QCheckBox::indicator:unchecked:disabled,QGroupBox::indicator:unchecked:disabled,QTreeView::indicator:unchecked:disabled,QListView::indicator:unchecked:disabled,QTableView::indicator:unchecked:disabled{
image:url(:/qss/flatgray/checkbox_unchecked_disable.png);
}
QCheckBox::indicator:checked,QGroupBox::indicator:checked,QTreeView::indicator:checked,QListView::indicator:checked,QTableView::indicator:checked{
image:url(:/qss/flatgray/checkbox_checked.png);
}
QCheckBox::indicator:checked:disabled,QGroupBox::indicator:checked:disabled,QTreeView::indicator:checked:disabled,QListView::indicator:checked:disabled,QTableView::indicator:checked:disabled{
image:url(:/qss/flatgray/checkbox_checked_disable.png);
}
QCheckBox::indicator:indeterminate,QGroupBox::indicator:indeterminate,QTreeView::indicator:indeterminate,QListView::indicator:indeterminate,QTableView::indicator:indeterminate{
image:url(:/qss/flatgray/checkbox_parcial.png);
}
QCheckBox::indicator:indeterminate:disabled,QGroupBox::indicator:indeterminate:disabled,QTreeView::indicator:indeterminate:disabled,QListView::indicator:indeterminate:disabled,QTableView::indicator:indeterminate:disabled{
image:url(:/qss/flatgray/checkbox_parcial_disable.png);
}
QTimeEdit::up-button,QDateEdit::up-button,QDateTimeEdit::up-button,QDoubleSpinBox::up-button,QSpinBox::up-button{
image:url(:/qss/flatgray/add_top.png);
width:10px;
height:10px;
padding:2px 5px 0px 0px;
}
QTimeEdit::down-button,QDateEdit::down-button,QDateTimeEdit::down-button,QDoubleSpinBox::down-button,QSpinBox::down-button{
image:url(:/qss/flatgray/add_bottom.png);
width:10px;
height:10px;
padding:0px 5px 2px 0px;
}
QTimeEdit::up-button:pressed,QDateEdit::up-button:pressed,QDateTimeEdit::up-button:pressed,QDoubleSpinBox::up-button:pressed,QSpinBox::up-button:pressed{
top:-2px;
}
QTimeEdit::down-button:pressed,QDateEdit::down-button:pressed,QDateTimeEdit::down-button:pressed,QDoubleSpinBox::down-button:pressed,QSpinBox::down-button:pressed,QSpinBox::down-button:pressed{
bottom:-2px;
}
QComboBox::down-arrow,QDateEdit[calendarPopup="true"]::down-arrow,QTimeEdit[calendarPopup="true"]::down-arrow,QDateTimeEdit[calendarPopup="true"]::down-arrow{
image:url(:/qss/flatgray/add_bottom.png);
width:10px;
height:10px;
right:2px;
}
QComboBox::drop-down,QDateEdit::drop-down,QTimeEdit::drop-down,QDateTimeEdit::drop-down{
subcontrol-origin:padding;
subcontrol-position:top right;
width:15px;
border-left-width:0px;
border-left-style:solid;
border-top-right-radius:3px;
border-bottom-right-radius:3px;
border-left-color:#B6B6B6;
}
QComboBox::drop-down:on{
top:1px;
}
QMenuBar::item{
color:#57595B;
background-color:#E4E4E4;
margin:0px;
padding:3px 10px;
}
QMenu,QMenuBar,QMenu:disabled,QMenuBar:disabled{
color:#57595B;
background-color:#E4E4E4;
border:1px solid #B6B6B6;
margin:0px;
}
QMenu::item{
padding:3px 20px;
}
QMenu::indicator{
width:20px;
height:13px;
}
QMenu::indicator::checked{
image:url(:/qss/flatgray/menu_checked.png);
}
QMenu::right-arrow{
image:url(:/qss/flatgray/arrow_right.png);
width:13px;
height:13px;
padding:0px 3px 0px 0px;
}
QMenu::item:selected,QMenuBar::item:selected{
color:#57595B;
border:0px solid #B6B6B6;
background:#F6F6F6;
}
QMenu::separator{
height:1px;
background:#B6B6B6;
}
QProgressBar{
min-height:10px;
background:#E4E4E4;
border-radius:5px;
text-align:center;
border:1px solid #E4E4E4;
}
QProgressBar:chunk{
border-radius:5px;
background-color:#B6B6B6;
}
QSlider::groove:horizontal{
height:8px;
border-radius:4px;
background:#E4E4E4;
}
QSlider::add-page:horizontal{
height:8px;
border-radius:4px;
background:#E4E4E4;
}
QSlider::sub-page:horizontal{
height:8px;
border-radius:4px;
background:#B6B6B6;
}
QSlider::handle:horizontal{
width:13px;
margin-top:-3px;
margin-bottom:-3px;
border-radius:6px;
background:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #FFFFFF,stop:0.8 #B6B6B6);
}
QSlider::groove:vertical{
width:8px;
border-radius:4px;
background:#E4E4E4;
}
QSlider::add-page:vertical{
width:8px;
border-radius:4px;
background:#B6B6B6;
}
QSlider::sub-page:vertical{
width:8px;
border-radius:4px;
background:#E4E4E4;
}
QSlider::handle:vertical{
height:14px;
margin-left:-3px;
margin-right:-3px;
border-radius:6px;
background:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #FFFFFF,stop:0.8 #B6B6B6);
}
QScrollBar:horizontal{
background:#E4E4E4;
padding:0px;
border-radius:6px;
max-height:12px;
}
QScrollBar::handle:horizontal{
background:#B6B6B6;
min-width:50px;
border-radius:6px;
}
QScrollBar::handle:horizontal:hover{
background:#575959;
}
QScrollBar::handle:horizontal:pressed{
background:#575959;
}
QScrollBar::add-page:horizontal{
background:none;
}
QScrollBar::sub-page:horizontal{
background:none;
}
QScrollBar::add-line:horizontal{
background:none;
}
QScrollBar::sub-line:horizontal{
background:none;
}
QScrollBar:vertical{
background:#E4E4E4;
padding:0px;
border-radius:6px;
max-width:12px;
}
QScrollBar::handle:vertical{
background:#B6B6B6;
min-height:50px;
border-radius:6px;
}
QScrollBar::handle:vertical:hover{
background:#575959;
}
QScrollBar::handle:vertical:pressed{
background:#575959;
}
QScrollBar::add-page:vertical{
background:none;
}
QScrollBar::sub-page:vertical{
background:none;
}
QScrollBar::add-line:vertical{
background:none;
}
QScrollBar::sub-line:vertical{
background:none;
}
QScrollArea{
border:0px;
}
QTreeView,QListView,QTableView,QTabWidget::pane{
border:1px solid #B6B6B6;
selection-background-color:#F6F6F6;
selection-color:#57595B;
alternate-background-color:#F6F6F6;
gridline-color:#B6B6B6;
}
QTreeView::branch:closed:has-children{
margin:4px;
border-image:url(:/qss/flatgray/branch_open.png);
}
QTreeView::branch:open:has-children{
margin:4px;
border-image:url(:/qss/flatgray/branch_close.png);
}
QTreeView,QListView,QTableView,QSplitter::handle,QTreeView::branch{
background:#FFFFFF;
}
QTableView::item:selected,QListView::item:selected,QTreeView::item:selected{
color:#57595B;
background:#E4E4E4;
}
QTableView::item:hover,QListView::item:hover,QTreeView::item:hover,QHeaderView,QHeaderView::section,QTableCornerButton:section{
color:#57595B;
background:#F6F6F6;
}
QTableView::item,QListView::item,QTreeView::item{
padding:1px;
margin:0px;
border:0px;
}
QHeaderView::section,QTableCornerButton:section{
padding:3px;
margin:0px;
border:1px solid #B6B6B6;
border-left-width:0px;
border-right-width:1px;
border-top-width:0px;
border-bottom-width:1px;
}
QTabBar::tab{
border:1px solid #B6B6B6;
color:#57595B;
margin:0px;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6);
}
QTabBar::tab:selected{
border-style:solid;
border-color:#575959;
background:#FFFFFF;
}
QTabBar::tab:top,QTabBar::tab:bottom{
padding:3px 8px 3px 8px;
}
QTabBar::tab:left,QTabBar::tab:right{
padding:8px 3px 8px 3px;
}
QTabBar::tab:top:selected{
border-width:2px 0px 0px 0px;
}
QTabBar::tab:right:selected{
border-width:0px 0px 0px 2px;
}
QTabBar::tab:bottom:selected{
border-width:0px 0px 2px 0px;
}
QTabBar::tab:left:selected{
border-width:0px 2px 0px 0px;
}
QTabBar::tab:first:top:selected,QTabBar::tab:first:bottom:selected{
border-left-width:1px;
border-left-color:#B6B6B6;
}
QTabBar::tab:first:left:selected,QTabBar::tab:first:right:selected{
border-top-width:1px;
border-top-color:#B6B6B6;
}
QTabBar::tab:last:top:selected,QTabBar::tab:last:bottom:selected{
border-right-width:1px;
border-right-color:#B6B6B6;
}
QTabBar::tab:last:left:selected,QTabBar::tab:last:right:selected{
border-bottom-width:1px;
border-bottom-color:#B6B6B6;
}
QStatusBar::item{
border:0px solid #E4E4E4;
border-radius:3px;
}
QToolBox::tab,QWidget[form="panel"]{
padding:3px;
border-radius:5px;
color:#57595B;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4);
}
QWidget[flag="paneltitle"]{
border-bottom-left-radius:0px;
border-bottom-right-radius:0px;
}
QWidget[flag="panelcontrol"]{
border-top-width:0px;
border-top-left-radius:0px;
border-top-right-radius:0px;
}
QToolTip{
border:0px solid #57595B;
padding:1px;
color:#57595B;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4);
}
QToolBox::tab:selected{
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6);
}
QPrintPreviewDialog QToolButton{
border:0px solid #57595B;
border-radius:0px;
margin:0px;
padding:3px;
background:none;
}
QColorDialog QPushButton,QFileDialog QPushButton{
min-width:80px;
}
QToolButton#qt_calendar_prevmonth{
icon-size:0px;
min-width:20px;
image:url(:/qss/flatgray/calendar_prevmonth.png);
}
QToolButton#qt_calendar_nextmonth{
icon-size:0px;
min-width:20px;
image:url(:/qss/flatgray/calendar_nextmonth.png);
}
QToolButton#qt_calendar_prevmonth,QToolButton#qt_calendar_nextmonth,QToolButton#qt_calendar_monthbutton,QToolButton#qt_calendar_yearbutton{
border:0px solid #57595B;
border-radius:3px;
margin:3px 3px 3px 3px;
padding:3px;
background:none;
}
QToolButton#qt_calendar_prevmonth:hover,QToolButton#qt_calendar_nextmonth:hover,QToolButton#qt_calendar_monthbutton:hover,QToolButton#qt_calendar_yearbutton:hover,QToolButton#qt_calendar_prevmonth:pressed,QToolButton#qt_calendar_nextmonth:pressed,QToolButton#qt_calendar_monthbutton:pressed,QToolButton#qt_calendar_yearbutton:pressed{
border:1px solid #B6B6B6;
}
QCalendarWidget QSpinBox#qt_calendar_yearedit{
margin:2px;
}
QCalendarWidget QToolButton::menu-indicator{
image:None;
}
QCalendarWidget QTableView{
border-width:0px;
}
QCalendarWidget QWidget#qt_calendar_navigationbar{
border:1px solid #B6B6B6;
border-width:1px 1px 0px 1px;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4);
}
QTableView[model="true"]::item{
padding:0px;
margin:0px;
}
QTableView QLineEdit,QTableView QComboBox,QTableView QSpinBox,QTableView QDoubleSpinBox,QTableView QDateEdit,QTableView QTimeEdit,QTableView QDateTimeEdit{
border-width:0px;
border-radius:0px;
}
QTableView QLineEdit:focus,QTableView QComboBox:focus,QTableView QSpinBox:focus,QTableView QDoubleSpinBox:focus,QTableView QDateEdit:focus,QTableView QTimeEdit:focus,QTableView QDateTimeEdit:focus{
border-width:0px;
border-radius:0px;
}
QLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{
background:#FFFFFF;
}
QTabWidget::pane:top{top:-1px;}
QTabWidget::pane:bottom{bottom:-1px;}
QTabWidget::pane:left{right:-1px;}
QTabWidget::pane:right{left:-1px;}
QDialog,QDial,#QUIWidgetMain{
background-color:#FFFFFF;
color:#57595B;
}
QDialogButtonBox>QPushButton{
min-width:50px;
}
QListView[noborder="true"],QTreeView[noborder="true"],QTabWidget[noborder="true"]::pane{
border-width:0px;
}
QToolBar>*,QStatusBar>*{
margin:2px;
}
*:disabled,QMenu::item:disabled,QTabBar:tab:disabled,QHeaderView::section:disabled{
background:#FFFFFF;
border-color:#E4E4E4;
color:#B6B6B6;
}
QLabel:disabled{
background:none;
}
/*TextColor:#57595B*/
/*PanelColor:#FFFFFF*/
/*BorderColor:#B6B6B6*/
/*NormalColorStart:#E4E4E4*/
/*NormalColorEnd:#E4E4E4*/
/*DarkColorStart:#F6F6F6*/
/*DarkColorEnd:#F6F6F6*/
/*HighColor:#575959*/

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

42
statistic.cpp Normal file
View File

@@ -0,0 +1,42 @@
#include "statistic.h"
#include "util.h"
Statistic::Statistic(std::unique_ptr<RepaySqlOpr>& repaySqlOpr) : repaySqlOpr_(repaySqlOpr)
{
}
Statistic::~Statistic()
{
}
void Statistic::Calculate(const AccountRecordList& list)
{
auto* d = SharedData::instance();
d->ClearTt();
for (const auto& item : list) {
if (item.payType == "现金支出") {
d->ttCashOut_ += item.money;
} else if (item.payType == "现金收入") {
d->ttCashIn_ += item.money;
} else if (item.payType == "信用支出") {
d->ttCreditOut_ += item.money;
RepayRecordList repayList;
if (repaySqlOpr_->GetRepayResult(repayList, item.id)) {
for (const auto& repay : repayList) {
d->ttCashPay_ += repay.money;
}
}
} else if (item.payType == "信用收入") {
d->ttCreditIn_ += item.money;
} else if (item.payType == "信用借款") {
d->ttCreditCash_ += item.money;
RepayRecordList repayList;
if (repaySqlOpr_->GetRepayResult(repayList, item.id)) {
for (const auto& repay : repayList) {
d->ttCashPay_ += repay.money;
}
}
}
}
}

19
statistic.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef STATISTIC_H
#define STATISTIC_H
#include "SqlOpr.h"
class Statistic
{
public:
Statistic(std::unique_ptr<RepaySqlOpr>& repaySqlOpr);
~Statistic();
public:
void Calculate(const AccountRecordList& list);
private:
std::unique_ptr<RepaySqlOpr>& repaySqlOpr_;
};
#endif // STATISTIC_H

145
util.cpp Normal file
View File

@@ -0,0 +1,145 @@
#include "util.h"
#include <QDir>
#include <QUuid>
#include <fstream>
static std::string gConfigPath;
static std::string gWorkDir;
static nlohmann::json gConfig;
void GenerateDefaultConfig(const std::string& path)
{
nlohmann::json config;
config["MainWidth"] = 1000;
config["MainHeight"] = 600;
config["Editor"] = "Notepad";
config["MediaDir"] = "./Media";
config["CurCash"] = 0;
config["CurSave"] = 0;
std::ofstream configFile(path);
configFile << config.dump(4);
configFile.close();
gConfig = config;
}
Util::Util()
{
}
bool Util::Init()
{
gWorkDir = QDir::homePath().toStdString() + "/.config/SimpleAccount";
if (!QDir(QString::fromStdString(gWorkDir)).exists()) {
QDir().mkdir(QString::fromStdString(gWorkDir));
}
gConfigPath = gWorkDir + "/config.json";
return LoadConfig();
}
std::string Util::GetWorkDir()
{
return gWorkDir;
}
std::string Util::GetMediaDir()
{
return gConfig["MediaDir"].get<std::string>();
}
std::string Util::GetConfigPath()
{
return gConfigPath;
}
std::pair<int, int> Util::GetMainSize()
{
return std::make_pair(gConfig["MainWidth"].get<int>(), gConfig["MainHeight"].get<int>());
}
bool Util::SetMainSize(int w, int h)
{
LoadConfig();
gConfig["MainWidth"] = w;
gConfig["MainHeight"] = h;
std::ofstream configFile(gConfigPath);
if (!configFile.is_open()) {
return false;
}
configFile << gConfig.dump(4);
configFile.close();
return true;
}
std::string Util::GetEditor()
{
return gConfig["Editor"].get<std::string>();
}
QString Util::NewUUIDName(const QString& path)
{
QFileInfo fileInfo(path);
QString originalSuffix = fileInfo.suffix();
QString newName = QUuid::createUuid().toString(QUuid::WithoutBraces);
if (!originalSuffix.isEmpty()) {
newName += "." + originalSuffix;
}
return newName;
}
double Util::GetCurCash()
{
return gConfig["CurCash"].get<double>();
}
double Util::GetCurSave()
{
return gConfig["CurSave"].get<double>();
}
int Util::CashInt(double cash)
{
return static_cast<int32_t>(round(cash * 100));
}
double Util::CashDouble(int cash)
{
return static_cast<double>(cash) / 100.0;
}
bool Util::LoadConfig()
{
QFile file(QString::fromStdString(gConfigPath));
if (file.exists()) {
std::ifstream configFile(gConfigPath);
if (!configFile.is_open()) {
return false;
}
gConfig = nlohmann::json::parse(configFile);
configFile.close();
} else {
GenerateDefaultConfig(gConfigPath);
}
return true;
}
SharedData::~SharedData()
{
}
SharedData* SharedData::instance()
{
static SharedData data;
return &data;
}
void SharedData::ClearTt()
{
ttCashOut_ = 0;
ttCashIn_ = 0;
ttCreditIn_ = 0;
ttCreditOut_ = 0;
ttCreditCash_ = 0;
ttCashPay_ = 0;
}

79
util.h Normal file
View File

@@ -0,0 +1,79 @@
#ifndef UTIL_H
#define UTIL_H
#include <QFileInfo>
#include <QString>
#include <nlohmann/json.hpp>
#include <string>
class Util
{
public:
Util();
public:
static bool Init();
public:
static std::string GetWorkDir();
static std::string GetMediaDir();
static std::string GetConfigPath();
static std::pair<int, int> GetMainSize();
static bool SetMainSize(int w, int h);
static std::string GetEditor();
static QString NewUUIDName(const QString& path);
static double GetCurCash();
static double GetCurSave();
static int CashInt(double cash);
static double CashDouble(int cash);
private:
static bool LoadConfig();
};
enum CashFilter {
FIL_NO_LIMIT = 0,
FIL_LOWER,
FIL_HIGHER,
FIL_BETWEEN
};
class SharedData
{
private:
SharedData() = default;
~SharedData();
public:
static SharedData* instance();
// 条件
public:
bool flType{};
QString type{};
bool flDays{};
bool flKeys{};
bool flClassify{};
QString classify{};
QString key{};
int days{};
int lowMoney{};
int highMoney{};
CashFilter filter{};
public:
void ClearTt();
// 结果
public:
int32_t ttCashOut_{};
int32_t ttCashIn_{};
int32_t ttCreditIn_{};
int32_t ttCreditOut_{};
int32_t ttCreditCash_{};
// 还款金额
int32_t ttCashPay_{};
// 不计算现有现金的理论现金
int32_t ttCash_{};
};
#endif // UTIL_H