diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa3808c --- /dev/null +++ b/.gitignore @@ -0,0 +1,82 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* +*.qbs.user* +CMakeLists.txt.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + +# Directories with generated files +.moc/ +.obj/ +.pch/ +.rcc/ +.uic/ +/build*/ diff --git a/Modules/Logger.cpp b/Modules/Logger.cpp new file mode 100644 index 0000000..426c873 --- /dev/null +++ b/Modules/Logger.cpp @@ -0,0 +1,72 @@ +#include "Logger.h" +#include "global.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // For Qt logging + +structIs is; + +std::string colorOut_256(std::string_view s, int ForeColor = 7) +{ + std::ostringstream oss; + oss << "\033[38;5;" << ForeColor << "m" << s << "\033[m"; + return oss.str(); +} + +std::string getSystemTime() { + using namespace std::chrono; + const auto now = system_clock::now(); + const auto now_time_t = system_clock::to_time_t(now); + const auto ms = duration_cast(now.time_since_epoch()).count() % 1000; + + std::tm ltm{}; + localtime_s(<m, &now_time_t); + std::ostringstream oss; + oss << std::put_time(<m, "%Y-%m-%d %H:%M:%S") + << '.' << std::setw(3) << std::setfill('0') << ms; + return oss.str(); +} + +// Logger mutex to ensure whole log lines are emitted atomically +static std::mutex loggerMutex; + +void postLog(const std::string& message, int level) { + const auto timeNow = getSystemTime(); + + constexpr std::array levelNames = {"DEBUG", "INFO", "WARNING", "ERROR", "FATAL"}; + constexpr std::array levelColors = {34, 27, 220, 196, 124}; + + const int idx = (level >= 0 && level < static_cast(levelNames.size())) ? level : 1; // default to INFO + + // Lock to make reading isDebug and all output atomic across threads + std::scoped_lock lock(loggerMutex); + + if (is.debug == false) { + if (idx == 0) return; // skip DEBUG when debug is off + qInfo() << "[" << QString::fromStdString(timeNow) << " - " << QString::fromStdString(levelNames[idx]) << "] " << QString::fromStdString(message); + return; + } + + // isDebug == 1 -> colored output + const std::string levelDisplay = colorOut_256(levelNames[idx], levelColors[idx]); + QString levelDisplayQt = QString::fromStdString(levelDisplay); + + if (idx == 0) { // DEBUG level + qDebug("[%s - %s] %s", QString::fromStdString(timeNow).toUtf8().data(), levelDisplayQt.toUtf8().data(), QString::fromStdString(message).toUtf8().data()); + } else if (idx == 1) { // INFO level + qInfo("[%s - %s] %s", QString::fromStdString(timeNow).toUtf8().data(), levelDisplayQt.toUtf8().data(), QString::fromStdString(message).toUtf8().data()); + } else if (idx == 2) { // WARNING level + qWarning("[%s - %s] %s", QString::fromStdString(timeNow).toUtf8().data(), levelDisplayQt.toUtf8().data(), QString::fromStdString(message).toUtf8().data()); + } else if (idx == 3) { // ERROR level + qCritical("[%s - %s] %s", QString::fromStdString(timeNow).toUtf8().data(), levelDisplayQt.toUtf8().data(), QString::fromStdString(message).toUtf8().data()); + } else if (idx == 4) { // FATAL level + qFatal("[%s - %s] %s", QString::fromStdString(timeNow).toUtf8().data(), levelDisplayQt.toUtf8().data(), QString::fromStdString(message).toUtf8().data()); + } +} diff --git a/Modules/Logger.h b/Modules/Logger.h new file mode 100644 index 0000000..07986bc --- /dev/null +++ b/Modules/Logger.h @@ -0,0 +1,9 @@ +#ifndef LOGGER_H +#define LOGGER_H + +#include + +std::string getSystemTime(); +void postLog(const std::string& message, int level); + +#endif diff --git a/Modules/global.h b/Modules/global.h new file mode 100644 index 0000000..c50c934 --- /dev/null +++ b/Modules/global.h @@ -0,0 +1,83 @@ +#ifndef GLOBAL_H +#define GLOBAL_H + +#include +#include +#include +#include +#include +#include +#include +#include + +// *** dialogLogin *** +struct structCheckBox { + QCheckBox *rememberMe = nullptr; +}; +extern structCheckBox checkBox; + +struct structComboBox{ + QComboBox *findBy = nullptr; +}; +extern structComboBox comboBox; + +struct structTextEdit { + QTextEdit *username = nullptr; + QTextEdit *passwd = nullptr; + QTextEdit *search = nullptr; +}; +extern structTextEdit textEdit; + +struct structPushButton { + QPushButton *ok = nullptr; + QPushButton *cancel = nullptr; + QPushButton *seacrh = nullptr; + QPushButton *clear = nullptr; + QPushButton *submit = nullptr; + QPushButton *remove = nullptr; +}; +extern structPushButton pushButton; + +struct structLabel { + QLabel *connStatus = nullptr; + QLabel *salaryShould = nullptr; + QLabel *salaryDiscount = nullptr; + QLabel *salaryActual = nullptr; +}; +extern structLabel label; + +struct structTableWidget { + QTableWidget *teacherInfo = nullptr; + QTableWidget *teacherSalary = nullptr; +}; +extern structTableWidget tableWidget; + +namespace Ui { +class DialogLogin; +} +// ******************* + + +// *** main *** +struct structIs{ + bool debug = true; + bool loggedIn = false; + bool connected = false; +}; +extern structIs is; + +struct structSession{ + std::string token; + std::string userName; + std::string userID; +}; +extern structSession session; + +struct structUserInfo{ + std::string userName; + std::string passwd; +}; +extern structUserInfo userInfo; +// ************ + +#endif // GLOBAL_H diff --git a/Modules/inicpp.hpp b/Modules/inicpp.hpp new file mode 100644 index 0000000..25598d7 --- /dev/null +++ b/Modules/inicpp.hpp @@ -0,0 +1,960 @@ +/* + * MIT License + * + * Copyright (c) 2023 dujingning + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef __JN_INICPP_H__ +#define __JN_INICPP_H__ + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#ifdef _ENBABLE_INICPP_STD_WSTRING_ // Not all of C++ 11 support +// for std::string <==> std::wstring convert +#include +#include +#endif + +#ifdef INICPP_DEBUG + +#include +#include +#include + +class TimeFormatter +{ +public: + static std::string format(const std::string &format = "%Y-%m-%d %H:%M:%S") + { + std::time_t t = std::time(nullptr); + std::tm tm = *std::localtime(&t); + std::array buffer; + std::strftime(buffer.data(), buffer.size(), format.c_str(), &tm); + return buffer.data(); + } +}; + +#define CODE_INFO std::string(" \t``````|") + std::string(__FILE__) + ":" + std::to_string(__LINE__) + " fun:" + std::string(__FUNCTION__) +#define INI_DEBUG(x) std::cout << "INICPP " << TimeFormatter::format() << " : " << x << CODE_INFO << std::endl + +#else // #ifdef INICPP_DEBUG +#define INI_DEBUG(x) +#endif // #ifdef INICPP_DEBUG + +namespace inicpp +{ + + typedef struct ValueNode + { + std::string Value = ""; + int lineNumber = -1; // text line start with 1 + } ValueNode; + + class parentHelper + { + public: + virtual parentHelper *parent() + { + INI_DEBUG("called parentHelper virtual impl: need to impl parent"); + return nullptr; + } + virtual void setParent(parentHelper *parent) + { + INI_DEBUG("called parentHelper virtual impl: need to impl setParent"); + } + virtual bool set(const std::string &Section, const std::string &Key, const std::string &Value, const std::string &comment = "") + { + INI_DEBUG("called parentHelper virtual impl: need to impl set"); + return true; + } + }; + + class ValueProxy + { + public: + ValueProxy(std::string &value) : _value(value) {} + ~ValueProxy() {} + + template + ValueProxy(const T &value) : _value(to_string(value)) {} + + template + static std::string to_string(const T &value) + { + std::ostringstream oss; + oss << value; + return oss.str(); + } + + template + T get() const + { + static_assert(!std::is_pointer::value, "Pointer types are not supported for conversion."); + std::istringstream iss(_value); + T result; + if (!(iss >> result)) + { + throw std::runtime_error("Type mismatch or invalid conversion. with(section-key-value): " +_sectionName +"-"+ _keyName +"-"+ _value); // error notify + } + return result; + } + + operator char() const { return this->get(); } + operator short() const { return this->get(); } + operator int() const { return this->get(); } + operator long() const { return this->get(); } + operator long long() const { return this->get(); } + operator float() const { return this->get(); } + operator double() const { return this->get(); } + + operator unsigned char() const { return this->get(); } + operator unsigned short() const { return this->get(); } + operator unsigned int() const { return this->get(); } + operator unsigned long() const { return this->get(); } + operator unsigned long long() const { return this->get(); } + + // false:'0' or 'false', true : others + operator bool() const + { + if (_value == "0" || _value == "false" || _value == "no") + { + return false; + } + return true; + } + + operator std::string() const + { + return _value; + } + + friend std::ostream &operator<<(std::ostream &os, const ValueProxy &proxy) + { + os << proxy._value; + return os; + } + + // assignment + template + ValueProxy &operator=(const T &other) + { + std::string value = this->to_string(other); + + if (_value != value) + { + set(value); + } + + _value = value; + return *this; + } + + ValueProxy &operator=(const std::string &other) + { + if (_value != other) + { + INI_DEBUG("Value Proxy Wanna Set Value: " << other); + set(other); + } + + _value = other; + return *this; + } + + // specify std::string + const std::string &String() noexcept + { + return _value; + } + + inline void setWriteCB(parentHelper *sectionObj, const std::string §ionName, const std::string &keyName) + { + _section = sectionObj; + _sectionName = sectionName; + _keyName = keyName; + } + + private: + void set(const std::string &value) + { + if (value.empty() || _keyName.empty()) + { + return; + } + if (_section && _section->parent() && _section->parent()->parent()) + { + _section->parent()->parent()->set(_sectionName, _keyName, value); + } + } + + private: + std::string &_value; + + std::string _sectionName, _keyName; + parentHelper *_section = nullptr; + }; +} // namespace inicpp + +namespace inicpp +{ + + class section : parentHelper + { + public: + section() : _sectionName() + { + } + + explicit section(const std::string §ionName) : _sectionName(sectionName) + { + } + + const std::string &name() + { + return _sectionName; + } + + const std::string getValue(const std::string &Key) + { + if (!_sectionMap.count(Key)) + { + return ""; + } + return _sectionMap[Key].Value; + } + + void setName(const std::string &name, const int &lineNumber) + { + _sectionName = name; + _lineNumber = lineNumber; + } + + void setValue(const std::string &Key, const std::string &Value, const int line) + { + _sectionMap[Key].Value = Value; + _sectionMap[Key].lineNumber = line; + } + + void append(section &sec) + { + _sectionMap.insert(sec._sectionMap.begin(), sec._sectionMap.end()); + } + + bool isKeyExist(const std::string &Key) + { + return !_sectionMap.count(Key) ? false : true; + } + + int getEndSection() + { + int line = -1; + + if (_sectionMap.empty() && _sectionName != "") + { + return _lineNumber; + } + + for (const auto &data : _sectionMap) + { + if (data.second.lineNumber > line) + { + line = data.second.lineNumber; + } + } + return line; + } + + int getLine(const std::string &Key) + { + if (!_sectionMap.count(Key)) + { + return -1; + } + return _sectionMap[Key].lineNumber; + } + + void clear() + { + _lineNumber = -1; + _sectionName.clear(); + _sectionMap.clear(); + } + + bool isEmpty() const + { + return _sectionMap.empty(); + } + + int toInt(const std::string &Key) noexcept + { + if (!_sectionMap.count(Key)) + { + return 0; + } + + int result = 0; + + try + { + result = std::stoi(_sectionMap[Key].Value); + } + catch (const std::invalid_argument &e) + { + INI_DEBUG("Invalid argument: " << e.what() << ",input:\'" << _sectionMap[Key].Value << "\'"); + } + catch (const std::out_of_range &e) + { + INI_DEBUG("Out of range: " << e.what() << ",input:\'" << _sectionMap[Key].Value << "\'"); + } + + return result; + } + + std::string toString(const std::string &Key) noexcept + { + if (!_sectionMap.count(Key)) + { + return ""; + } + return _sectionMap[Key].Value; + } + +#ifdef _ENBABLE_INICPP_STD_WSTRING_ + std::wstring toWString(const std::string &Key) + { + std::wstring_convert> converter; + return converter.from_bytes(toString(Key)); + } +#endif + + double toDouble(const std::string &Key) noexcept + { + if (!_sectionMap.count(Key)) + { + return 0.0; + } + + double result = 0.0; + + try + { + result = std::stod(_sectionMap[Key].Value); + } + catch (const std::invalid_argument &e) + { + INI_DEBUG("Invalid argument: " << e.what() << ",input:\'" << _sectionMap[Key].Value << "\'"); + } + catch (const std::out_of_range &e) + { + INI_DEBUG("Out of range: " << e.what() << ",input:\'" << _sectionMap[Key].Value << "\'"); + } + + return result; + } + + std::map getSectionMap() + { + std::map sectionKVMap; + + for (auto &iter : _sectionMap) + { + sectionKVMap[iter.first] = iter.second.Value; + } + + return sectionKVMap; + } + + // Automatically converts to any type; throws std::runtime_error if not found or conversion fails + ValueProxy operator[](const std::string &Key) + { + ValueProxy vp(_sectionMap[Key].Value); + + vp.setWriteCB(this, _sectionName, Key); + + return vp; + } + + inline parentHelper *parent() override { return _parent; }; + inline void setParent(parentHelper *parent) override { _parent = parent; }; + + private: + std::string _sectionName; + std::map _sectionMap; + int _lineNumber = -1; // text line start with 1 + + parentHelper *_parent = nullptr; + }; + + class ini : parentHelper + { + public: + void addSection(section &sec) + { + if (_iniInfoMap.count(sec.name())) // if exist,need to merge + { + _iniInfoMap[sec.name()].append(sec); + return; + } + _iniInfoMap.emplace(sec.name(), sec); + return; + } + + void removeSection(const std::string §ionName) + { + if (!_iniInfoMap.count(sectionName)) + { + return; + } + _iniInfoMap.erase(sectionName); + return; + } + + bool isSectionExists(const std::string §ionName) + { + return !_iniInfoMap.count(sectionName) ? false : true; + } + + // may contains default of Unnamed section with "" + std::list getSectionsList() + { + std::list sectionList; + for (const auto &data : _iniInfoMap) + { + if (data.first == "" && data.second.isEmpty()) // default section: no section name,if empty,not count it. + { + continue; + } + sectionList.emplace_back(data.first); + } + return sectionList; + } + + std::map getSectionMap(const std::string §ionName) + { + std::map kvMap; + if (_iniInfoMap.count(sectionName) == 0) + { + return kvMap; + } + return _iniInfoMap[sectionName].getSectionMap(); + } + + const section &operator[](const std::string §ionName) + { + _iniInfoMap[sectionName].setParent(this); + + if (_iniInfoMap[sectionName].name().empty()) + { + _iniInfoMap[sectionName].setName(sectionName, -1); + } + + return _iniInfoMap[sectionName]; + } + + inline std::size_t getSectionSize() + { + return _iniInfoMap.size(); + } + + std::string getValue(const std::string §ionName, const std::string &Key) + { + if (!_iniInfoMap.count(sectionName)) + { + return ""; + } + return _iniInfoMap[sectionName][Key]; + } + + // for none section + int getLine(const std::string &Key) + { + if (!_iniInfoMap.count("")) + { + return -1; + } + return _iniInfoMap[""].getLine(Key); + } + + // for section-key + int getLine(const std::string §ionName, const std::string &Key) + { + if (!_iniInfoMap.count(sectionName)) + { + return -1; + } + return _iniInfoMap[sectionName].getLine(Key); + } + + inline void clear() { _iniInfoMap.clear(); } + inline bool empty() { return _iniInfoMap.empty(); } + + parentHelper *parent() override { return _parent; } + void setParent(parentHelper *parent) override { _parent = parent; } + + protected: + std::map _iniInfoMap; + + private: + parentHelper *_parent = nullptr; + }; + + class IniManager : parentHelper + { + public: +#ifdef _ENBABLE_INICPP_STD_WSTRING_ + explicit IniManager(const std::wstring &configFileName = L"") + { + std::wstring_convert> converter; + std::string stringValue = converter.to_bytes(configFileName); + _configFileName = stringValue; + + _iniData.setParent(this); + parse(); + } +#else + explicit IniManager(const std::string &configFileName = "") : _configFileName(configFileName) + { + _iniData.setParent(this); + + parse(); + } +#endif + ~IniManager() + { + _iniFile.close(); + } + + section operator[](const std::string §ionName) + { + return _iniData[sectionName]; + } + + void parse() + { + if (_configFileName.empty()) + { + return; + } + + if (!_iniFile.is_open()) + { + _iniFile.clear(); + _iniFile.open(_configFileName, std::ifstream::in | std::ifstream::out | std::fstream::app); + } + + if (!_iniFile.is_open()) + { + INI_DEBUG("Failed to open(WR),try to open with readonly(R)."); + _iniFile.clear(); + _iniFile.open(_configFileName, std::ifstream::in); + } + + if (!_iniFile.is_open()) + { + INI_DEBUG("Failed to open the input INI file for parsing! file:" << _configFileName); + return; + } + + _iniData.clear(); + + _iniFile.seekg(0, _iniFile.beg); + std::string data, sectionName; + int sectionLine = -1; + + section sectionRecord; + + _SumOfLines = 1; + do + { + std::getline(_iniFile, data); + + if (!filterData(data)) + { + ++_SumOfLines; + continue; + } + + if (data.find('[') == 0) // section + { + if (!sectionRecord.isEmpty() || sectionRecord.name() != "") + { + _iniData.addSection(sectionRecord); + } + + size_t first = data.find('['); + size_t last = data.find(']'); + + if (last == std::string::npos) + { + ++_SumOfLines; + continue; + } + + sectionName = data.substr(first + 1, last - first - 1); + sectionLine = _SumOfLines; + + sectionRecord.clear(); + sectionRecord.setName(sectionName, sectionLine); + } + + size_t pos = data.find('='); + if (pos != std::string::npos) + { // k=v + std::string key = data.substr(0, pos); + std::string value = data.substr(pos + 1); + + trimEdges(key); + trimEdges(value); + + sectionRecord.setValue(key, value, _SumOfLines); + } + + ++_SumOfLines; + + } while (!_iniFile.eof()); + + if (!sectionRecord.isEmpty()) + { + sectionRecord.setName(sectionName, -1); + _iniData.addSection(sectionRecord); + } + + if (_iniFile.is_open()) + { + _iniFile.close(); + } + } + + bool set(const std::string &Section, const std::string &Key, const std::string &Value, const std::string &comment = "") override + { + parse(); + + std::string key = Key, value = Value; + + trimEdges(key); + trimEdges(key); + + if (key == "" || value == "") + { + INI_DEBUG("Invalid parameter input: key[" << key << "],value[" << value << "]"); + return false; + } + + std::string keyValueData = key + "=" + value + "\n"; + if (comment.length() > 0) + { + keyValueData = comment + "\n" + keyValueData; + if (comment[0] != ';') + { + keyValueData = ";" + keyValueData; + } + } + + const std::string &tempFile = ".temp.ini"; + std::fstream input(_configFileName, std::ifstream::in); + std::ofstream output(tempFile); + + if (!input.is_open()) + { + INI_DEBUG("Failed to open the input INI file for modification! File name:" << _configFileName); + return false; + } + + if (!output.is_open()) + { + INI_DEBUG("Failed to open the output INI file for modification!"); + return false; + } + + int line_number_mark = -1; + bool isInputDataWited = false; + + do + { + // exist key at one section replace it, or need to create it + if (_iniData.isSectionExists(Section)) + { + line_number_mark = (*this)[Section].getLine(key); + + if (line_number_mark == -1) + { // section exist, key not exist + line_number_mark = (*this)[Section].getEndSection(); + + std::string lineData; + int input_line_number = 0; + while (std::getline(input, lineData)) + { + ++input_line_number; + + if (input_line_number == (line_number_mark + 1)) + { // new line,append to next line + isInputDataWited = true; + output << keyValueData; + } + + output << lineData << "\n"; + } + + if (input.eof() && !isInputDataWited) + { + isInputDataWited = true; + output << keyValueData; + } + + break; + } + } + + if (line_number_mark <= 0) // not found key at config file + { + input.seekg(0, input.beg); + + bool isHoldSection = false; + std::string newLine = "\n\n"; + if (Section != "" && Section.find("[") == std::string::npos && Section.find("]") == std::string::npos && Section.find("=") == std::string::npos) + { + if (_iniData.empty() || _iniData.getSectionSize() <= 0) + { + newLine.clear(); + } + + isHoldSection = true; + } + + // 1.section is exist or empty section + if (_iniData.isSectionExists(Section) || Section == "") + { + // write key/value to head + if (isHoldSection) + { + output << newLine << "[" << Section << "]" << "\n"; + } + output << keyValueData; + // write others + std::string lineData; + while (std::getline(input, lineData)) + { + output << lineData << "\n"; + } + } + // 2.section is not exist + else + { + // write others + std::string lineData; + while (std::getline(input, lineData)) + { + output << lineData << "\n"; + } + // write key/value to end + if (isHoldSection) + { + output << newLine << "[" << Section << "]" << "\n"; + } + output << keyValueData; + } + + break; + } + else + { // found, replace it + + std::string lineData; + int input_line_number = 0; + + while (std::getline(input, lineData)) + { + ++input_line_number; + + // delete old comment if new comment is set + if (input_line_number == (line_number_mark - 1) && lineData.length() > 0 && lineData[0] == ';' && comment != "") + { + continue; + } + + if (input_line_number == line_number_mark) + { // replace to this line + output << keyValueData; + } + else + { + output << lineData << "\n"; + } + } + break; + } + + INI_DEBUG("error! inicpp lost process of set function"); + return false; + + } while (false); + + // clear work + input.close(); + output.close(); + + std::remove(_configFileName.c_str()); + std::rename(tempFile.c_str(), _configFileName.c_str()); + + // reload + parse(); + + return true; + } + + bool set(const std::string &Section, const std::string &Key, const int Value, const std::string &comment = "") + { + std::string stringValue = std::to_string(Value); + return set(Section, Key, stringValue, comment); + } + + bool set(const std::string &Section, const std::string &Key, const double &Value, const std::string &comment = "") + { + std::string stringValue = std::to_string(Value); + return set(Section, Key, stringValue, comment); + } + + bool set(const std::string &Section, const std::string &Key, const char &Value, const std::string &comment = "") + { + std::string stringValue = ValueProxy::to_string(Value); + return set(Section, Key, stringValue, comment); + } + + // no sections: head of config file + bool set(const std::string &Key, const std::string &Value) + { + return set("", Key, Value, ""); + } + bool set(const std::string &Key, const char *Value) + { + return set("", Key, Value, ""); + } + template + bool set(const std::string &Key, const T &Value) + { + std::string stringValue = std::to_string(Value); + return set("", Key, stringValue, ""); + } + +#ifdef _ENBABLE_INICPP_STD_WSTRING_ + bool set(const std::string &Section, const std::string &Key, const std::wstring &Value, const std::string &comment = "") + { + std::wstring_convert> converter; + std::string stringValue = converter.to_bytes(Value); + + return set(Section, Key, stringValue, comment); + } +#endif + // comment for section name of key + bool setComment(const std::string &Section, const std::string &Key, const std::string &comment) + { + return set(Section, Key, (*this)[Section].toString(Key), comment); + } + // comment for no section name of key + bool setComment(const std::string &Key, const std::string &comment) + { + return set("", Key, (*this)[""].toString(Key), comment); + } + + bool isSectionExists(const std::string §ionName) + { + return _iniData.isSectionExists(sectionName); + } + + inline std::list sectionsList() + { + return _iniData.getSectionsList(); + } + + inline std::map sectionMap(const std::string §ionName) + { + return _iniData.getSectionMap(sectionName); + } + +#ifdef _ENBABLE_INICPP_STD_WSTRING_ + void setFileName(const std::wstring &fileName) + { + std::wstring_convert> converter; + std::string name = converter.to_bytes(fileName); + + _configFileName = name; + } +#else + void setFileName(const std::string &fileName) + { + _configFileName = fileName; + } +#endif + + private: + bool filterData(std::string &data) + { + if (data.length() == 0) + { + return false; + } + + if (data[0] == ';') + { + return false; + } + + if (data[0] == '#') + { + return false; + } + + return true; + } + + void trimEdges(std::string &data) + { + // remove left ' ' and '\t' + data.erase(data.begin(), std::find_if(data.begin(), data.end(), [](unsigned char c) + { return !std::isspace(c); })); + // remove right ' ' and '\t' + data.erase(std::find_if(data.rbegin(), data.rend(), [](unsigned char c) + { return !std::isspace(c); }) + .base(), + data.end()); + + // INI_DEBUG("trimEdges data:|" << data << "|"); + } + + private: + ini _iniData; + int _SumOfLines; + std::fstream _iniFile; + std::string _configFileName; + }; + +} // namespace inicpp + +#endif diff --git a/Modules/methods.cpp b/Modules/methods.cpp new file mode 100644 index 0000000..5898265 --- /dev/null +++ b/Modules/methods.cpp @@ -0,0 +1,33 @@ +#include "methods.h" +#include +#include + +methods::methods() {} + +using inicpp::ini; +using inicpp::IniManager; +using std::string; + +int getUnixTimestamp() { // 获取当前Unix时间戳 + return static_cast(time(nullptr)); +} + +string getTextMiddle(string source, string left, string right) { + size_t leftPos = source.find(left); + if (leftPos == string::npos) return ""; + leftPos += left.length(); + size_t rightPos = source.find(right, leftPos); + if (rightPos == string::npos) return ""; + return source.substr(leftPos, rightPos - leftPos); +} + +string getKeyText(string iniFilePath, string mainLevel, string subLevel) { + IniManager _ini(iniFilePath); // Load and parse the INI file + string returnValue = _ini[mainLevel][subLevel]; + return (returnValue); +} + +void setKeyText(string iniFilePath, string mainLevel, string subLevel, string value) { + IniManager _ini(iniFilePath); // Load and parse the INI file + _ini.set(mainLevel, subLevel, value); +} diff --git a/Modules/methods.h b/Modules/methods.h new file mode 100644 index 0000000..80d3f77 --- /dev/null +++ b/Modules/methods.h @@ -0,0 +1,19 @@ +#ifndef METHODS_H +#define METHODS_H +#include +#include "Modules/inicpp.hpp" + +using std::string; + +class methods +{ +public: + methods(); +}; + +int getUnixTimestamp(); +string getTextMiddle(string source, string left, string right); // Get a middle text from a string +string getKeyText(string iniFilePath, string mainLevel, string subLevel); // Get key value from INI file +void setKeyText(string iniFilePath, string mainLevel, string subLevel, string value); // Set key value to INI file + +#endif // METHODS_H diff --git a/Modules/socketactions.cpp b/Modules/socketactions.cpp new file mode 100644 index 0000000..30da0e3 --- /dev/null +++ b/Modules/socketactions.cpp @@ -0,0 +1,348 @@ +#include "socketactions.h" +#include "Logger.h" +#include +#include +#include +#include +#include +#include +#include + +namespace sockAct { + +struct SocketContext { + std::unique_ptr socket; + QMutex sendMutex; + QMutex connectMutex; + bool isInitialized = false; + std::string lastError; + QHostAddress lastConnectedHost; + quint16 lastConnectedPort = 0; + qint64 lastSendTime = 0; + qint64 lastReceiveTime = 0; + int sendCount = 0; + int receiveCount = 0; +}; + +static SocketContext& context() { // Static context + static SocketContext ctx; + return ctx; +} + +namespace { + bool ensureInitialized(); + bool internalConnectToHost(const std::string& host, quint16 port, int timeoutMs); + std::string waitForResponse(int timeoutMs); + void logNetworkEvent(const std::string& message, LogLevel level); + void handleSocketError(QAbstractSocket::SocketError error); + void resetSocket(); + QString toQString(const std::string& str); + std::string fromQString(const QString& str); + // Add UTF-8 encoding helper functions + QByteArray stringToUtf8Bytes(const std::string& str); + std::string bytesToUtf8String(const QByteArray& bytes); +} + +bool sockInit() { + QMutexLocker locker(&context().connectMutex); + + if (context().isInitialized) { + logNetworkEvent("[Socket] Initialization completed.", INFO); + return true; + } + + if (!QNetworkInterface::allInterfaces().isEmpty()) { // Check Qt Network Module + logNetworkEvent("[Socket] Qt Networking module initializing completed.", INFO); + } else { + logNetworkEvent("[Socket] No avaliable network interfaces", WARNING); + } + + context().isInitialized = true; + logNetworkEvent("[Socket] Initialization completed.", INFO); + + return true; +} + +bool connectToHost(const std::string& host, quint16 port, int timeoutMs) { + if (!ensureInitialized()) { + return false; + } + + QMutexLocker locker(&context().connectMutex); + + QString qhost = toQString(host); + + if (context().socket && + context().lastConnectedHost.toString() == qhost && + context().lastConnectedPort == port) { + if (context().socket->state() == QAbstractSocket::ConnectedState) { + logNetworkEvent(std::string("[Socket] Connected to server: ") + host + ":" + std::to_string(port), INFO); + return true; + } + } + + return internalConnectToHost(host, port, timeoutMs); +} + +std::string tcpSend(const std::string& data, int timeoutMs) { + if (!ensureInitialized()) { + return std::string(); + } + + QMutexLocker locker(&context().sendMutex); + + if (!context().socket || context().socket->state() != QAbstractSocket::ConnectedState) { + logNetworkEvent("[Socket] No connection avaliable, cannot send any data.", ERROR); + return std::string(); + } + + // Convert string to UTF-8 bytes for transmission + QByteArray sendData = stringToUtf8Bytes(data); + + qint64 bytesWritten = context().socket->write(sendData); // Send data + context().lastSendTime = QDateTime::currentMSecsSinceEpoch(); + context().sendCount++; + + if (bytesWritten == -1) { + QString errorStr = context().socket->errorString(); + logNetworkEvent("[Socket] Failed to send data: " + fromQString(errorStr), ERROR); + return std::string(); + } + + if (bytesWritten != sendData.size()) { + logNetworkEvent("[Socket] Data send not complete, expected to send " + std::to_string(sendData.size()) + + " bytes, " + std::to_string(bytesWritten) + " bytes sent", WARNING); + } + + logNetworkEvent("[Socket] " + std::to_string(bytesWritten) + " bytes has been sent to sevrer", DEBUG); + + std::string response = waitForResponse(timeoutMs); + + return response; +} + +void clearSocket() { + QMutexLocker locker1(&context().connectMutex); + QMutexLocker locker2(&context().sendMutex); + + if (context().socket) { + std::string serverInfo; + if (!context().lastConnectedHost.isNull()) { + serverInfo = fromQString(context().lastConnectedHost.toString()) + ":" + + std::to_string(context().lastConnectedPort); + } + + logNetworkEvent("[Socket] Disconnecting from server " + serverInfo + "...", INFO); + + if (context().socket->state() != QAbstractSocket::UnconnectedState) { + context().socket->abort(); + } + + context().socket->disconnect(); + + // Reset connection info + context().lastConnectedHost.clear(); + context().lastConnectedPort = 0; + context().lastSendTime = 0; + context().lastReceiveTime = 0; + context().sendCount = 0; + context().receiveCount = 0; + + logNetworkEvent("[Socket] Disconnected.", INFO); + } +} + +bool isConnected() { + if (!context().socket) { + return false; + } + return context().socket->state() == QAbstractSocket::ConnectedState; +} + +std::string getLastError() { + return context().lastError; +} + +// ==================== Internal methods ==================== + +namespace { + +QString toQString(const std::string& str) { + return QString::fromStdString(str); +} + +std::string fromQString(const QString& str) { + return str.toStdString(); +} + +// Convert string to UTF-8 bytes +QByteArray stringToUtf8Bytes(const std::string& str) { + // Use QString to convert to UTF-8 + QString qstr = QString::fromStdString(str); + return qstr.toUtf8(); +} + +// Convert UTF-8 bytes to string +std::string bytesToUtf8String(const QByteArray& bytes) { + // Convert from UTF-8 bytes to QString, then to std::string + QString qstr = QString::fromUtf8(bytes); + return qstr.toStdString(); +} + +bool ensureInitialized() { + if (!context().isInitialized) { + logNetworkEvent("[Socket] Not initialized.", ERROR); + return false; + } + return true; +} + +bool internalConnectToHost(const std::string& host, quint16 port, int timeoutMs) { + if (context().socket) { + context().socket->abort(); + context().socket.reset(); + } + + + context().socket.reset(new QTcpSocket()); + + QString qhost = toQString(host); + + QObject::connect(context().socket.get(), &QTcpSocket::connected, [host, port]() { + logNetworkEvent("[Socket] Connected to server " + host + ":" + std::to_string(port), INFO); + }); + + QObject::connect(context().socket.get(), QOverload::of(&QTcpSocket::errorOccurred), + [](QAbstractSocket::SocketError error) { + handleSocketError(error); + }); + + logNetworkEvent("[Socket] Connecting to server " + host + ":" + std::to_string(port) + + " (Timeout: " + std::to_string(timeoutMs) + "ms)...", INFO); + context().socket->connectToHost(qhost, port); + + QElapsedTimer timer; + timer.start(); + + while (context().socket->state() == QAbstractSocket::ConnectingState) { + if (timer.elapsed() > timeoutMs) { + logNetworkEvent("[Socket] Connection to server " + host + ":" + std::to_string(port) + " timeout", WARNING); + context().socket->abort(); + return false; + } + + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + QThread::msleep(10); + } + + if (context().socket->state() != QAbstractSocket::ConnectedState) { + QString errorStr = context().socket->errorString(); + logNetworkEvent("[Sockct] Failed to connect to server " + host + ":" + std::to_string(port) + + " Reason: " + fromQString(errorStr), ERROR); + return false; + } + + context().lastConnectedHost = context().socket->peerAddress(); + context().lastConnectedPort = context().socket->peerPort(); + + return true; +} + +std::string waitForResponse(int timeoutMs) { + if (!context().socket) { + return std::string(); + } + + QElapsedTimer timer; + timer.start(); + + while (context().socket->bytesAvailable() == 0) { + if (timer.elapsed() > timeoutMs) { + logNetworkEvent("[Socket] Timeout when waiting for reply (" + std::to_string(timeoutMs) + "ms)", WARNING); + return std::string(); + } + + if (!context().socket->waitForReadyRead(100)) { + if (context().socket->state() != QAbstractSocket::ConnectedState) { + logNetworkEvent("[Socket] Connection lost.", ERROR); + return std::string(); + } + } + } + + QByteArray responseData = context().socket->readAll(); + context().lastReceiveTime = QDateTime::currentMSecsSinceEpoch(); + context().receiveCount++; + + // Convert UTF-8 bytes to string + std::string response = bytesToUtf8String(responseData); + + int waitTime = timer.elapsed(); + logNetworkEvent("[Socket] Reply from server " + std::to_string(responseData.size()) + + " bytes, Time: " + std::to_string(waitTime) + "ms", DEBUG); + + return response; +} + +void logNetworkEvent(const std::string& message, LogLevel level) { + postLog(message, static_cast(level)); +} + +void handleSocketError(QAbstractSocket::SocketError error) { + std::string errorMsg; + LogLevel level = ERROR; + + switch (error) { + case QAbstractSocket::ConnectionRefusedError: + errorMsg = "[Socket] Connection refused."; + break; + case QAbstractSocket::RemoteHostClosedError: + errorMsg = "[Socket] Remote server closed connection."; + level = WARNING; + break; + case QAbstractSocket::HostNotFoundError: + errorMsg = "[Socket] No host found."; + break; + case QAbstractSocket::SocketAccessError: + errorMsg = "[Socket] Access error."; + break; + case QAbstractSocket::SocketResourceError: + errorMsg = "[Socket] Resource error."; + level = WARNING; + break; + case QAbstractSocket::SocketTimeoutError: + errorMsg = "[Socket] Timeout."; + level = WARNING; + break; + case QAbstractSocket::NetworkError: + errorMsg = "[Socket] Network fatal."; + level = FATAL; + break; + case QAbstractSocket::SslHandshakeFailedError: + errorMsg = "[Socket] TLS handshake error."; + break; + default: + errorMsg = "[Socket] Unknown error."; + break; + } + + if (context().socket) { + QString errorStr = context().socket->errorString(); + errorMsg += ": " + fromQString(errorStr); + } + + logNetworkEvent(errorMsg, level); + context().lastError = errorMsg; +} + +void resetSocket() { + if (context().socket) { + context().socket->disconnect(); + context().socket.reset(); + } + context().lastError.clear(); +} + +} + +} // namespace sockAct diff --git a/Modules/socketactions.h b/Modules/socketactions.h new file mode 100644 index 0000000..e8f87d2 --- /dev/null +++ b/Modules/socketactions.h @@ -0,0 +1,76 @@ +#ifndef SOCKETACTIONS_H +#define SOCKETACTIONS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace sockAct { + +// 日志级别枚举(与Logger.h保持一致) +enum LogLevel { + DEBUG = 0, + INFO = 1, + WARNING = 2, + ERROR = 3, + FATAL = 4 +}; + +/** + * @brief 初始化Socket模块 + * + * 初始化Qt网络模块,必须在使用其他socket函数前调用 + * @return 初始化是否成功 + */ +bool sockInit(); + +/** + * @brief 连接到TCP服务器 + * + * @param host 服务器地址 + * @param port 服务器端口 + * @param timeoutMs 连接超时时间(毫秒) + * @return 连接是否成功 + */ +bool connectToHost(const std::string& host, quint16 port, int timeoutMs = 3000); + +/** + * @brief 发送TCP数据并接收响应 + * + * 发送数据到已连接的服务器,并等待最多3秒接收响应 + * 一旦收到数据立即返回,否则等待3秒后返回空字符串 + * + * @param data 要发送的数据 + * @param timeoutMs 接收超时时间(毫秒) + * @return 接收到的数据,如果超时或出错则返回空字符串 + */ +std::string tcpSend(const std::string& data, int timeoutMs = 3000); + +/** + * @brief 断开连接并清理Socket资源 + */ +void clearSocket(); + +/** + * @brief 检查是否已连接到服务器 + * @return 连接状态 + */ +bool isConnected(); + +/** + * @brief 获取最后一次的错误信息 + * @return 错误描述 + */ +std::string getLastError(); + +} // namespace sockAct + +#endif // SOCKETACTIONS_H diff --git a/TeacherSalaryMgmt_Qt.pro b/TeacherSalaryMgmt_Qt.pro new file mode 100644 index 0000000..60288e1 --- /dev/null +++ b/TeacherSalaryMgmt_Qt.pro @@ -0,0 +1,35 @@ +QT += core gui network + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++17 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + Modules/Logger.cpp \ + Modules/methods.cpp \ + Modules/socketactions.cpp \ + dialoglogin.cpp \ + main.cpp \ + mainwindow.cpp + +HEADERS += \ + Modules/Logger.h \ + Modules/global.h \ + Modules/inicpp.hpp \ + Modules/methods.h \ + Modules/socketactions.h \ + dialoglogin.h \ + mainwindow.h + +FORMS += \ + dialoglogin.ui \ + mainwindow.ui + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target diff --git a/dialoglogin.cpp b/dialoglogin.cpp new file mode 100644 index 0000000..8e9bf75 --- /dev/null +++ b/dialoglogin.cpp @@ -0,0 +1,115 @@ +#include "dialoglogin.h" +#include "ui_dialoglogin.h" +#include "mainwindow.h" +#include "Modules/Logger.h" +#include "Modules/socketactions.h" +#include "Modules/methods.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma comment(lib, "ws2_32.lib") +#pragma comment(lib, "user32.lib") + +using std::to_string; +using std::string; + +structCheckBox checkBox; +structTextEdit textEdit; +structPushButton pushButton; +structLabel label; +structSession session; + +extern string configPath; +extern structUserInfo userInfo; + +DialogLogin::DialogLogin(QWidget *parent) + : QDialog(parent) + , ui(new Ui::DialogLogin){ + + ui->setupUi(this); + /* + * Place all elements handles here + */ + checkBox.rememberMe = findChild("checkBox_rememberMe"); + textEdit.username = findChild("textEdit_username"); + textEdit.passwd = findChild("textEdit_passwd"); + pushButton.ok = findChild("pushButton_ok"); + pushButton.cancel = findChild("pushButton_cancel"); + label.connStatus = findChild("label_connStatus"); +} + +DialogLogin::~DialogLogin() +{ + delete ui; +} + +void DialogLogin::enableAll() { + checkBox.rememberMe->setEnabled(true); + textEdit.username->setEnabled(true); + textEdit.passwd->setEnabled(true); + pushButton.ok->setEnabled(true); + pushButton.cancel->setEnabled(true); + label.connStatus->setText("Connected"); + label.connStatus->setStyleSheet("QLabel { color: green; }"); + if(userInfo.userName != "" && userInfo.passwd != ""){ + tryLogin(userInfo.userName, userInfo.passwd); + } +} + +void DialogLogin::on_pushButton_ok_clicked() +{ + QString username = textEdit.username->toPlainText(); + QString passwd = textEdit.passwd->toPlainText(); + + if(username.isEmpty() && passwd.isEmpty()){ + ::MessageBoxA(NULL, "Username or Password can't be empty.", "Error", MB_OK|MB_ICONERROR); + } + if(!tryLogin(username.toStdString(), passwd.toStdString())){ + return; + } +} + + +void DialogLogin::on_pushButton_cancel_clicked() +{ + QApplication::quit(); +} + +bool DialogLogin::tryLogin(string userName, string passWd){ + QString username = QString::fromStdString(userName); + QString passwd = QString::fromStdString(passWd); + + if (username.isEmpty() || passwd.isEmpty()){ + return false; + } else { + string resp = sockAct::tcpSend("[newUserEntry] " + username.toStdString() + " " + passwd.toStdString() + " " + to_string(getUnixTimestamp()) + ""); + if(getTextMiddle(resp, "[", "]") == "Login_Accepted"){ + session.userName = getTextMiddle(resp, "", ""); + session.token = getTextMiddle(resp, "", ""); + session.userID = getTextMiddle(resp, "", ""); + is.loggedIn = true; + + if(checkBox.rememberMe->isChecked()){ + setKeyText(configPath, "userInfo", "userName", username.toStdString()); + setKeyText(configPath, "userInfo", "passwd", passwd.toStdString()); + } + userInfo.userName = username.toStdString(); + userInfo.passwd = passwd.toStdString(); + emit loadMainWindow(); + this->close(); + return true; + } else if (getTextMiddle(resp, "[", "]") == "Login_Rejected"){ + ::MessageBoxA(NULL, ("Login failed due to: " + resp.substr(resp.find_first_of(' '))).c_str(), "Login Failed", MB_OK|MB_ICONERROR); + postLog("[userLogin] Login rejected due to: " + resp, 3); + return false; + } + return false; + } +} diff --git a/dialoglogin.h b/dialoglogin.h new file mode 100644 index 0000000..678c8c2 --- /dev/null +++ b/dialoglogin.h @@ -0,0 +1,69 @@ +#ifndef DIALOGLOGIN_H +#define DIALOGLOGIN_H + +#include +#include +#include +#include +#include +#include +#include "Modules/global.h" + +using std::string; + +// struct structCheckBox { +// QCheckBox *rememberMe = nullptr; +// }; +// extern structCheckBox checkBox; + +// struct structTextEdit { +// QTextEdit *username = nullptr; +// QTextEdit *passwd = nullptr; +// }; +// extern structTextEdit textEdit; + +// struct structPushButton { +// QPushButton *ok = nullptr; +// QPushButton *cancel = nullptr; +// }; +// extern structPushButton pushButton; + +// struct structLabel { +// QLabel *connStatus = nullptr; +// }; +// extern structLabel label; + +// namespace Ui { +// class DialogLogin; +// } + +class DialogLogin : public QDialog +{ + Q_OBJECT + +public: + explicit DialogLogin(QWidget *parent = nullptr); + ~DialogLogin(); + void enableAll(); + +signals: + void loadMainWindow(); + +private slots: + + void on_pushButton_ok_clicked(); + + void on_pushButton_cancel_clicked(); + + bool tryLogin(string userName, string passWd); + +private: + Ui::DialogLogin *ui; +}; + +// struct structCheckBox{} dialogLogin_checkBox; +// struct structTextEdit{} dialogLogin_textEdit; +// struct structPushButton{} dialogLogin_pushButton; +// struct structLabel{} dialogLogin_label; + +#endif // DIALOGLOGIN_H diff --git a/dialoglogin.ui b/dialoglogin.ui new file mode 100644 index 0000000..1488f75 --- /dev/null +++ b/dialoglogin.ui @@ -0,0 +1,217 @@ + + + DialogLogin + + + + 0 + 0 + 634 + 396 + + + + + 634 + 396 + + + + + 634 + 396 + + + + Login to server + + + + + 20 + 30 + 591 + 51 + + + + + Microsoft YaHei + 20 + + + + Qt::LayoutDirection::LeftToRight + + + <html><head/><body><p>登录到 简单教师工资管理系统</p></body></html> + + + Qt::AlignmentFlag::AlignCenter + + + + + + 50 + 130 + 101 + 31 + + + + + 16 + + + + Username + + + Qt::AlignmentFlag::AlignCenter + + + + + false + + + + 150 + 130 + 411 + 31 + + + + true + + + QTextEdit::LineWrapMode::WidgetWidth + + + + + false + + + + 150 + 190 + 411 + 31 + + + + true + + + + + + 50 + 190 + 101 + 31 + + + + + 16 + + + + Password + + + Qt::AlignmentFlag::AlignCenter + + + + + false + + + + 50 + 240 + 191 + 20 + + + + + + + Remember me on this device + + + false + + + false + + + + + + 10 + 370 + 311 + 21 + + + + + 10 + + + + false + + + false + + + Connecting... + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + false + + + + 450 + 360 + 80 + 22 + + + + OK + + + + + false + + + + 540 + 360 + 80 + 22 + + + + Cancel + + + + + + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..db8d8f0 --- /dev/null +++ b/main.cpp @@ -0,0 +1,89 @@ +#include "mainwindow.h" +#include "dialoglogin.h" +#include "Modules/inicpp.hpp" +#include "Modules/socketactions.h" +#include "Modules/Logger.h" +#include "Modules/global.h" +#include "Modules/methods.h" +#include +#include +#include +#include +#include + +using std::string; +using std::to_string; + + +SOCKET clientSock; +//static bool isRunning = true; +structUserInfo userInfo; + +string serverAddr; +int serverPort; +string configPath = ".\\Config.ini"; +string debug_configPath = ".\\debug.ini"; + +struct globalAppInfo { + string appName = "Simple Teacher Salary Management"; + string version = "0.0.0.1"; + int buildVer = 0; + string buildDate = "2025-12-22"; + string developer = "Madobi Nanami"; + string buildType = "debug"; +} appInfo; + +void clientInit_loadConfig(); +int str_toint(const string& str); // Convert string to int + +int main(int argc, char *argv[]) +{ + postLog(appInfo.appName + " Ver." + appInfo.version + "." + to_string(appInfo.buildVer) + "." + appInfo.buildType, 1); + postLog("Developed by " + appInfo.developer + " on " + appInfo.buildDate, 1); + clientInit_loadConfig(); + + QApplication a(argc, argv); + DialogLogin *p_DialogLogin = new DialogLogin; + MainWindow *p_MainWindow = nullptr; + p_DialogLogin->setAttribute(Qt::WA_DeleteOnClose); + p_DialogLogin->show(); + QObject::connect(p_DialogLogin, &DialogLogin::loadMainWindow, [&](){ + p_MainWindow = new MainWindow; + p_MainWindow->setAttribute(Qt::WA_DeleteOnClose); + p_MainWindow->show(); + }); + + sockAct::sockInit(); + postLog("[System] Trying to connect to " + serverAddr + ":" + to_string(serverPort), 1); + if(sockAct::connectToHost(serverAddr, serverPort)){ + postLog("[Socket] Connected successfully to " + serverAddr + ":" + to_string(serverPort), 1); + p_DialogLogin->enableAll(); + } + + return a.exec(); +} + +void clientInit_loadConfig(){ + if(is.debug == 1){ + serverAddr = getKeyText(debug_configPath, "Main", "serverAddr"); + serverPort = str_toint(getKeyText(debug_configPath, "Main", "serverPort")); + } else { + // : Add formal serverAddr && serverPort + serverAddr = "localhost"; + serverPort = 50010; + } + postLog("[System] Target server address has been set to: " + serverAddr + ":" + to_string(serverPort), 0); + userInfo.userName = getKeyText(configPath, "userInfo", "userName"); + userInfo.passwd = getKeyText(configPath, "userInfo", "passwd"); +} + +int str_toint(const string& str) { + try { + return stoi(str); + } + catch (...) { + return 0; // or handle error as needed + } +} + + diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..eac06ee --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,371 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include "Modules/global.h" +#include "Modules/socketactions.h" +#include "Modules/Logger.h" +#include "Modules/methods.h" +#include +#include +#include +#include +#include +#include + +structTableWidget tableWidget; +structComboBox comboBox; +extern structUserInfo userInfo; +extern structLabel label; +extern structPushButton pushButton; +extern structTextEdit textEdit; +extern structUserInfo userInfo; +extern structSession session; + +using std::string; +using std::to_string; + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); + QLabel *sb_label_currentUser = new QLabel(("Current User: " + userInfo.userName).c_str(), this); + sb_label_currentUser->setMinimumWidth(100); + label.salaryActual = findChild("label_salaryActual"); + label.salaryDiscount = findChild("label_salaryDiscount"); + label.salaryShould = findChild("label_salaryShould"); + textEdit.search = findChild("textEdit_search"); + pushButton.clear = findChild("pushButton_clear"); + pushButton.seacrh = findChild("pushButton_search"); + pushButton.submit = findChild("pushButton_submit"); + pushButton.remove = findChild("pushButton_remove"); + comboBox.findBy = findChild("comboBox_findBy"); + tableWidget.teacherInfo = findChild("tableWidget_teacherInfo"); + tableWidget.teacherSalary = findChild("tableWidget_teacherSalary"); + + ui->statusbar->addWidget(sb_label_currentUser); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::on_pushButton_search_clicked() +{ + struct structSearch{ + QString method; + QString content; + } search; + + struct structTeacherInfo{ + string id; + string name; + string gender; + string company; + string address; + string tel; + } teacherInfo; + + struct structSalaryInfo{ + string id; + string basicSalary; + string allowances; + string livingAllowances; + string phoneCost; + string waterPowerCost; + string incomeTax; + string houseRent; + string cleaningCost; + string houseingFund; + } salaryInfo; + + struct structSalary{ + int should; + int discount; + int actual; + } salary; + + string searchResult = ""; + search.method = comboBox.findBy->currentText(); + search.content = textEdit.search->toPlainText(); + if(search.method.isEmpty() && search.content.isEmpty()){ + MessageBoxA(NULL, "Search method or context can't be empty.", "Error", MB_OK|MB_ICONERROR); + return; + } else { + if(search.method == "ID"){ + searchResult = sockAct::tcpSend("[userGet] " + session.userID + " " + session.userName + " " + session.token + " " + to_string(getUnixTimestamp()) + " teacherInfo
" + search.content.toStdString() + " all"); + postLog("[method.search] " + searchResult, 0); + if(getTextMiddle(searchResult, "[", "]") == "Get_Rejected"){ + ::MessageBoxA(NULL, ("Failed to get data. Reason: " + searchResult.substr(searchResult.find_first_of(' '))).c_str(), "Error", MB_OK|MB_ICONERROR); + return; + } else { + teacherInfo.id = getTextMiddle(searchResult, "", ""); + teacherInfo.name = getTextMiddle(searchResult, "", ""); + teacherInfo.gender = getTextMiddle(searchResult, "", ""); + teacherInfo.company = getTextMiddle(searchResult, "", ""); + teacherInfo.address = getTextMiddle(searchResult, "
", "
"); + teacherInfo.tel = getTextMiddle(searchResult, "", ""); + + tableWidget.teacherInfo->setItem(0, 0, new QTableWidgetItem(QString::fromStdString(teacherInfo.id))); + tableWidget.teacherInfo->setItem(0, 1, new QTableWidgetItem(QString::fromStdString(teacherInfo.name))); + tableWidget.teacherInfo->setItem(0, 2, new QTableWidgetItem(QString::fromStdString(teacherInfo.gender))); + tableWidget.teacherInfo->setItem(0, 3, new QTableWidgetItem(QString::fromStdString(teacherInfo.company))); + tableWidget.teacherInfo->setItem(0, 4, new QTableWidgetItem(QString::fromStdString(teacherInfo.address))); + tableWidget.teacherInfo->setItem(0, 5, new QTableWidgetItem(QString::fromStdString(teacherInfo.tel))); + } + + searchResult = sockAct::tcpSend("[userGet] " + session.userID + " " + session.userName + " " + session.token + " " + to_string(getUnixTimestamp()) + " salaryForm
" + search.content.toStdString() + " all"); + postLog("[method.search] " + searchResult, 0); + if(getTextMiddle(searchResult, "[", "]") == "Get_Rejected"){ + ::MessageBoxA(NULL, ("Failed to get data. Reason: " + searchResult.substr(searchResult.find_first_of(' '))).c_str(), "Error", MB_OK|MB_ICONERROR); + return; + } else { + salaryInfo.id = getTextMiddle(searchResult, "", ""); + salaryInfo.basicSalary = getTextMiddle(searchResult, "", ""); + salaryInfo.allowances = getTextMiddle(searchResult, "", ""); + salaryInfo.cleaningCost = getTextMiddle(searchResult, "", ""); + salaryInfo.phoneCost = getTextMiddle(searchResult, "", ""); + salaryInfo.waterPowerCost = getTextMiddle(searchResult, "", ""); + salaryInfo.houseRent = getTextMiddle(searchResult, "", ""); + salaryInfo.incomeTax = getTextMiddle(searchResult, "", ""); + salaryInfo.livingAllowances = getTextMiddle(searchResult, "", ""); + salaryInfo.houseingFund = getTextMiddle(searchResult, "", ""); + + tableWidget.teacherSalary->setItem(0, 0, new QTableWidgetItem(QString::fromStdString(salaryInfo.basicSalary))); + tableWidget.teacherSalary->setItem(0, 1, new QTableWidgetItem(QString::fromStdString(salaryInfo.allowances))); + tableWidget.teacherSalary->setItem(0, 2, new QTableWidgetItem(QString::fromStdString(salaryInfo.livingAllowances))); + tableWidget.teacherSalary->setItem(0, 3, new QTableWidgetItem(QString::fromStdString(salaryInfo.phoneCost))); + tableWidget.teacherSalary->setItem(0, 4, new QTableWidgetItem(QString::fromStdString(salaryInfo.waterPowerCost))); + tableWidget.teacherSalary->setItem(0, 5, new QTableWidgetItem(QString::fromStdString(salaryInfo.houseRent))); + tableWidget.teacherSalary->setItem(0, 6, new QTableWidgetItem(QString::fromStdString(salaryInfo.incomeTax))); + tableWidget.teacherSalary->setItem(0, 7, new QTableWidgetItem(QString::fromStdString(salaryInfo.cleaningCost))); + tableWidget.teacherSalary->setItem(0, 8, new QTableWidgetItem(QString::fromStdString(salaryInfo.houseingFund))); + + salary.should = stoi(salaryInfo.basicSalary) + stoi(salaryInfo.allowances) + stoi(salaryInfo.livingAllowances); + salary.discount = stoi(salaryInfo.phoneCost) + stoi(salaryInfo.waterPowerCost) + stoi(salaryInfo.houseRent) + stoi(salaryInfo.incomeTax) + stoi(salaryInfo.cleaningCost) + stoi(salaryInfo.houseingFund); + salary.actual = salary.should - salary.discount; + label.salaryShould->setText(QString::fromStdString("应发工资: " + to_string(salary.should))); + label.salaryDiscount->setText(QString::fromStdString("合计扣款: " + to_string(salary.discount))); + label.salaryActual->setText(QString::fromStdString("实发工资: " + to_string(salary.actual))); + } + } + if(search.method == "Name"){ + searchResult = sockAct::tcpSend("[userGet] " + session.userID + " " + session.userName + " " + session.token + " " + to_string(getUnixTimestamp()) + " teacherInfo
" + search.content.toStdString() + " all"); + postLog("[method.search] " + searchResult, 0); + if(searchResult == "[Get_Rejected] ItemID does not exist."){ + MessageBoxA(NULL, "Target item not found.", "Error", MB_OK|MB_ICONERROR); + return; + } else { + if(getTextMiddle(searchResult, "[", "]") == "Get_Rejected"){ + ::MessageBoxA(NULL, ("Failed to get data. Reason: " + searchResult.substr(searchResult.find_first_of(' '))).c_str(), "Error", MB_OK|MB_ICONERROR); + return; + } else { + teacherInfo.id = getTextMiddle(searchResult, "", ""); + teacherInfo.name = getTextMiddle(searchResult, "", ""); + teacherInfo.gender = getTextMiddle(searchResult, "", ""); + teacherInfo.company = getTextMiddle(searchResult, "", ""); + teacherInfo.address = getTextMiddle(searchResult, "
", "
"); + teacherInfo.tel = getTextMiddle(searchResult, "", ""); + + tableWidget.teacherInfo->setItem(0, 0, new QTableWidgetItem(QString::fromStdString(teacherInfo.id))); + tableWidget.teacherInfo->setItem(0, 1, new QTableWidgetItem(QString::fromStdString(teacherInfo.name))); + tableWidget.teacherInfo->setItem(0, 2, new QTableWidgetItem(QString::fromStdString(teacherInfo.gender))); + tableWidget.teacherInfo->setItem(0, 3, new QTableWidgetItem(QString::fromStdString(teacherInfo.company))); + tableWidget.teacherInfo->setItem(0, 4, new QTableWidgetItem(QString::fromStdString(teacherInfo.address))); + tableWidget.teacherInfo->setItem(0, 5, new QTableWidgetItem(QString::fromStdString(teacherInfo.tel))); + } + + searchResult = sockAct::tcpSend("[userGet] " + session.userID + " " + session.userName + " " + session.token + " " + to_string(getUnixTimestamp()) + " salaryForm
" + search.content.toStdString() + " all"); + postLog("[method.search] " + searchResult, 0); + if(getTextMiddle(searchResult, "[", "]") == "Get_Rejected"){ + ::MessageBoxA(NULL, ("Failed to get data. Reason: " + searchResult.substr(searchResult.find_first_of(' '))).c_str(), "Error", MB_OK|MB_ICONERROR); + return; + } else { + salaryInfo.basicSalary = getTextMiddle(searchResult, "", ""); + salaryInfo.cleaningCost = getTextMiddle(searchResult, "", ""); + salaryInfo.phoneCost = getTextMiddle(searchResult, "", ""); + salaryInfo.waterPowerCost = getTextMiddle(searchResult, "", ""); + salaryInfo.houseRent = getTextMiddle(searchResult, "", ""); + salaryInfo.incomeTax = getTextMiddle(searchResult, "", ""); + salaryInfo.allowances = getTextMiddle(searchResult, "", ""); + salaryInfo.livingAllowances = getTextMiddle(searchResult, "", ""); + salaryInfo.houseingFund = getTextMiddle(searchResult, "", ""); + + tableWidget.teacherSalary->setItem(0, 0, new QTableWidgetItem(QString::fromStdString(salaryInfo.basicSalary))); + tableWidget.teacherSalary->setItem(0, 1, new QTableWidgetItem(QString::fromStdString(salaryInfo.allowances))); + tableWidget.teacherSalary->setItem(0, 2, new QTableWidgetItem(QString::fromStdString(salaryInfo.livingAllowances))); + tableWidget.teacherSalary->setItem(0, 3, new QTableWidgetItem(QString::fromStdString(salaryInfo.phoneCost))); + tableWidget.teacherSalary->setItem(0, 4, new QTableWidgetItem(QString::fromStdString(salaryInfo.waterPowerCost))); + tableWidget.teacherSalary->setItem(0, 5, new QTableWidgetItem(QString::fromStdString(salaryInfo.houseRent))); + tableWidget.teacherSalary->setItem(0, 6, new QTableWidgetItem(QString::fromStdString(salaryInfo.incomeTax))); + tableWidget.teacherSalary->setItem(0, 7, new QTableWidgetItem(QString::fromStdString(salaryInfo.cleaningCost))); + tableWidget.teacherSalary->setItem(0, 8, new QTableWidgetItem(QString::fromStdString(salaryInfo.houseingFund))); + + salary.should = stoi(salaryInfo.basicSalary) + stoi(salaryInfo.allowances) + stoi(salaryInfo.livingAllowances); + salary.discount = stoi(salaryInfo.phoneCost) + stoi(salaryInfo.waterPowerCost) + stoi(salaryInfo.houseRent) + stoi(salaryInfo.incomeTax) + stoi(salaryInfo.cleaningCost) + stoi(salaryInfo.houseingFund); + salary.actual = salary.should - salary.discount; + label.salaryShould->setText(QString::fromStdString("应发工资: " + to_string(salary.should))); + label.salaryDiscount->setText(QString::fromStdString("合计扣款: " + to_string(salary.discount))); + label.salaryActual->setText(QString::fromStdString("实发工资: " + to_string(salary.actual))); + } + } + } + } +} + + +void MainWindow::on_pushButton_submit_clicked() +{ + struct structTeacherInfo{ + string id = tableWidget.teacherInfo->item(0, 0)->text().toStdString(); + string name = tableWidget.teacherInfo->item(0, 1)->text().toStdString(); + string gender = tableWidget.teacherInfo->item(0, 2)->text().toStdString(); + string company = tableWidget.teacherInfo->item(0, 3)->text().toStdString(); + string address = tableWidget.teacherInfo->item(0, 4)->text().toStdString(); + string tel = tableWidget.teacherInfo->item(0, 5)->text().toStdString(); + } teacherInfo; + + struct structSalaryInfo{ + string id = tableWidget.teacherInfo->item(0, 0)->text().toStdString(); + string basicSalary = tableWidget.teacherSalary->item(0, 0)->text().toStdString(); + string allowances = tableWidget.teacherSalary->item(0, 1)->text().toStdString(); + string livingAllowances = tableWidget.teacherSalary->item(0, 2)->text().toStdString(); + string phoneCost = tableWidget.teacherSalary->item(0, 3)->text().toStdString(); + string waterPowerCost = tableWidget.teacherSalary->item(0, 4)->text().toStdString(); + string incomeTax = tableWidget.teacherSalary->item(0, 6)->text().toStdString(); + string houseRent = tableWidget.teacherSalary->item(0, 5)->text().toStdString(); + string cleaningCost = tableWidget.teacherSalary->item(0, 7)->text().toStdString(); + string houseingFund = tableWidget.teacherSalary->item(0, 8)->text().toStdString(); + } salaryInfo; + + if(teacherInfo.id == "" && teacherInfo.name == ""){ + MessageBoxA(NULL, "Name or ID can't be empty.", "Error", MB_OK|MB_ICONERROR); + return; + } else { + for(int i = 0; i < 6; i++){ + string nowColumn; + string nowItem; + switch (i){ + case 0: + nowColumn = "teacherID"; + nowItem = teacherInfo.id; + break; + case 1: + nowColumn = "teacherName"; + nowItem = teacherInfo.name; + break; + case 2: + nowColumn = "gender"; + nowItem = teacherInfo.gender; + break; + case 3: + nowColumn = "companyName"; + nowItem = teacherInfo.company; + break; + case 4: + nowColumn = "address"; + nowItem = teacherInfo.address; + break; + case 5: + nowColumn = "tel"; + nowItem = teacherInfo.tel; + break; + } + string resp = sockAct::tcpSend("[userSet] " + session.userID + " " + session.token + " " + to_string(getUnixTimestamp())+ " teacherInfo
" + teacherInfo.id + " " + nowColumn + " " + nowItem + ""); + } + for(int i = 0; i < 11; i++){ + string nowColumn; + string nowItem; + switch (i){ + case 0: + nowColumn = "teacherID"; + nowItem = teacherInfo.id; + break; + case 1: + nowColumn = "teacherName"; + nowItem = teacherInfo.name; + break; + case 2: + nowColumn = "basicSalary"; + nowItem = salaryInfo.basicSalary; + break; + case 3: + nowColumn = "allowances"; + nowItem = salaryInfo.allowances; + break; + case 4: + nowColumn = "livingAllowances"; + nowItem = salaryInfo.livingAllowances; + break; + case 5: + nowColumn = "phoneCost"; + nowItem = salaryInfo.phoneCost; + break; + case 6: + nowColumn = "waterPowerCost"; + nowItem = salaryInfo.waterPowerCost; + break; + case 7: + nowColumn = "houseRent"; + nowItem = salaryInfo.houseRent; + break; + case 8: + nowColumn = "incomeTax"; + nowItem = salaryInfo.incomeTax; + break; + case 9: + nowColumn = "cleaningCost"; + nowItem = salaryInfo.cleaningCost; + break; + case 10: + nowColumn = "housingFund"; + nowItem = salaryInfo.houseingFund; + break; + } + string resp = sockAct::tcpSend("[userSet] " + session.userID + " " + session.token + " " + to_string(getUnixTimestamp())+ " salaryForm
" + teacherInfo.id + " " + nowColumn + " " + nowItem + ""); + } + MessageBoxA(NULL, "Submitted.", "Info", MB_OK|MB_ICONINFORMATION); + } +} + + +void MainWindow::on_pushButton_remove_clicked() +{ + struct structTeacherInfo{ + string id = tableWidget.teacherInfo->item(0, 0)->text().toStdString(); + string name = tableWidget.teacherInfo->item(0, 1)->text().toStdString(); + string gender = tableWidget.teacherInfo->item(0, 2)->text().toStdString(); + string company = tableWidget.teacherInfo->item(0, 3)->text().toStdString(); + string address = tableWidget.teacherInfo->item(0, 4)->text().toStdString(); + string tel = tableWidget.teacherInfo->item(0, 5)->text().toStdString(); + } teacherInfo; + string resp = sockAct::tcpSend("[userDel] " + session.userID + " " + session.token + " " + to_string(getUnixTimestamp())+ " teacherInfo
" + teacherInfo.id + ""); + if (getTextMiddle(resp, "[", "]") == "Del_Rejected"){ + postLog("[method.del] Failed to remove due to " + resp.substr(resp.find_first_of(' ')), 3); + ::MessageBoxA(NULL, ("Failed to remove item. Reason: " + resp.substr(resp.find_first_of(' '))).c_str(), "Error", MB_OK|MB_ICONERROR); + return; + } else { + string resp = sockAct::tcpSend("[userDel] " + session.userID + " " + session.token + " " + to_string(getUnixTimestamp())+ " salaryForm
" + teacherInfo.id + ""); + if (getTextMiddle(resp, "[", "]") == "Del_Rejected"){ + postLog("[method.del] Failed to remove due to " + resp.substr(resp.find_first_of(' ')), 3); + ::MessageBoxA(NULL, ("Failed to remove item. Reason: " + resp.substr(resp.find_first_of(' '))).c_str(), "Error", MB_OK|MB_ICONERROR); + return; + } + } + MessageBoxA(NULL, "Remove successfully.", "Info", MB_OK|MB_ICONINFORMATION); +} + + +void MainWindow::on_pushButton_clear_clicked() +{ + for(int i = 0; i < 6; i++){ + tableWidget.teacherInfo->setItem(0, i, new QTableWidgetItem("")); + } + for(int i = 0; i < 10; i++){ + tableWidget.teacherSalary->setItem(0, i, new QTableWidgetItem("")); + } + label.salaryShould->setText(QString::fromStdString("应发工资: ")); + label.salaryDiscount->setText(QString::fromStdString("合计扣款: ")); + label.salaryActual->setText(QString::fromStdString("实发工资: ")); +} + diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..c8ad713 --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,33 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +QT_BEGIN_NAMESPACE +namespace Ui { +class MainWindow; +} +QT_END_NAMESPACE + + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private slots: + void on_pushButton_search_clicked(); + + void on_pushButton_submit_clicked(); + + void on_pushButton_remove_clicked(); + + void on_pushButton_clear_clicked(); + +private: + Ui::MainWindow *ui; +}; +#endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..edfb68c --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,376 @@ + + + MainWindow + + + + 0 + 0 + 953 + 395 + + + + + 953 + 395 + + + + + 953 + 395 + + + + + true + + + + Simple Teacher Salary Management System + + + 54245225 + + + -1 + + + false + + + QMainWindow::DockOption::AllowTabbedDocks|QMainWindow::DockOption::AnimatedDocks + + + true + + + + + + 819 + 320 + 121 + 31 + + + + Submit + + + + + + 10 + 10 + 61 + 31 + + + + + 12 + true + + + + Find by + + + + + + 70 + 10 + 72 + 31 + + + + + ID + + + + + Name + + + + + + + 860 + 10 + 80 + 31 + + + + Search + + + + + + 10 + 320 + 121 + 31 + + + + Clear + + + + + + 10 + 40 + 931 + 271 + + + + Info Center + + + + + 20 + 30 + 191 + 231 + + + + Personal Info + + + + + + 220 + 50 + 691 + 71 + + + + + Microsoft YaHei UI + true + + + + + + ID + + + + + 姓名 + + + + + 性别 + + + + + 单位名称 + + + + + 家庭住址 + + + + + 联系电话 + + + + + + + + + + + + 220 + 130 + 691 + 71 + + + + + Microsoft YaHei UI + true + + + + + + 基本工资 + + + + + 津贴 + + + + + 生活补贴 + + + + + 电话费 + + + + + 水电费 + + + + + 房租 + + + + + 所得税 + + + + + 卫生费 + + + + + 公积金 + + + + + + + 220 + 220 + 131 + 31 + + + + + Microsoft YaHei + 12 + true + + + + 应发工资: + + + + + + 500 + 220 + 131 + 31 + + + + + Microsoft YaHei + 12 + true + + + + 合计扣款: + + + + + + 780 + 220 + 131 + 31 + + + + + Microsoft YaHei + 12 + true + + + + 实发工资: + + + + + + + 150 + 10 + 701 + 31 + + + + true + + + + + + 409 + 320 + 121 + 31 + + + + Remove + + + pushButton_submit + label + comboBox_findBy + pushButton_search + groupBox + textEdit_search + pushButton_remove + pushButton_clear + + + + + 0 + 0 + 953 + 21 + + + + + + + +