add task.
All checks were successful
Deploy / Build (push) Successful in 6m31s

This commit is contained in:
amass 2025-01-16 04:48:31 +08:00
parent cf9a3bfde8
commit 69220431d3
11 changed files with 327 additions and 10 deletions

View File

@ -13,7 +13,7 @@ public:
bool finished = false;
std::chrono::system_clock::time_point createTime;
std::string content;
std::string comment;
std::string remark;
Wt::Dbo::ptr<Task> parent;
Tasks children;
@ -21,7 +21,7 @@ public:
template <class Action>
void persist(Action &a) {
Wt::Dbo::field(a, content, "content");
Wt::Dbo::field(a, comment, "comment");
Wt::Dbo::field(a, remark, "remark");
Wt::Dbo::field(a, finished, "finished");
Wt::Dbo::field(a, createTime, "create_time");

View File

@ -104,7 +104,7 @@ Application::Application(const std::string &path) : ApplicationSettings(path), m
auto task = std::make_unique<Task>();
task->createTime = system_clock::time_point(seconds(root.at("createTime").as_int64()));
task->content = content;
task->comment = std::string(root.at("comment").as_string());
task->remark = std::string(root.at("comment").as_string());
auto t = database->add(std::move(task));
Wt::Dbo::ptr<Task> parent = database->find<Task>("where id=?").bind(root.at("parentId").as_int64());
if (parent) {

View File

@ -6,20 +6,20 @@
using namespace std::chrono;
BOOST_AUTO_TEST_CASE(DatabaseTest) {
BOOST_AUTO_TEST_CASE(DatabaseTest) {
auto session = Database::session();
Wt::Dbo ::Transaction transaction(*session);
auto task = std::make_unique<Task>();
task->comment = "my_comment";
task->remark = "my_comment";
task->content = "my_content";
auto p = session->add(std::move(task));
{
task = std::make_unique<Task>();
task->comment = "my_comment1";
task->remark = "my_comment1";
task->content = "my_content1";
auto c = session->add(std::move(task));
p.modify()->children.insert(c);
@ -27,14 +27,14 @@ BOOST_AUTO_TEST_CASE(DatabaseTest) {
{
task = std::make_unique<Task>();
task->comment = "my_comment2";
task->remark = "my_comment2";
task->content = "my_content2";
auto c = session->add(std::move(task));
p.modify()->children.insert(c);
{
task = std::make_unique<Task>();
task->comment = "my_comment3";
task->remark = "my_comment3";
task->content = "my_content3";
auto d = session->add(std::move(task));
c.modify()->children.insert(d);
@ -42,7 +42,7 @@ BOOST_AUTO_TEST_CASE(DatabaseTest) {
{
task = std::make_unique<Task>();
task->comment = "my_comment4";
task->remark = "my_comment4";
task->content = "my_content4";
auto d = session->add(std::move(task));
c.modify()->children.insert(d);
@ -51,6 +51,7 @@ BOOST_AUTO_TEST_CASE(DatabaseTest) {
Wt::Dbo::ptr<Task> tt = session->find<Task>("where id = 3");
LOG(info) << tt->parent->content;
BOOST_CHECK_EQUAL(tt.id(), 3);
LOG(info) << tt->children.size();
{

View File

@ -7,6 +7,7 @@
#include "NavigationBar.h"
#include "RedirectPage.h"
#include "Restful.h"
#include "TaskPage.h"
#include "VisitorRecordsPage.h"
#include "WebRTCClientPage.h"
#include "model/AuthModel.h"
@ -50,6 +51,7 @@ Application::Application(const Wt::WEnvironment &env, bool embedded)
m_root = root()->addNew<Wt::WContainerWidget>();
m_root->addStyleClass("bulma-container bulma-is-flex-grow-1 bulma-is-flex");
} else {
m_extern = true;
std::unique_ptr<Wt::WContainerWidget> topPtr = std::make_unique<Wt::WContainerWidget>();
m_root = topPtr.get();
const std::string *div = env.getParameter("div");
@ -208,6 +210,9 @@ void Application::handlePathChange(const std::string &path) {
} else if (path.starts_with("/wt/webrtc")) {
m_root->clear();
auto p = m_root->addNew<WebRTCClientPage>();
} else if (path.starts_with("/wt/task")) {
m_root->clear();
m_root->addNew<TaskPage>();
} else {
m_root->clear();
m_root->addNew<HomePage>();
@ -215,6 +220,10 @@ void Application::handlePathChange(const std::string &path) {
}
}
bool Application::inDocusaurus() const {
return m_extern;
}
Server::Server(uint16_t port, const std::string &applicationRoot, const std::string &documentRoot) {
try {
std::vector<std::string> args;

View File

@ -1,11 +1,11 @@
#ifndef __WEBAPPLICATION_H__
#define __WEBAPPLICATION_H__
#include "Database/User.h"
#include "Singleton.h"
#include <Wt/WApplication.h>
#include <memory>
#include <unordered_map>
#include "Database/User.h"
namespace Wt {
class WServer;
@ -30,6 +30,7 @@ class Application : public Wt::WApplication {
public:
Application(const Wt::WEnvironment &env, bool embedded);
~Application();
bool inDocusaurus() const;
protected:
void authEvent();
@ -37,6 +38,7 @@ protected:
private:
std::unique_ptr<Session> m_session;
bool m_extern = false;
Wt::JSignal<> m_startup;
Wt::WContainerWidget *m_root = nullptr;

View File

@ -8,6 +8,7 @@ add_library(WebApplication
RegistrationPage.h RegistrationPage.cpp
NavigationBar.h NavigationBar.cpp
RedirectPage.h RedirectPage.cpp
TaskPage.h TaskPage.cpp
VisitorRecordsPage.h VisitorRecordsPage.cpp
WebRTCClientPage.h WebRTCClientPage.cpp
Restful.h Restful.cpp

View File

@ -15,6 +15,10 @@ HomePage::HomePage() {
li->setHtmlTagName("li");
li->addNew<Wt::WAnchor>(Wt::WLink(Wt::LinkType::InternalPath, "/wt/login"), "登录页面");
li = ul->addNew<Wt::WContainerWidget>();
li->setHtmlTagName("li");
li->addNew<Wt::WAnchor>(Wt::WLink(Wt::LinkType::InternalPath, "/wt/task"), "任务清单");
li = ul->addNew<Wt::WContainerWidget>();
li->setHtmlTagName("li");
li->addNew<Wt::WAnchor>(Wt::WLink(Wt::LinkType::InternalPath, "/wt/visitor/analysis"), "访客数据");

199
WebApplication/TaskPage.cpp Normal file
View File

@ -0,0 +1,199 @@
#include "TaskPage.h"
#include "Application.h"
#include "BoostLog.h"
#include "Database/Session.h"
#include <Wt/WCheckBox.h>
#include <Wt/WContainerWidget.h>
#include <Wt/WDialog.h>
#include <Wt/WLineEdit.h>
#include <Wt/WMessageBox.h>
#include <Wt/WPushButton.h>
#include <Wt/WTemplateFormView.h>
#include <Wt/WTextArea.h>
TaskPage::TaskPage() : Wt::WTemplate(tr("Wt.Task.Home")) {
using namespace Wt;
WApplication *app = WApplication::instance();
app->messageResourceBundle().use(app->appRoot() + "webrtc");
addStyleClass("bulma-is-flex-grow-1 bulma-is-flex bulma-is-flex-direction-column");
auto addButton = bindNew<Wt::WPushButton>("add-new", "创建");
addButton->clicked().connect(this, [this]() { showAddTaskDialog(-1); });
update();
}
std::unique_ptr<Wt::WTemplate> TaskPage::createTask(const Wt::Dbo::ptr<Task> &task) {
auto ret = std::make_unique<Wt::WTemplate>(tr("Wt.Task.Item"));
auto app = dynamic_cast<WebToolkit::Application *>(Wt::WApplication::instance());
if (app->inDocusaurus()) {
ret->addStyleClass("li-none-style");
}
ret->bindString("content", task->content);
ret->bindString("remark", task->remark);
ret->setHtmlTagName("li");
auto checkbox = ret->bindNew<Wt::WCheckBox>("finished-checkbox");
checkbox->setChecked(task->finished);
checkbox->clicked().connect(ret.get(), [this, id = task.id(), checkbox]() {
{
auto session = Database::session();
Wt::Dbo::Transaction transaction(*session);
Wt::Dbo::ptr<Task> task = session->find<Task>().where("id = ?").bind(id);
if (task) {
task.modify()->finished = checkbox->isChecked();
}
}
update();
});
auto addButton = ret->bindNew<Wt::WPushButton>("add-child", tr("Wt.Icon.Add"), Wt::TextFormat::XHTML);
addButton->clicked().connect(ret.get(), [id = task.id(), this]() { showAddTaskDialog(id); });
auto removeButton = ret->bindNew<Wt::WPushButton>("remove-button", tr("Wt.Icon.Remove"), Wt::TextFormat::XHTML);
removeButton->clicked().connect(ret.get(), [this, id = task.id()]() {
Wt::StandardButton result = Wt::WMessageBox::show("删除任务", "是否删除任务?子任务也将一起删除",
Wt::StandardButton::Ok | Wt::StandardButton::Cancel);
if (result == Wt::StandardButton::Ok) {
{
auto session = Database::session();
Wt::Dbo::Transaction transaction(*session);
Wt::Dbo::ptr<Task> task = session->find<Task>().where("id = ?").bind(id);
if (task) {
removeTask(task);
}
}
update();
}
});
auto collapseButton = ret->bindNew<Wt::WPushButton>("collapse-button", tr("Wt.Icon.Expand"), Wt::TextFormat::XHTML);
collapseButton->setCheckable(true);
if (task->children.empty()) {
ret->bindEmpty("children");
collapseButton->addStyleClass("bulma-is-invisible");
} else {
auto list = ret->bindNew<Wt::WContainerWidget>("children");
list->setList(true);
for (auto child : task->children) {
list->addWidget(createTask(child));
}
collapseButton->clicked().connect(ret.get(), [collapseButton, list]() {
collapseButton->setText(collapseButton->isChecked() ? tr("Wt.Icon.Fold") : tr("Wt.Icon.Expand"));
if (collapseButton->isChecked()) {
list->addStyleClass("bulma-is-hidden");
} else {
list->removeStyleClass("bulma-is-hidden");
}
});
}
return ret;
}
class AddTaskModel : public Wt::WFormModel {
public:
static constexpr Field ContentField = "task-content";
static constexpr Field RemarkField = "task-remark";
AddTaskModel() : Wt::WFormModel() {
addField(ContentField);
addField(RemarkField);
}
Task task() {
Task t;
t.content = Wt::asString(value(ContentField)).toUTF8();
t.remark = Wt::asString(value(RemarkField)).toUTF8();
return t;
}
};
class AddTaskView : public Wt::WTemplateFormView {
public:
AddTaskView(const Wt::WString &text) : Wt::WTemplateFormView(text), m_model{std::make_shared<AddTaskModel>()} {
setFormWidget(AddTaskModel::ContentField, std::make_unique<Wt::WLineEdit>());
auto remark = std::make_unique<Wt::WTextArea>();
remark->setColumns(40);
remark->setRows(5);
setFormWidget(AddTaskModel::RemarkField, std::move(remark));
auto button = bindWidget("submit-button", std::make_unique<Wt::WPushButton>("确定"));
button->clicked().connect(this, &AddTaskView::process);
updateView(m_model.get());
}
Wt::Signal<const Task &> &accepted() {
return m_accepted;
}
std::unique_ptr<Task> task() const {
return std::make_unique<Task>(m_model->task());
}
protected:
void process() {
updateModel(m_model.get());
if (m_model->validate()) {
m_accepted.emit(m_model->task());
// updateView(m_model.get());
} else {
LOG(error) << "validate failed.";
updateView(m_model.get());
}
}
private:
Wt::Signal<const Task &> m_accepted;
std::shared_ptr<AddTaskModel> m_model;
};
void TaskPage::showAddTaskDialog(int64_t parentId) {
auto d = std::make_unique<Wt::WDialog>("添加任务");
d->setMovable(false);
auto content = d->contents()->addNew<AddTaskView>(tr("Wt.Task.Add"));
auto cancelButton = content->bindNew<Wt::WPushButton>("cancel-button", "取消");
cancelButton->clicked().connect(content, [dialog = d.get()]() { dialog->reject(); });
content->accepted().connect(this, [dialog = d.get()](const Task &task) { dialog->accept(); });
auto code = d->exec();
if (code == Wt::DialogCode::Accepted) {
auto task = content->task();
auto session = Database::session();
if (session) {
Wt::Dbo::Transaction transaction(*session);
Wt::Dbo::ptr<Task> parent;
if (parentId >= 0) {
parent = session->find<Task>(std::format("where id = {}", parentId));
}
if (parent) {
task->parent = parent;
}
session->add(std::move(task));
}
update();
}
}
void TaskPage::update() {
auto session = Database::session();
Wt::Dbo::Transaction transaction(*session);
Tasks tasks = session->find<Task>("WHERE parent_id IS NULL");
if (tasks.empty()) {
bindEmpty("tasks");
return;
} else {
auto list = bindNew<Wt::WContainerWidget>("tasks");
list->setList(true);
for (auto &task : tasks) {
list->addWidget(createTask(task));
}
}
}
void TaskPage::removeTask(Wt::Dbo::ptr<Task> task) {
if (!task->children.empty()) {
for (auto child : task->children) {
removeTask(child);
}
}
task.remove();
}

20
WebApplication/TaskPage.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef __TASKPAGE_H__
#define __TASKPAGE_H__
#include <Wt/Dbo/ptr.h>
#include <Wt/WTemplate.h>
class Task;
class TaskPage : public Wt::WTemplate {
public:
TaskPage();
protected:
void update();
void showAddTaskDialog(int64_t parentId = -1);
std::unique_ptr<Wt::WTemplate> createTask(const Wt::Dbo::ptr<Task> &task);
void removeTask(Wt::Dbo::ptr<Task> task);
};
#endif // __TASKPAGE_H__

View File

@ -3,6 +3,16 @@ body {
height: 100%;
}
ul li ul li {
margin-left: 2.5rem
}
.li-none-style {
list-style: none;
margin: 0;
padding: 0;
}
.Wt-itemview .Wt-headerdiv {
overflow: hidden;
-webkit-user-select: none;

View File

@ -26,4 +26,75 @@
</div>
</div>
</message>
<message id="Wt.Task.Home">
<h2 class="bulma-subtitle bulma-has-text-centered">任务清单</h2>
<div class="bulma-buttons bulma-is-right">
${add-new class="bulma-button bulma-is-success"}
</div>
${tasks}
</message>
<message id="Wt.Task.Item">
<div class="bulma-is-flex bulma-has-background-black-ter bulma-p-5 bulma-mb-1">
${finished-checkbox}
<div class="bulma-is-flex-grow-1 bulma-ml-5">
<div>${content}</div>
<div>${remark}</div>
</div>
<div class="bulma-buttons">
${remove-button class="bulma-button bulma-is-danger"}
${add-child class="bulma-button bulma-is-success"}
${collapse-button class="bulma-button"}
</div>
</div>
${children}
</message>
<message id="Wt.Task.Add">
<div class="bulma-field">
<label for="${id:task-content}" class="bulma-label">
${task-content-label}
</label>
<div class="bulma-control bulma-has-icons-left">
${task-content class="bulma-input" type="text"}
<span class="bulma-icon bulma-is-small bulma-is-left">
<i class="fa-solid fa-user"></i>
</span>
</div>
${task-content-info class="Wt-info bulma-help"}
</div>
<div class="bulma-field">
<label for="${id:task-remark}" class="bulma-label">
${task-remark-label}
</label>
<div class="bulma-control">
${task-remark class="bulma-textarea"}
</div>
${task-remark-info class="Wt-info bulma-help"}
</div>
<div>
${submit-button class="bulma-button"}
${cancel-button class="bulma-button"}
</div>
</message>
<message id="Wt.Icon.Expand">
<span class="bulma-icon bulma-is-small">
<i class="fa-solid fa-angle-down"></i>
</span>
</message>
<message id="Wt.Icon.Fold">
<span class="bulma-icon bulma-is-small">
<i class="fa-solid fa-angle-left"></i>
</span>
</message>
<message id="Wt.Icon.Add">
<span class="bulma-icon bulma-is-small">
<i class="fa-solid fa-plus"></i>
</span>
</message>
<message id="Wt.Icon.Remove">
<span class="bulma-icon bulma-is-small">
<i class="fa-regular fa-trash-can"></i>
</span>
</message>
<message id="task-content">内容</message>
<message id="task-remark">备注</message>
</messages>