diff --git a/Database/VisitorRecord.h b/Database/VisitorRecord.h index 9ae31eb..6ca4ab3 100644 --- a/Database/VisitorRecord.h +++ b/Database/VisitorRecord.h @@ -5,6 +5,7 @@ class VisitorRecord { public: + int id = -1; std::string url; std::string userAgent; std::string visitorUuid; @@ -19,4 +20,6 @@ public: } }; +using VisitorRecords = Wt::Dbo::collection>; + #endif // __VISITORRECORD_H__ \ No newline at end of file diff --git a/WebApplication/VisitorRecordTableModel.cpp b/WebApplication/VisitorRecordTableModel.cpp index dbaeadc..2524cfa 100644 --- a/WebApplication/VisitorRecordTableModel.cpp +++ b/WebApplication/VisitorRecordTableModel.cpp @@ -1,54 +1,170 @@ #include "VisitorRecordTableModel.h" #include "BoostLog.h" +#include "Database/Session.h" +#include "DateTime.h" + +VisitorRecordTableModel::VisitorRecordTableModel(Session &session) : m_session(session) { + Wt::Dbo::Transaction transaction(session); + Wt::Dbo::collection> records = session.find(); + int index = 1; + std::unordered_map users; + for (auto &record : records) { + if (record->url.size() <= 1) continue; + if (!users.contains(record->visitorUuid)) { + users.insert({record->visitorUuid, std::format("匿名用户{}", index++)}); + } + + m_records.push_back({*record, false}); + std::get<0>(m_records.back()).visitorUuid = users.at(record->visitorUuid); + std::get<0>(m_records.back()).id = record.id(); + } +} int VisitorRecordTableModel::columnCount(const Wt::WModelIndex &parent) const { return 5; } int VisitorRecordTableModel::rowCount(const Wt::WModelIndex &parent) const { - return 50; + return m_records.size(); +} + +Wt::WFlags VisitorRecordTableModel::headerFlags(int section, Wt::Orientation orientation) const { + if (section == 0 && orientation == Wt::Orientation::Horizontal) { + return Wt::HeaderFlag::UserCheckable; + } else { + return Wt::None; + } +} + +Wt::cpp17::any VisitorRecordTableModel::headerData(int section, Wt::Orientation orientation, Wt::ItemDataRole role) const { + if (orientation == Wt::Orientation::Horizontal) { + if (role == Wt::ItemDataRole::Level) return Wt::WAbstractTableModel::headerData(section, orientation, role); + if ((role == Wt::ItemDataRole::Checked || role == Wt::ItemDataRole::Edit) && + section == 0) { // Assuming first column for checkbox + return m_selectAll; + } else if ((section == 1) && (role == Wt::ItemDataRole::Display)) { + return "文章"; + } else if ((section == 2) && (role == Wt::ItemDataRole::Display)) { + return "时间"; + } else if ((section == 3) && (role == Wt::ItemDataRole::Display)) { + return "浏览器"; + } else if ((section == 4) && (role == Wt::ItemDataRole::Display)) { + return "用户"; + } else { + return Wt::cpp17::any{}; + } + } else { + return Wt::WAbstractTableModel::headerData(section, orientation, role); + } +} + +bool VisitorRecordTableModel::setHeaderData(int section, Wt::Orientation orientation, const Wt::cpp17::any &value, + Wt::ItemDataRole role) { + if ((section == 0) && (role == Wt::ItemDataRole::Checked)) { + m_selectAll = Wt::cpp17::any_cast(value); + for (int i = 0; i < m_records.size(); i++) { + std::get<1>(m_records[i]) = m_selectAll; + } + dataChanged().emit(index(0, 0), index(m_records.size(), 0)); + return true; + } else { + return Wt::WAbstractTableModel::setHeaderData(section, orientation, value, role); + } +} + +void VisitorRecordTableModel::sort(int column, Wt::SortOrder order) { + if (column == 2) { + layoutAboutToBeChanged().emit(); + std::sort(m_records.begin(), m_records.end(), [order](const VisitorRecord &lfh, const VisitorRecord &rfh) { + if (order == Wt::SortOrder::Ascending) { + return lfh.time < rfh.time; + } else { + return lfh.time > rfh.time; + } + }); + layoutChanged().emit(); + } } Wt::WFlags VisitorRecordTableModel::flags(const Wt::WModelIndex &index) const { if (index.column() == 0) { return Wt::ItemFlag::UserCheckable | Wt::ItemFlag::Selectable; + } else if (index.column() == 1) { + return Wt::ItemFlag::XHTMLText | Wt::ItemFlag::Selectable; } else { return Wt::ItemFlag::Selectable; } } -Wt::WFlags VisitorRecordTableModel::headerFlags(int section, Wt::Orientation orientation) const { - if (section == 0 && orientation== Wt::Orientation::Horizontal) { - return Wt::HeaderFlag::UserCheckable|Wt::HeaderFlag::XHTMLText; - } else { - return Wt::HeaderFlag::XHTMLText; - } -} - -Wt::cpp17::any VisitorRecordTableModel::headerData(int section, Wt::Orientation orientation, Wt::ItemDataRole role) const { - if ((role == Wt::ItemDataRole::Checked || role == Wt::ItemDataRole::Edit) && - section == 0) { // Assuming first column for checkbox - - return true; - } else if ((role == Wt::ItemDataRole::Display || role == Wt::ItemDataRole::Edit) && - section == 0) { // Assuming first column for checkbox - return Wt::cpp17::any{}; - } else { - return Wt::cpp17::any{}; - } -} - Wt::cpp17::any VisitorRecordTableModel::data(const Wt::WModelIndex &index, Wt::ItemDataRole role) const { - LOG(info) << "VisitorRecordTableModel: " << role.value(); + // 选择框 + int row = index.row(); + auto &record = std::get<0>(m_records.at(row)); if ((role == Wt::ItemDataRole::Checked || role == Wt::ItemDataRole::Edit) && index.column() == 0) { // Assuming first column for checkbox - - return true; - } else if ((role == Wt::ItemDataRole::Checked || role == Wt::ItemDataRole::Edit) && - index.column() == 1) { // Assuming first column for checkbox - - return false; + return std::get<1>(m_records.at(row)); + } else if ((index.column() == 1) && (role == Wt::ItemDataRole::Display)) { + auto pos = record.url.rfind("/"); + auto name = record.url; + if (pos != std::string::npos && pos < (name.size() - 1)) { + name = name.substr(pos + 1); + } + return std::format("{}", record.url, name); + } else if ((index.column() == 2) && (role == Wt::ItemDataRole::Display)) { + return DateTime::toString(record.time); + } else if ((index.column() == 3) && (role == Wt::ItemDataRole::Display)) { + return record.userAgent; + } else if ((index.column() == 4) && (role == Wt::ItemDataRole::Display)) { + return record.visitorUuid; } else { return Wt::cpp17::any{}; } } + +bool VisitorRecordTableModel::setData(const Wt::WModelIndex &index, const Wt::cpp17::any &value, Wt::ItemDataRole role) { + int row = index.row(); + if ((index.column() == 0) && (role == Wt::ItemDataRole::Checked)) { + + std::get<1>(m_records[row]) = Wt::cpp17::any_cast(value); + auto iterator = std::find_if_not(m_records.begin(), m_records.end(), [](const Item &item) { return std::get<1>(item); }); + if (iterator == m_records.end()) { + if (!m_selectAll) { + m_selectAll = true; + headerDataChanged().emit(Wt::Orientation::Horizontal, 0, 0); + } + } else { + if (m_selectAll) { + m_selectAll = false; + headerDataChanged().emit(Wt::Orientation::Horizontal, 0, 0); + } + } + return true; + } else { + return Wt::WAbstractTableModel::setData(index, value, role); + } +} + +std::vector VisitorRecordTableModel::selected() const { + std::vector ret; + for (int i = 0; i < m_records.size(); i++) { + if (std::get<1>(m_records.at(i))) { + ret.push_back(std::get<0>(m_records.at(i))); + } + } + return ret; +} + +void VisitorRecordTableModel::removeSelected() { + Wt::Dbo::Transaction transaction(m_session); + for (auto iterator = m_records.cbegin(); iterator != m_records.cend();) { + auto selected = std::get<1>(*iterator); + auto &r = std::get<0>(*iterator); + if (selected) { + Wt::Dbo::ptr item = m_session.find().where("id = ?").bind(r.id); + item.remove(); + iterator = m_records.erase(iterator); + } else { + ++iterator; + } + } +} \ No newline at end of file diff --git a/WebApplication/VisitorRecordTableModel.h b/WebApplication/VisitorRecordTableModel.h index 5626040..b9acf9a 100644 --- a/WebApplication/VisitorRecordTableModel.h +++ b/WebApplication/VisitorRecordTableModel.h @@ -1,16 +1,33 @@ #ifndef __VISITORRECORDTABLEMODEL_H__ #define __VISITORRECORDTABLEMODEL_H__ +#include "Database/VisitorRecord.h" #include +class Session; + class VisitorRecordTableModel : public Wt::WAbstractTableModel { public: + VisitorRecordTableModel(Session &session); Wt::cpp17::any data(const Wt::WModelIndex &index, Wt::ItemDataRole role = Wt::ItemDataRole::Display) const final; + bool setData(const Wt::WModelIndex &index, const Wt::cpp17::any &value, Wt::ItemDataRole role = Wt::ItemDataRole::Edit) final; int columnCount(const Wt::WModelIndex &parent = Wt::WModelIndex()) const final; int rowCount(const Wt::WModelIndex &parent = Wt::WModelIndex()) const final; Wt::WFlags flags(const Wt::WModelIndex &index) const final; Wt::WFlags headerFlags(int section, Wt::Orientation orientation = Wt::Orientation::Horizontal) const final; Wt::cpp17::any headerData(int section, Wt::Orientation orientation = Wt::Orientation::Horizontal, Wt::ItemDataRole role = Wt::ItemDataRole::Display) const final; + bool setHeaderData(int section, Wt::Orientation orientation, const Wt::cpp17::any &value, + Wt::ItemDataRole role = Wt::ItemDataRole::Edit) final; + void sort(int column, Wt::SortOrder order = Wt::SortOrder::Ascending) final; + + std::vector selected() const; + void removeSelected(); + +private: + Session &m_session; + using Item = std::tuple; + std::vector m_records; + bool m_selectAll = false; }; #endif // __VISITORRECORDTABLEMODEL_H__ \ No newline at end of file diff --git a/WebApplication/VisitorRecordsPage.cpp b/WebApplication/VisitorRecordsPage.cpp index b4da81a..aa472d6 100644 --- a/WebApplication/VisitorRecordsPage.cpp +++ b/WebApplication/VisitorRecordsPage.cpp @@ -3,59 +3,60 @@ #include "Database/Session.h" #include "VisitorRecordTableModel.h" #include +#include #include #include +#include +#include #include -class CheckBoxDelegate : public Wt::WAbstractItemDelegate { -public: - CheckBoxDelegate() : Wt::WAbstractItemDelegate() { - } - std::unique_ptr update(Wt::WWidget *widget, const Wt::WModelIndex &index, - Wt::WFlags flags) final { - auto checkBox = dynamic_cast(widget); - if (!checkBox) { - checkBox = new Wt::WCheckBox(); - } - LOG(info) << Wt::asString(index.data()); - // checkBox->setChecked(index.data().toBool()); - checkBox->setEnabled(true); - - return std::unique_ptr(checkBox); - } -}; - -VisitorRecordsPage::VisitorRecordsPage(Session &session) { +VisitorRecordsPage::VisitorRecordsPage(Session &session) : m_model{std::make_shared(session)} { addNew("访客数据统计"); + addNew(); + auto button = addNew("删除"); + button->clicked().connect(this, &VisitorRecordsPage::onDeleteButtonClicked); auto view = addNew(); view->setColumnWidth(0, Wt::WLength::Auto); view->setColumnWidth(1, Wt::WLength::Auto); view->setHeaderHeight(30); // 设置表头的高度 - view->setWidth(Wt::WLength("99%")); view->setColumnResizeEnabled(true); - view->setAlternatingRowColors(true); + view->setAlternatingRowColors(true); // 限制于 stripe-40px.gif 类似的文件,wt 有提供脚本生成 view->setRowHeight(50); view->setSelectionMode(Wt::SelectionMode::None); view->setColumnAlignment(0, Wt::AlignmentFlag::Center); - view->setHeaderAlignment(0,Wt::AlignmentFlag::Center); - view->setSortingEnabled(0,false); + view->setColumnAlignment(2, Wt::AlignmentFlag::Center); + view->setColumnAlignment(4, Wt::AlignmentFlag::Center); + + view->setHeaderAlignment(0, Wt::AlignmentFlag::Center); + view->setHeaderAlignment(1, Wt::AlignmentFlag::Center); + view->setHeaderAlignment(2, Wt::AlignmentFlag::Center); + view->setHeaderAlignment(3, Wt::AlignmentFlag::Center); + view->setHeaderAlignment(4, Wt::AlignmentFlag::Center); + + view->setSortingEnabled(0, false); + view->setSortingEnabled(1, false); + view->setSortingEnabled(3, false); + view->setSortingEnabled(4, false); // view->setEditTriggers(Wt::EditTrigger::None); // view->setItemDelegateForColumn(0, std::make_shared()); // view->setHeaderItemDelegate( std::make_shared()); - // auto model = std::make_shared>>(); - // model->setQuery(session.find()); - // model->addColumn("id", "ID",Wt::ItemFlag::UserCheckable); - // model->addColumn("url", "URL"); - // model->addColumn("time", "Time"); - // model->addColumn("user_agent", "User Agent"); - auto model = std::make_shared(); - view->setModel(model); - - /* - * Configure column widths and matching table width - */ - const int WIDTH = 240; - for (int i = 0; i < view->model()->columnCount(); ++i) view->setColumnWidth(i, 120); + view->setModel(m_model); + view->setColumnWidth(0, 60); + view->setColumnWidth(1, 180); + view->setColumnWidth(2, 180); + view->setColumnWidth(3, 480); } + +void VisitorRecordsPage::onDeleteButtonClicked() { + auto selected = m_model->selected(); + if (selected.empty()) { + Wt::WMessageBox::show("删除记录", "请先选择要删除的记录", Wt::StandardButton::Ok); + } else { + auto anwser = + Wt::WMessageBox::show("删除记录", "确定删除选择的记录?", Wt::StandardButton::Ok | Wt::StandardButton::Cancel); + if (anwser == Wt::StandardButton::Ok) { + } + } +} \ No newline at end of file diff --git a/WebApplication/VisitorRecordsPage.h b/WebApplication/VisitorRecordsPage.h index bba2596..010bc72 100644 --- a/WebApplication/VisitorRecordsPage.h +++ b/WebApplication/VisitorRecordsPage.h @@ -4,12 +4,19 @@ #include class Session; +class VisitorRecordTableModel; // https://infima.dev/ class VisitorRecordsPage : public Wt::WContainerWidget { public: VisitorRecordsPage(Session &session); + +protected: + void onDeleteButtonClicked(); + +private: + std::shared_ptr m_model; }; #endif // __VISITORRECORDSPAGE_H__ \ No newline at end of file