commit 21b9cc0590106c8c6e4765c06499585ee0644a63 Author: taynpg Date: Fri Feb 27 12:29:27 2026 +0800 账单记录器迁移。 diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..36e4997 --- /dev/null +++ b/.clang-format @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7034ad --- /dev/null +++ b/.gitignore @@ -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* \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f5e7bbc --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ofen"] + path = ofen + url = https://www.sinxmiao.cn/taynpg/ofen diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9163a59 --- /dev/null +++ b/.vscode/settings.json @@ -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" + } + } + ] + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d9334b2 --- /dev/null +++ b/CMakeLists.txt @@ -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() diff --git a/README.md b/README.md new file mode 100644 index 0000000..e3f13a9 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# SimpleAccount + +记账工具。 \ No newline at end of file diff --git a/SqlOpr.cpp b/SqlOpr.cpp new file mode 100644 index 0000000..a204707 --- /dev/null +++ b/SqlOpr.cpp @@ -0,0 +1,495 @@ +#include "SqlOpr.h" +#include +#include +#include +#include +#include + +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> 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; +} diff --git a/SqlOpr.h b/SqlOpr.h new file mode 100644 index 0000000..12cb186 --- /dev/null +++ b/SqlOpr.h @@ -0,0 +1,111 @@ +#ifndef SQL_OPR_H +#define SQL_OPR_H + +#include +#include +#include +#include +#include + +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; +using AccountRecordList = std::vector; +using RepayRecordList = std::vector; + +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 \ No newline at end of file diff --git a/filterform.cpp b/filterform.cpp new file mode 100644 index 0000000..e46325c --- /dev/null +++ b/filterform.cpp @@ -0,0 +1,439 @@ +#include "filterform.h" + +#include "recordedit.h" +#include "ui_filterform.h" +#include +#include +#include +#include +#include +#include +#include +#include + +FilterForm::FilterForm(QWidget* parent, std::unique_ptr& sqlOpr, std::unique_ptr& comSqlOpr, + std::unique_ptr& repaySqlOpr) + : QDialog(parent), ui(new Ui::FilterForm), sqlOpr_(sqlOpr), comSqlOpr_(comSqlOpr), repaySqlOpr_(repaySqlOpr) +{ + ui->setupUi(this); + statistic_ = std::make_shared(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>> FilterForm::GetClassifyCash(const AccountRecordList& list, + bool isCash) +{ + std::vector>> ret; + std::unordered_map> 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>& a, + const std::pair>& 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 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)); + } + } + }); +} \ No newline at end of file diff --git a/filterform.h b/filterform.h new file mode 100644 index 0000000..5f4e4c6 --- /dev/null +++ b/filterform.h @@ -0,0 +1,52 @@ +#ifndef FILTERFORM_H +#define FILTERFORM_H + +#include "SqlOpr.h" +#include "statistic.h" +#include "util.h" +#include +#include +#include +#include + +namespace Ui { +class FilterForm; +} + +class FilterForm : public QDialog +{ + Q_OBJECT + +public: + explicit FilterForm(QWidget* parent, std::unique_ptr& sqlOpr, std::unique_ptr& comSqlOpr, + std::unique_ptr& repaySqlOpr); + ~FilterForm(); + int exec() override; + +public: + static bool Filter(const AccountRecordList& list, AccountRecordList& result); + static std::vector>> 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_; + std::unique_ptr& sqlOpr_; + std::unique_ptr& comSqlOpr_; + std::unique_ptr& repaySqlOpr_; + Ui::FilterForm* ui; +}; + +#endif // FILTERFORM_H diff --git a/filterform.ui b/filterform.ui new file mode 100644 index 0000000..98752fa --- /dev/null +++ b/filterform.ui @@ -0,0 +1,248 @@ + + + FilterForm + + + + 0 + 0 + 1472 + 710 + + + + Dialog + + + + + + 1.清单 + + + + + + + 0 + 0 + + + + + + + + + + 上一页 + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 80 + 16777215 + + + + + + + + Qt::Orientation::Vertical + + + + + + + + 0 + 0 + + + + + 80 + 16777215 + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + 下一页 + + + + + + + + + + + + + 0 + 0 + + + + + 200 + 16777215 + + + + 2.统计 + + + + + + + + 总计现金支出: + + + + + + + + + + + + + + 总计现金收入: + + + + + + + + + + + + + + 总计信用支出: + + + + + + + + + + + + + + 总计信用收入: + + + + + + + + + + + + + + 总计信用借款: + + + + + + + + + + + + + + 总计信用还款: + + + + + + + + + + + + 现金分类占比(支出/收入): + + + + + + + + + + 信用分类占比(支出/收入): + + + + + + + + + + + + + + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..f1c5ee4 --- /dev/null +++ b/main.cpp @@ -0,0 +1,31 @@ +#include "mainwidget.h" +#include +#include +#include + +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(); +} diff --git a/mainwidget.cpp b/mainwidget.cpp new file mode 100644 index 0000000..fd187a3 --- /dev/null +++ b/mainwidget.cpp @@ -0,0 +1,344 @@ +#include "mainwidget.h" +#include "./ui_mainwidget.h" +#include "filterform.h" +#include +#include +#include +#include +#include +#include + +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(); + if (!sqlOpr_->OpenDb(dbPath)) { + QMessageBox::warning(this, "错误", QString::fromStdString(sqlOpr_->GetLastErr())); + return; + } + comSqlOpr_ = std::make_unique(sqlOpr_->GetDb()); + repaySqlOpr_ = std::make_unique(sqlOpr_->GetDb()); + statistic_ = std::make_shared(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(Util::GetCurCash() * 100); + auto curSave = static_cast(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(ui->edLowCash->text().toDouble() * 100); + d->highMoney = static_cast(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(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(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(); +} \ No newline at end of file diff --git a/mainwidget.h b/mainwidget.h new file mode 100644 index 0000000..6918c91 --- /dev/null +++ b/mainwidget.h @@ -0,0 +1,50 @@ +#ifndef MAINWIDGET_H +#define MAINWIDGET_H + +#include "SqlOpr.h" +#include "util.h" +#include "statistic.h" +#include +#include + +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_; + std::unique_ptr sqlOpr_; + std::unique_ptr comSqlOpr_; + std::unique_ptr repaySqlOpr_; + Ui::MainWidget* ui; +}; +#endif // MAINWIDGET_H diff --git a/mainwidget.ui b/mainwidget.ui new file mode 100644 index 0000000..b385177 --- /dev/null +++ b/mainwidget.ui @@ -0,0 +1,341 @@ + + + MainWidget + + + + 0 + 0 + 687 + 237 + + + + MainWidget + + + + + + NULL + + + + + + + Qt::Orientation::Horizontal + + + + + + + + + 剩余总现金(元): + + + + + + + NULL + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + 信用收支(元)(支出/收入): + + + + + + + NULL + + + + + + + + + Qt::Orientation::Horizontal + + + + + + + + + 款项类型: + + + + + + + + 0 + 0 + + + + + + + + + + + + + + 设置为现在 + + + + + + + 分类增 + + + + + + + 分类删 + + + + + + + 设定 + + + + + + + + + + + 记录事项: + + + + + + + + + + 金额(元): + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + + + + + 记录 + + + + + + + + + Qt::Orientation::Horizontal + + + + + + + + + 款项筛选 + + + + + + + 最近天数 + + + + + + + 关键字筛 + + + + + + + 分类筛选 + + + + + + + 金额小: + + + + + + + + + + 金额大: + + + + + + + + + + + + + + 金额大于 + + + + + + + 金额小于 + + + + + + + 金额之间 + + + + + + + 金额不限 + + + + + + + 关键字: + + + + + + + + + + 天范围: + + + + + + + + + + + + + + 清除 + + + + + + + 添加附件图 + + + + + + + + + + 筛选查找 + + + + + + + + + + diff --git a/ofen b/ofen new file mode 160000 index 0000000..55f0045 --- /dev/null +++ b/ofen @@ -0,0 +1 @@ +Subproject commit 55f0045958a858a517ea23e2bfc767f6f754e39c diff --git a/recordedit.cpp b/recordedit.cpp new file mode 100644 index 0000000..06d9ed1 --- /dev/null +++ b/recordedit.cpp @@ -0,0 +1,133 @@ +#include "recordedit.h" + +#include "repayment.h" +#include "ui_recordedit.h" +#include "util.h" +#include +#include + +RecordEdit::RecordEdit(QWidget* parent, std::unique_ptr& comSqlOpr, std::unique_ptr& 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(); +} \ No newline at end of file diff --git a/recordedit.h b/recordedit.h new file mode 100644 index 0000000..58dd17b --- /dev/null +++ b/recordedit.h @@ -0,0 +1,39 @@ +#ifndef RECORDEDIT_H +#define RECORDEDIT_H + +#include "SqlOpr.h" +#include + +namespace Ui { +class RecordEdit; +} + +class RecordEdit : public QDialog +{ + Q_OBJECT + +public: + explicit RecordEdit(QWidget* parent, std::unique_ptr& comSqlOpr, std::unique_ptr& 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_; + std::unique_ptr& comSqlOpr_; + Ui::RecordEdit* ui; +}; + +#endif // RECORDEDIT_H diff --git a/recordedit.ui b/recordedit.ui new file mode 100644 index 0000000..e4dc9b2 --- /dev/null +++ b/recordedit.ui @@ -0,0 +1,206 @@ + + + RecordEdit + + + + 0 + 0 + 305 + 386 + + + + Dialog + + + + + + + + 序号: + + + + + + + + + + + + + + 类型: + + + + + + + + 0 + 0 + + + + + + + + 分类: + + + + + + + + 0 + 0 + + + + + + + + + + + + 金额: + + + + + + + + + + + + + + 日期: + + + + + + + + + + + + + + + 内容: + + + + + + + + + + 备注: + + + + + + + + + 已还清 + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + 还款 + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + 当前时间 + + + + + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + 修改 + + + + + + + 取消 + + + + + + + + + + diff --git a/repayment.cpp b/repayment.cpp new file mode 100644 index 0000000..da2768e --- /dev/null +++ b/repayment.cpp @@ -0,0 +1,225 @@ +#include "repayment.h" +#include "ui_repayment.h" +#include "util.h" +#include +#include +#include +#include + +Repayment::Repayment(QWidget* parent, std::unique_ptr& 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 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)) + "元,未还清。"); + } +} diff --git a/repayment.h b/repayment.h new file mode 100644 index 0000000..619eb33 --- /dev/null +++ b/repayment.h @@ -0,0 +1,41 @@ +#ifndef REPAYMENT_H +#define REPAYMENT_H + +#include "SqlOpr.h" +#include +#include + +namespace Ui { +class Repayment; +} + +class Repayment : public QDialog +{ + Q_OBJECT + +public: + explicit Repayment(QWidget* parent, std::unique_ptr& 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_; +}; + +#endif // REPAYMENT_H diff --git a/repayment.ui b/repayment.ui new file mode 100644 index 0000000..21c44f6 --- /dev/null +++ b/repayment.ui @@ -0,0 +1,86 @@ + + + Repayment + + + + 0 + 0 + 476 + 300 + + + + Dialog + + + + + + + 0 + 0 + + + + 统计 + + + + + + NULL + + + + + + + + + 总白条: + + + + + + + + + + 结果: + + + + + + + + + + 保存 + + + + + + + + + + + + 详情 + + + + + + + + + + + + + diff --git a/res.qrc b/res.qrc new file mode 100644 index 0000000..225705f --- /dev/null +++ b/res.qrc @@ -0,0 +1,5 @@ + + + resource/ico.ico + + diff --git a/resource/SimpleAccount.rc b/resource/SimpleAccount.rc new file mode 100644 index 0000000..1d4bc7c --- /dev/null +++ b/resource/SimpleAccount.rc @@ -0,0 +1 @@ +IDI_APP_ICON ICON "ico.ico" \ No newline at end of file diff --git a/resource/ico.ico b/resource/ico.ico new file mode 100644 index 0000000..c28f7e6 Binary files /dev/null and b/resource/ico.ico differ diff --git a/resource/qss.qrc b/resource/qss.qrc new file mode 100644 index 0000000..940aeb7 --- /dev/null +++ b/resource/qss.qrc @@ -0,0 +1,28 @@ + + + qss/flatgray.css + qss/flatgray/add_bottom.png + qss/flatgray/add_left.png + qss/flatgray/add_right.png + qss/flatgray/add_top.png + qss/flatgray/arrow_bottom.png + qss/flatgray/arrow_left.png + qss/flatgray/arrow_right.png + qss/flatgray/arrow_top.png + qss/flatgray/branch_close.png + qss/flatgray/branch_open.png + qss/flatgray/calendar_nextmonth.png + qss/flatgray/calendar_prevmonth.png + qss/flatgray/checkbox_checked.png + qss/flatgray/checkbox_checked_disable.png + qss/flatgray/checkbox_parcial.png + qss/flatgray/checkbox_parcial_disable.png + qss/flatgray/checkbox_unchecked.png + qss/flatgray/checkbox_unchecked_disable.png + qss/flatgray/menu_checked.png + qss/flatgray/radiobutton_checked.png + qss/flatgray/radiobutton_checked_disable.png + qss/flatgray/radiobutton_unchecked.png + qss/flatgray/radiobutton_unchecked_disable.png + + diff --git a/resource/qss/flatgray.css b/resource/qss/flatgray.css new file mode 100644 index 0000000..fe82b5b --- /dev/null +++ b/resource/qss/flatgray.css @@ -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*/ \ No newline at end of file diff --git a/resource/qss/flatgray/add_bottom.png b/resource/qss/flatgray/add_bottom.png new file mode 100644 index 0000000..868e687 Binary files /dev/null and b/resource/qss/flatgray/add_bottom.png differ diff --git a/resource/qss/flatgray/add_left.png b/resource/qss/flatgray/add_left.png new file mode 100644 index 0000000..d9d5127 Binary files /dev/null and b/resource/qss/flatgray/add_left.png differ diff --git a/resource/qss/flatgray/add_right.png b/resource/qss/flatgray/add_right.png new file mode 100644 index 0000000..be8dd1a Binary files /dev/null and b/resource/qss/flatgray/add_right.png differ diff --git a/resource/qss/flatgray/add_top.png b/resource/qss/flatgray/add_top.png new file mode 100644 index 0000000..ef55ce1 Binary files /dev/null and b/resource/qss/flatgray/add_top.png differ diff --git a/resource/qss/flatgray/arrow_bottom.png b/resource/qss/flatgray/arrow_bottom.png new file mode 100644 index 0000000..37307b7 Binary files /dev/null and b/resource/qss/flatgray/arrow_bottom.png differ diff --git a/resource/qss/flatgray/arrow_left.png b/resource/qss/flatgray/arrow_left.png new file mode 100644 index 0000000..aa4312b Binary files /dev/null and b/resource/qss/flatgray/arrow_left.png differ diff --git a/resource/qss/flatgray/arrow_right.png b/resource/qss/flatgray/arrow_right.png new file mode 100644 index 0000000..4db5efa Binary files /dev/null and b/resource/qss/flatgray/arrow_right.png differ diff --git a/resource/qss/flatgray/arrow_top.png b/resource/qss/flatgray/arrow_top.png new file mode 100644 index 0000000..0da1cad Binary files /dev/null and b/resource/qss/flatgray/arrow_top.png differ diff --git a/resource/qss/flatgray/branch_close.png b/resource/qss/flatgray/branch_close.png new file mode 100644 index 0000000..f5b6d34 Binary files /dev/null and b/resource/qss/flatgray/branch_close.png differ diff --git a/resource/qss/flatgray/branch_open.png b/resource/qss/flatgray/branch_open.png new file mode 100644 index 0000000..6392c13 Binary files /dev/null and b/resource/qss/flatgray/branch_open.png differ diff --git a/resource/qss/flatgray/calendar_nextmonth.png b/resource/qss/flatgray/calendar_nextmonth.png new file mode 100644 index 0000000..6cb40d7 Binary files /dev/null and b/resource/qss/flatgray/calendar_nextmonth.png differ diff --git a/resource/qss/flatgray/calendar_prevmonth.png b/resource/qss/flatgray/calendar_prevmonth.png new file mode 100644 index 0000000..8a17d0f Binary files /dev/null and b/resource/qss/flatgray/calendar_prevmonth.png differ diff --git a/resource/qss/flatgray/checkbox_checked.png b/resource/qss/flatgray/checkbox_checked.png new file mode 100644 index 0000000..8808382 Binary files /dev/null and b/resource/qss/flatgray/checkbox_checked.png differ diff --git a/resource/qss/flatgray/checkbox_checked_disable.png b/resource/qss/flatgray/checkbox_checked_disable.png new file mode 100644 index 0000000..9aa0a10 Binary files /dev/null and b/resource/qss/flatgray/checkbox_checked_disable.png differ diff --git a/resource/qss/flatgray/checkbox_parcial.png b/resource/qss/flatgray/checkbox_parcial.png new file mode 100644 index 0000000..97376f3 Binary files /dev/null and b/resource/qss/flatgray/checkbox_parcial.png differ diff --git a/resource/qss/flatgray/checkbox_parcial_disable.png b/resource/qss/flatgray/checkbox_parcial_disable.png new file mode 100644 index 0000000..b9b286f Binary files /dev/null and b/resource/qss/flatgray/checkbox_parcial_disable.png differ diff --git a/resource/qss/flatgray/checkbox_unchecked.png b/resource/qss/flatgray/checkbox_unchecked.png new file mode 100644 index 0000000..50c25e2 Binary files /dev/null and b/resource/qss/flatgray/checkbox_unchecked.png differ diff --git a/resource/qss/flatgray/checkbox_unchecked_disable.png b/resource/qss/flatgray/checkbox_unchecked_disable.png new file mode 100644 index 0000000..7c4c7db Binary files /dev/null and b/resource/qss/flatgray/checkbox_unchecked_disable.png differ diff --git a/resource/qss/flatgray/menu_checked.png b/resource/qss/flatgray/menu_checked.png new file mode 100644 index 0000000..6a1c729 Binary files /dev/null and b/resource/qss/flatgray/menu_checked.png differ diff --git a/resource/qss/flatgray/radiobutton_checked.png b/resource/qss/flatgray/radiobutton_checked.png new file mode 100644 index 0000000..513a41e Binary files /dev/null and b/resource/qss/flatgray/radiobutton_checked.png differ diff --git a/resource/qss/flatgray/radiobutton_checked_disable.png b/resource/qss/flatgray/radiobutton_checked_disable.png new file mode 100644 index 0000000..8d16af5 Binary files /dev/null and b/resource/qss/flatgray/radiobutton_checked_disable.png differ diff --git a/resource/qss/flatgray/radiobutton_unchecked.png b/resource/qss/flatgray/radiobutton_unchecked.png new file mode 100644 index 0000000..8ef34fb Binary files /dev/null and b/resource/qss/flatgray/radiobutton_unchecked.png differ diff --git a/resource/qss/flatgray/radiobutton_unchecked_disable.png b/resource/qss/flatgray/radiobutton_unchecked_disable.png new file mode 100644 index 0000000..f70c364 Binary files /dev/null and b/resource/qss/flatgray/radiobutton_unchecked_disable.png differ diff --git a/statistic.cpp b/statistic.cpp new file mode 100644 index 0000000..ef0a6f6 --- /dev/null +++ b/statistic.cpp @@ -0,0 +1,42 @@ +#include "statistic.h" +#include "util.h" + +Statistic::Statistic(std::unique_ptr& 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; + } + } + } + } +} diff --git a/statistic.h b/statistic.h new file mode 100644 index 0000000..cd21e1d --- /dev/null +++ b/statistic.h @@ -0,0 +1,19 @@ +#ifndef STATISTIC_H +#define STATISTIC_H + +#include "SqlOpr.h" + +class Statistic +{ +public: + Statistic(std::unique_ptr& repaySqlOpr); + ~Statistic(); + +public: + void Calculate(const AccountRecordList& list); + +private: + std::unique_ptr& repaySqlOpr_; +}; + +#endif // STATISTIC_H \ No newline at end of file diff --git a/util.cpp b/util.cpp new file mode 100644 index 0000000..6c13dc4 --- /dev/null +++ b/util.cpp @@ -0,0 +1,145 @@ +#include "util.h" +#include +#include +#include + +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 Util::GetConfigPath() +{ + return gConfigPath; +} + +std::pair Util::GetMainSize() +{ + return std::make_pair(gConfig["MainWidth"].get(), gConfig["MainHeight"].get()); +} + +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(); +} + +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 Util::GetCurSave() +{ + return gConfig["CurSave"].get(); +} + +int Util::CashInt(double cash) +{ + return static_cast(round(cash * 100)); +} + +double Util::CashDouble(int cash) +{ + return static_cast(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; +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..529ffe5 --- /dev/null +++ b/util.h @@ -0,0 +1,79 @@ +#ifndef UTIL_H +#define UTIL_H + +#include +#include +#include +#include + +class Util +{ +public: + Util(); + +public: + static bool Init(); + +public: + static std::string GetWorkDir(); + static std::string GetMediaDir(); + static std::string GetConfigPath(); + static std::pair 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