Compare commits
16 Commits
68628a0ee2
...
12aee0ab67
Author | SHA1 | Date | |
---|---|---|---|
12aee0ab67 | |||
035448ca00 | |||
4625978ab1 | |||
ba68cd26ef | |||
079dbeaf81 | |||
394bcd83ba | |||
dea868fc2a | |||
78b6416e15 | |||
08dfb27ac9 | |||
1b1196cb21 | |||
a8f889f886 | |||
fb4d7df519 | |||
c0f8e4c4e6 | |||
2d0dacd495 | |||
02f2c73eee | |||
2e0f22a939 |
35
include/db/records.h
Normal file
35
include/db/records.h
Normal file
@ -0,0 +1,35 @@
|
||||
#ifndef DB_RECORDS_H
|
||||
#define DB_RECORDS_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
int open_records_db();
|
||||
|
||||
void close_records_db();
|
||||
|
||||
int add_record(const char* user_id, int problems_id, const char* data);
|
||||
|
||||
int add_record_ac(const char* user_id, int problems_id, const char* answer);
|
||||
|
||||
int add_record_wa(const char* user_id, int problems_id, const char* answer);
|
||||
|
||||
int add_record_uke(const char* user_id, int problems_id, const char* answer);
|
||||
|
||||
int get_record(int id, char** result);
|
||||
|
||||
int all_records_by(const char* user_id, char** result);
|
||||
|
||||
int all_records_of(int problems_id, char** result);
|
||||
|
||||
int records_count();
|
||||
|
||||
int have_ac(const char* user_id, int problem_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
27
include/db/sets.h
Normal file
27
include/db/sets.h
Normal file
@ -0,0 +1,27 @@
|
||||
#ifndef DB_SETS_H
|
||||
#define DB_SETS_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
int open_sets_db();
|
||||
|
||||
void close_sets_db();
|
||||
|
||||
int add_set(const char* name, const char* problems, const char* data, int* result);
|
||||
|
||||
int delete_set(int id);
|
||||
|
||||
int get_set(int id, char** result);
|
||||
|
||||
int modify_set(int id, const char* name, const char* problems, const char* data);
|
||||
|
||||
int all_sets(char** result);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
@ -12,7 +12,8 @@ int user_permission_handler(mg_connection* conn, void* cbdata);
|
||||
int admin_handler(mg_connection* conn, void* cbdata);
|
||||
|
||||
extern char* secret;
|
||||
|
||||
extern char* admin_session;
|
||||
|
||||
void auth_cleanup();
|
||||
|
||||
#endif // SERVER_AUTH_H
|
@ -1,8 +1,6 @@
|
||||
#ifndef SERVER_CONFIG_H
|
||||
#define SERVER_CONFIG_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int server_port;
|
||||
@ -12,7 +10,7 @@ typedef struct
|
||||
void config_ctor(config_t* this);
|
||||
void config_dtor(config_t* this);
|
||||
|
||||
bool config_read(config_t* this, const char* filename);
|
||||
int config_read(config_t* this, const char* filename);
|
||||
|
||||
int print_config();
|
||||
|
||||
|
@ -53,4 +53,16 @@ void res_query_problem2(mg_connection* conn, const char* problem, const char* an
|
||||
void res_check_answer(mg_connection* conn, int flag);
|
||||
void res_all_problems(mg_connection* conn, const char* problems);
|
||||
|
||||
void res_get_record(mg_connection* conn, const char* record);
|
||||
void res_all_records(mg_connection* conn, const char* records);
|
||||
void res_all_records_by(mg_connection* conn, const char* records);
|
||||
void res_all_records_of(mg_connection* conn, const char* records);
|
||||
void res_records_count(mg_connection* conn, int count);
|
||||
void res_have_ac(mg_connection* conn, int status);
|
||||
|
||||
void res_add_set(mg_connection* conn, int id);
|
||||
void res_get_set(mg_connection* conn, const char* set);
|
||||
void res_all_sets(mg_connection* conn, const char* sets);
|
||||
void res_sets_count(mg_connection* conn, int count);
|
||||
|
||||
#endif
|
@ -4,6 +4,8 @@
|
||||
#include "server/types.h"
|
||||
|
||||
int problems_handler(mg_connection* conn, void* cbdata);
|
||||
int records_handler(mg_connection* conn, void* cbdata);
|
||||
int sets_handler(mg_connection* conn, void* cbdata);
|
||||
|
||||
extern char* secret;
|
||||
|
||||
|
@ -2,17 +2,43 @@
|
||||
|
||||
#include <print>
|
||||
|
||||
#include <leveldb/comparator.h>
|
||||
#include <leveldb/db.h>
|
||||
#include <leveldb/write_batch.h>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
class NumericComparator : public leveldb::Comparator
|
||||
{
|
||||
public:
|
||||
int Compare(const leveldb::Slice& a, const leveldb::Slice& b) const override
|
||||
{
|
||||
auto x = std::stoi(a.ToString()), y = std::stoi(b.ToString());
|
||||
|
||||
if (x < y) {
|
||||
return -1;
|
||||
} else if (x > y) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const char* Name() const override
|
||||
{
|
||||
return "NumericComparator";
|
||||
}
|
||||
|
||||
void FindShortestSeparator(std::string* start, const leveldb::Slice& limit) const override {}
|
||||
void FindShortSuccessor(std::string* key) const override {}
|
||||
};
|
||||
|
||||
using leveldb_ptr = leveldb::DB*;
|
||||
|
||||
auto problems_db = leveldb_ptr{nullptr};
|
||||
auto max_problem_id = 0;
|
||||
|
||||
auto comparator = NumericComparator{};
|
||||
|
||||
extern "C"
|
||||
{
|
||||
@ -20,6 +46,8 @@ extern "C"
|
||||
{
|
||||
auto opts = leveldb::Options{};
|
||||
|
||||
opts.comparator = &comparator;
|
||||
|
||||
opts.create_if_missing = true;
|
||||
|
||||
auto status = leveldb::DB::Open(opts, "db/problems", &problems_db);
|
||||
@ -29,22 +57,20 @@ extern "C"
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto value = std::string{};
|
||||
status = problems_db->Get(leveldb::ReadOptions{}, "@", &value);
|
||||
if (status.ok()) {
|
||||
auto json = nlohmann::json::parse(value);
|
||||
json["max_problem_id"].get_to(max_problem_id);
|
||||
} else if (status.IsNotFound()) {
|
||||
auto json = nlohmann::json{
|
||||
{"max_problem_id", 0}
|
||||
};
|
||||
status = problems_db->Put(leveldb::WriteOptions{}, "@", json.dump());
|
||||
return 1;
|
||||
} else {
|
||||
std::println(stderr, "Failed to get max problem id: {}", status.ToString());
|
||||
auto it = problems_db->NewIterator(leveldb::ReadOptions());
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
auto id = std::stoi(it->key().ToString());
|
||||
if (id > max_problem_id) max_problem_id = id;
|
||||
}
|
||||
|
||||
if (!it->status().ok()) {
|
||||
std::println(stderr, "Failed to get max problem id: {}", it->status().ToString());
|
||||
delete it;
|
||||
delete problems_db;
|
||||
return 0;
|
||||
}
|
||||
|
||||
delete it;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -58,10 +84,6 @@ extern "C"
|
||||
auto batch = leveldb::WriteBatch{};
|
||||
++max_problem_id;
|
||||
auto json = nlohmann::json{
|
||||
{"max_problem_id", max_problem_id}
|
||||
};
|
||||
batch.Put("@", json.dump());
|
||||
json = nlohmann::json{
|
||||
{"content", problem },
|
||||
{"answer", answer },
|
||||
{"error", check_error}
|
||||
@ -176,16 +198,17 @@ extern "C"
|
||||
{
|
||||
auto problems = std::vector<int>{};
|
||||
|
||||
leveldb::Iterator* it = problems_db->NewIterator(leveldb::ReadOptions());
|
||||
auto it = problems_db->NewIterator(leveldb::ReadOptions());
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
if (!it->key().compare("@")) continue;
|
||||
problems.emplace_back(std::stoi(it->key().ToString()));
|
||||
}
|
||||
|
||||
if (!it->status().ok()) {
|
||||
std::println(stderr, "Failed to get all problems: {}", it->status().ToString());
|
||||
delete it;
|
||||
return 0;
|
||||
}
|
||||
delete it;
|
||||
auto json = nlohmann::json(problems);
|
||||
|
||||
*result = strdup(json.dump().c_str());
|
||||
|
186
src/db/records.cpp
Normal file
186
src/db/records.cpp
Normal file
@ -0,0 +1,186 @@
|
||||
#include "records.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <print>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <leveldb/db.h>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using leveldb_ptr = leveldb::DB*;
|
||||
|
||||
auto records_db = leveldb_ptr{nullptr};
|
||||
auto records_count_ = 0;
|
||||
|
||||
auto records = std::unordered_map<std::string, std::unordered_set<int>>{};
|
||||
|
||||
extern "C"
|
||||
{
|
||||
int open_records_db()
|
||||
{
|
||||
auto opts = leveldb::Options{};
|
||||
|
||||
opts.create_if_missing = true;
|
||||
|
||||
auto status = leveldb::DB::Open(opts, "db/records", &records_db);
|
||||
|
||||
if (!status.ok()) {
|
||||
std::println(stderr, "Failed to open records database: {}", status.ToString());
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto user_id_buf = std::string{};
|
||||
auto problem_id_buf = 0;
|
||||
auto status_buf = std::string{};
|
||||
|
||||
auto it = records_db->NewIterator(leveldb::ReadOptions());
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
auto json = nlohmann::json::parse(it->value().ToString());
|
||||
// std::println(stderr, "{}", json.dump());
|
||||
json["data"]["status"].get_to(status_buf);
|
||||
if (status_buf == "ac") {
|
||||
// std::println(stderr, "{}", json.dump());
|
||||
json["user_id"].get_to(user_id_buf);
|
||||
json["problem"].get_to(problem_id_buf);
|
||||
records[user_id_buf].emplace(problem_id_buf);
|
||||
}
|
||||
records_count_++;
|
||||
}
|
||||
|
||||
if (!it->status().ok()) {
|
||||
std::println(stderr, "Failed to count records: {}", it->status().ToString());
|
||||
delete it;
|
||||
delete records_db;
|
||||
return 0;
|
||||
}
|
||||
|
||||
delete it;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void close_records_db()
|
||||
{
|
||||
delete records_db;
|
||||
}
|
||||
|
||||
int add_record(const char* user_id, int problems_id, const char* data)
|
||||
{
|
||||
auto json = nlohmann::json{
|
||||
{"user_id", user_id },
|
||||
{"problem", problems_id },
|
||||
{"data", nlohmann::json::parse(data)}
|
||||
};
|
||||
auto status = records_db->Put(leveldb::WriteOptions{}, std::to_string(records_count_), json.dump());
|
||||
if (!status.ok()) {
|
||||
std::println(stderr, "Failed to add a record: {}", status.ToString());
|
||||
return 0;
|
||||
}
|
||||
++records_count_;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int add_record_impl(const char* user_id, int problems_id, const char* answer, auto status)
|
||||
{
|
||||
auto now = std::chrono::utc_clock::now();
|
||||
auto timestamp_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now);
|
||||
auto timestamp = timestamp_ms.time_since_epoch().count();
|
||||
|
||||
auto json = nlohmann::json{
|
||||
{"answer", answer },
|
||||
{"status", status },
|
||||
{"timestamp", timestamp}
|
||||
};
|
||||
return add_record(user_id, problems_id, json.dump().c_str());
|
||||
}
|
||||
|
||||
int add_record_ac(const char* user_id, int problems_id, const char* answer)
|
||||
{
|
||||
return add_record_impl(user_id, problems_id, answer, "ac");
|
||||
}
|
||||
int add_record_wa(const char* user_id, int problems_id, const char* answer)
|
||||
{
|
||||
return add_record_impl(user_id, problems_id, answer, "wa");
|
||||
}
|
||||
int add_record_uke(const char* user_id, int problems_id, const char* answer)
|
||||
{
|
||||
return add_record_impl(user_id, problems_id, answer, "uke");
|
||||
}
|
||||
|
||||
int get_record(int id, char** result)
|
||||
{
|
||||
auto value = std::string{};
|
||||
|
||||
auto status = records_db->Get(leveldb::ReadOptions{}, std::to_string(id), &value);
|
||||
if (status.IsNotFound()) {
|
||||
*result = NULL;
|
||||
} else if (!status.ok()) {
|
||||
std::println(stderr, "Failed to get a record: {}", status.ToString());
|
||||
return 0;
|
||||
} else {
|
||||
*result = strdup(value.c_str());
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int all_records_by(const char* user_id, char** result)
|
||||
{
|
||||
auto problems = std::vector<int>{};
|
||||
auto buf = std::string{};
|
||||
|
||||
auto it = records_db->NewIterator(leveldb::ReadOptions());
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
auto json = nlohmann::json::parse(it->value().ToString());
|
||||
json["user_id"].get_to(buf);
|
||||
if (buf != user_id) continue;
|
||||
problems.emplace_back(std::stoi(it->key().ToString()));
|
||||
}
|
||||
|
||||
if (!it->status().ok()) {
|
||||
std::println(stderr, "Failed to get all records by user id: {}", it->status().ToString());
|
||||
delete it;
|
||||
return 0;
|
||||
}
|
||||
delete it;
|
||||
auto json = nlohmann::json(problems);
|
||||
|
||||
*result = strdup(json.dump().c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int all_records_of(int problems_id, char** result)
|
||||
{
|
||||
auto problems = std::vector<int>{};
|
||||
auto buf = 0;
|
||||
|
||||
auto it = records_db->NewIterator(leveldb::ReadOptions());
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
auto json = nlohmann::json::parse(it->value().ToString());
|
||||
json["problem"].get_to(buf);
|
||||
if (buf != problems_id) continue;
|
||||
problems.emplace_back(std::stoi(it->key().ToString()));
|
||||
}
|
||||
|
||||
if (!it->status().ok()) {
|
||||
std::println(stderr, "Failed to get all records of the problem: {}", it->status().ToString());
|
||||
delete it;
|
||||
return 0;
|
||||
}
|
||||
delete it;
|
||||
auto json = nlohmann::json(problems);
|
||||
|
||||
*result = strdup(json.dump().c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int records_count()
|
||||
{
|
||||
return records_count_;
|
||||
}
|
||||
|
||||
int have_ac(const char* user_id, int problem_id)
|
||||
{
|
||||
return records[user_id].contains(problem_id);
|
||||
}
|
||||
}
|
136
src/db/sets.cpp
Normal file
136
src/db/sets.cpp
Normal file
@ -0,0 +1,136 @@
|
||||
#include "sets.h"
|
||||
#include "leveldb/options.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <print>
|
||||
|
||||
|
||||
#include <leveldb/db.h>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
|
||||
using leveldb_ptr = leveldb::DB*;
|
||||
|
||||
auto sets_db = leveldb_ptr{nullptr};
|
||||
auto max_sets_id = 0;
|
||||
|
||||
extern "C"
|
||||
{
|
||||
int open_sets_db()
|
||||
{
|
||||
auto opts = leveldb::Options{};
|
||||
|
||||
opts.create_if_missing = true;
|
||||
|
||||
auto status = leveldb::DB::Open(opts, "db/sets", &sets_db);
|
||||
|
||||
if (!status.ok()) {
|
||||
std::println(stderr, "Failed to open sets database: {}", status.ToString());
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto it = sets_db->NewIterator(leveldb::ReadOptions());
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
auto id = std::stoi(it->key().ToString());
|
||||
if (id > max_sets_id) max_sets_id = id;
|
||||
}
|
||||
|
||||
if (!it->status().ok()) {
|
||||
std::println(stderr, "Failed to get max set id: {}", it->status().ToString());
|
||||
delete it;
|
||||
delete sets_db;
|
||||
return 0;
|
||||
}
|
||||
|
||||
delete it;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void close_sets_db()
|
||||
{
|
||||
delete sets_db;
|
||||
}
|
||||
|
||||
int add_set(const char* name, const char* problems, const char* data, int* result)
|
||||
{
|
||||
auto json = nlohmann::json{
|
||||
{"name", name },
|
||||
{"problems", nlohmann::json::parse(problems)},
|
||||
{"data", nlohmann::json::parse(data) }
|
||||
};
|
||||
auto status = sets_db->Put(leveldb::WriteOptions{}, std::to_string(max_sets_id + 1), json.dump());
|
||||
if (!status.ok()) {
|
||||
std::println(stderr, "Failed to add a set: {}", status.ToString());
|
||||
return 0;
|
||||
}
|
||||
++max_sets_id;
|
||||
*result = max_sets_id;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int delete_set(int id)
|
||||
{
|
||||
auto status = sets_db->Delete(leveldb::WriteOptions{}, std::to_string(id));
|
||||
if (status.IsNotFound()) {
|
||||
return -1;
|
||||
} else if (!status.ok()) {
|
||||
std::println(stderr, "Failed to delete a set: {}", status.ToString());
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int get_set(int id, char** result)
|
||||
{
|
||||
auto value = std::string{};
|
||||
|
||||
auto status = sets_db->Get(leveldb::ReadOptions{}, std::to_string(id), &value);
|
||||
if (status.IsNotFound()) {
|
||||
*result = NULL;
|
||||
} else if (!status.ok()) {
|
||||
std::println(stderr, "Failed to get a set: {}", status.ToString());
|
||||
return 0;
|
||||
} else {
|
||||
*result = strdup(value.c_str());
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int modify_set(int id, const char* name, const char* problems, const char* data)
|
||||
{
|
||||
auto json = nlohmann::json{
|
||||
{"name", name },
|
||||
{"problems", nlohmann::json::parse(problems)},
|
||||
{"data", nlohmann::json::parse(data) }
|
||||
};
|
||||
auto status = sets_db->Put(leveldb::WriteOptions{}, std::to_string(id), json.dump());
|
||||
if (!status.ok()) {
|
||||
std::println(stderr, "Failed to add a set: {}", status.ToString());
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int all_sets(char** result)
|
||||
{
|
||||
auto problems = std::vector<int>{};
|
||||
auto buf = 0;
|
||||
|
||||
auto it = sets_db->NewIterator(leveldb::ReadOptions());
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
problems.emplace_back(std::stoi(it->key().ToString()));
|
||||
}
|
||||
|
||||
if (!it->status().ok()) {
|
||||
std::println(stderr, "Failed to get all sets of the problem: {}", it->status().ToString());
|
||||
delete it;
|
||||
return 0;
|
||||
}
|
||||
delete it;
|
||||
auto json = nlohmann::json(problems);
|
||||
|
||||
*result = strdup(json.dump().c_str());
|
||||
return 1;
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
#include "jwt.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <print>
|
||||
|
||||
#include <jwt-cpp/jwt.h>
|
||||
|
||||
|
||||
@ -23,9 +25,13 @@ extern "C"
|
||||
|
||||
auto get_payload(const char* token) -> char*
|
||||
{
|
||||
auto decoded_token = jwt::decode(token);
|
||||
auto payload = decoded_token.get_payload_claim("user_id").as_string();
|
||||
return strdup(payload.c_str());
|
||||
try {
|
||||
auto decoded_token = jwt::decode(token);
|
||||
auto payload = decoded_token.get_payload_claim("user_id").as_string();
|
||||
return strdup(payload.c_str());
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto verify_token(const char* token, const char* secret) -> int
|
||||
|
221
src/main.c
221
src/main.c
@ -1,12 +1,14 @@
|
||||
#include "db/problems.h"
|
||||
#include "db/records.h"
|
||||
#include "db/sets.h"
|
||||
#include "server/auth.h"
|
||||
#include "server/config.h"
|
||||
#include "server/study.h"
|
||||
#include "server/types.h"
|
||||
|
||||
#include "jwt/jwt.h"
|
||||
#include "hash/hash.h"
|
||||
|
||||
#include "db/auth.h"
|
||||
#include "db/db.h"
|
||||
|
||||
|
||||
#include <civetweb.h>
|
||||
|
||||
@ -15,127 +17,162 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
leveldb_t* db = NULL;
|
||||
|
||||
void signal_handler(int signal)
|
||||
{
|
||||
fprintf(stderr, "Signal %d received, cleaning up...\n", signal);
|
||||
close_db(db);
|
||||
exit(1);
|
||||
printf("Recieved signal %d.\n", signal);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void cleanup(void)
|
||||
{
|
||||
if (admin_session) free(admin_session);
|
||||
if (secret) free(secret);
|
||||
close_sets_db();
|
||||
close_records_db();
|
||||
close_problems_db();
|
||||
close_user_db();
|
||||
printf("Cleanup.\n");
|
||||
}
|
||||
|
||||
int reset_admin_password()
|
||||
{
|
||||
char* random_password = kqm_random_password(16);
|
||||
printf("Admin password is `%s`, "
|
||||
"this password will not be showed again, "
|
||||
"please save it well by yourself.\n",
|
||||
random_password);
|
||||
|
||||
int flag = set_admin_password(random_password);
|
||||
|
||||
if (!flag) {
|
||||
printf("cannot set admin password hash\n");
|
||||
}
|
||||
free(random_password);
|
||||
return flag;
|
||||
}
|
||||
|
||||
int init_admin_password()
|
||||
{
|
||||
int result;
|
||||
int flag = has_admin_password(&result);
|
||||
if (flag) {
|
||||
if (!result) {
|
||||
flag = reset_admin_password();
|
||||
}
|
||||
} else {
|
||||
printf("cannot check admin password hash\n");
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
int init_db()
|
||||
{
|
||||
if (!open_user_db()) {
|
||||
fprintf(stderr, "cannot open user database\n");
|
||||
return 1;
|
||||
}
|
||||
if (!open_problems_db()) {
|
||||
fprintf(stderr, "cannot open problems database");
|
||||
return 1;
|
||||
}
|
||||
if (!open_records_db()) {
|
||||
fprintf(stderr, "cannot open records database\n");
|
||||
return 1;
|
||||
}
|
||||
if (!open_sets_db()) {
|
||||
fprintf(stderr, "cannot open problems set database\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
open_user_db();
|
||||
/*
|
||||
这一部分在 4.1.1。
|
||||
*/
|
||||
if (atexit(cleanup)) {
|
||||
printf("cannot register cleanup function\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// while (1) {
|
||||
// printf("> ");
|
||||
// char op[16];
|
||||
// scanf("%15s", op);
|
||||
// if (strcmp(op, "exit") == 0) break;
|
||||
// if (strcmp(op, "login") == 0) {
|
||||
// char user_id[64];
|
||||
// char password[64];
|
||||
// scanf("%63s %63s", user_id, password);
|
||||
// int result;
|
||||
// int flag = login(user_id, password, &result);
|
||||
// if (!flag) {
|
||||
// printf("Failed to login\n");
|
||||
// } else {
|
||||
// printf("Result: %s\n", result ? "login success" : "login failed");
|
||||
// }
|
||||
// }else if(strcmp(op, "logout") == 0){
|
||||
// char user_id[64];
|
||||
// scanf("%63s", user_id);
|
||||
// int flag = logout(user_id);
|
||||
// if (!flag) {
|
||||
// printf("Failed to logout\n");
|
||||
// } else {
|
||||
// printf("Logout success\n");
|
||||
// }
|
||||
// } else if (strcmp(op, "register") == 0) {
|
||||
// char user_id[64];
|
||||
// char password[64];
|
||||
// scanf("%63s %63s", user_id, password);
|
||||
// int result;
|
||||
// int flag = check_user_exists(user_id, &result);
|
||||
// if(!flag){
|
||||
// printf("Failed to check user existence\n");
|
||||
// continue;
|
||||
// }else if (result) {
|
||||
// printf("User already exists\n");
|
||||
// continue;
|
||||
// }
|
||||
// flag = set_user_password(user_id, password);
|
||||
// if (!flag) {
|
||||
// printf("Failed to register\n");
|
||||
// } else {
|
||||
// printf("Registration success\n");
|
||||
// }
|
||||
// } else if (strcmp(op, "check") == 0) {
|
||||
// char user_id[64];
|
||||
// scanf("%63s", user_id);
|
||||
// int result;
|
||||
// int flag = check_user_exists(user_id, &result);
|
||||
// if (!flag) {
|
||||
// printf("Failed to check user existence\n");
|
||||
// } else {
|
||||
// printf("Result: %s\n", result ? "user exists" : "user does not exist");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
int flag = 0;
|
||||
|
||||
// getchar();
|
||||
flag |= SIG_ERR == signal(SIGINT, signal_handler);
|
||||
flag |= SIG_ERR == signal(SIGTERM, signal_handler);
|
||||
flag |= SIG_ERR == signal(SIGABRT, signal_handler);
|
||||
flag |= SIG_ERR == signal(SIGSEGV, signal_handler);
|
||||
|
||||
// char* jwt = create_token("Hello", "world");
|
||||
// printf("JWT: %s\n", jwt);
|
||||
// int flag = verify_token(jwt, "world");
|
||||
// printf("Flag: %d\n", flag);
|
||||
// char* id = get_payload(jwt);
|
||||
// printf("ID: %s\n", id);
|
||||
// free(id);
|
||||
// free(jwt);
|
||||
if (flag) {
|
||||
printf("cannot register signal handler\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// if (!(db = open_db("db/main"))) return 1;
|
||||
if (!init_db()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// signal(SIGINT, signal_handler);
|
||||
// signal(SIGTERM, signal_handler);
|
||||
// signal(SIGSEGV, signal_handler);
|
||||
if (!init_admin_password()) {
|
||||
fprintf(stderr, "cannot init admin password hash\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (argc > 1) {
|
||||
if (!strcmp(argv[1], "repasswd")) {
|
||||
int flag = reset_admin_password();
|
||||
if (!flag) {
|
||||
fprintf(stderr, "cannot reset admin password\n");
|
||||
}
|
||||
return !flag;
|
||||
}
|
||||
}
|
||||
|
||||
config_t config;
|
||||
int stat = config_read(&config, "configs/config.json");
|
||||
if (!stat) {
|
||||
printf("config_read() returned %d\n", stat);
|
||||
flag = config_read(&config, "configs/config.json");
|
||||
if (!flag) {
|
||||
fprintf(stderr, "cannot read config file\n");
|
||||
config_dtor(&config);
|
||||
return 1;
|
||||
}
|
||||
if (config.server_port > 65535 || config.server_port <= 0) {
|
||||
printf("Invalid server_port: %d\n", config.server_port);
|
||||
printf("invalid server_port: %d\n", config.server_port);
|
||||
config_dtor(&config);
|
||||
return 1;
|
||||
}
|
||||
secret = config.secret;
|
||||
secret = config.secret;
|
||||
config.secret = NULL;
|
||||
config_dtor(&config);
|
||||
|
||||
char server_port_str[6];
|
||||
snprintf(server_port_str, sizeof(server_port_str), "%d", config.server_port);
|
||||
|
||||
const char* options[] =
|
||||
{"document_root", "./www", "listening_ports", server_port_str, "error_log_file", "logs/civetweb.log", NULL};
|
||||
const char* options[] = {"document_root",
|
||||
"./www",
|
||||
"listening_ports",
|
||||
server_port_str,
|
||||
"error_log_file",
|
||||
"logs/civetweb.log",
|
||||
NULL};
|
||||
mg_callbacks callbacks;
|
||||
mg_context* ctx;
|
||||
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
ctx = mg_start(&callbacks, NULL, options);
|
||||
|
||||
mg_set_request_handler(ctx, "/api/auth/login", login_handler, NULL);
|
||||
mg_set_request_handler(ctx, "/api/auth/register", register_handler, NULL);
|
||||
mg_set_request_handler(ctx, "/api/auth/delete", delete_handler, NULL);
|
||||
mg_set_request_handler(ctx, "/api/auth/login", user_login_handler, NULL);
|
||||
mg_set_request_handler(ctx, "/api/auth/register", user_register_handler, NULL);
|
||||
mg_set_request_handler(ctx, "/api/auth/delete", user_delete_handler, NULL);
|
||||
mg_set_request_handler(ctx, "/api/auth/repasswd", user_repasswd_handler, NULL);
|
||||
mg_set_request_handler(ctx, "/api/auth/logout", user_logout_handler, NULL);
|
||||
mg_set_request_handler(ctx, "/api/auth/permission", user_permission_handler, NULL);
|
||||
mg_set_request_handler(ctx, "/api/auth/admin", admin_handler, NULL);
|
||||
|
||||
mg_set_request_handler(ctx, "/api/study/problems", problems_handler, NULL);
|
||||
mg_set_request_handler(ctx, "/api/study/records", records_handler, NULL);
|
||||
mg_set_request_handler(ctx, "/api/study/sets", sets_handler, NULL);
|
||||
|
||||
|
||||
printf("Server started on port(s) %s\n", mg_get_option(ctx, "listening_ports"));
|
||||
getchar();
|
||||
mg_stop(ctx);
|
||||
|
||||
close_user_db();
|
||||
// close_db(db);
|
||||
return 0;
|
||||
}
|
||||
|
@ -1,7 +1,14 @@
|
||||
#include "server/auth.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
char* secret = NULL;
|
||||
|
||||
char* admin_session = NULL;
|
||||
|
||||
void auth_cleanup()
|
||||
{
|
||||
if (secret) free(secret);
|
||||
if (admin_session) free(admin_session);
|
||||
}
|
||||
|
@ -82,6 +82,11 @@ int user_delete_handler(mg_connection* conn, void* cbdata)
|
||||
}
|
||||
|
||||
char* user_id = get_payload(form.token);
|
||||
if (!user_id) {
|
||||
res_permission_denied(conn);
|
||||
delete_form_dtor(&form);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (form.user_id && strcmp(user_id, form.user_id)) {
|
||||
int perm1;
|
||||
|
@ -14,36 +14,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// typedef struct
|
||||
// {
|
||||
// char* token;
|
||||
// char* user_id;
|
||||
// } logout_form_t;
|
||||
|
||||
// static void logout_form_dtor(logout_form_t* form)
|
||||
// {
|
||||
// if (form->token) free(form->token);
|
||||
// if (form->user_id) free(form->user_id);
|
||||
// }
|
||||
|
||||
// static int field_found(const char* key, const char* filename, char* path, size_t pathlen, void* user_data)
|
||||
// {
|
||||
// return MG_FORM_FIELD_HANDLE_GET;
|
||||
// }
|
||||
|
||||
// static int field_get(const char* key, const char* value, size_t valuelen, void* user_data)
|
||||
// {
|
||||
// logout_form_t* form = (logout_form_t*)user_data;
|
||||
// if (strcmp(key, "token") == 0) {
|
||||
// form->token = kqm_strndup(value, valuelen);
|
||||
// } else if (strcmp(key, "user_id") == 0) {
|
||||
// form->user_id = kqm_strndup(value, valuelen);
|
||||
// }
|
||||
// if (form->token && form->user_id) {
|
||||
// return MG_FORM_FIELD_HANDLE_ABORT;
|
||||
// }
|
||||
// return MG_FORM_FIELD_HANDLE_GET;
|
||||
// }
|
||||
|
||||
int user_logout_handler(mg_connection* conn, void* cbdata)
|
||||
{
|
||||
@ -61,62 +31,5 @@ int user_logout_handler(mg_connection* conn, void* cbdata)
|
||||
|
||||
res_logout(conn);
|
||||
|
||||
// logout_form_t form = {NULL, NULL};
|
||||
|
||||
// mg_form_data_handler logout_callback = {
|
||||
// .field_found = field_found,
|
||||
// .field_get = field_get,
|
||||
// .field_store = NULL,
|
||||
// .user_data = &form,
|
||||
// };
|
||||
|
||||
// mg_handle_form_request(conn, &logout_callback);
|
||||
|
||||
// if (!form.token) {
|
||||
// res_need_token(conn);
|
||||
// logout_form_dtor(&form);
|
||||
// return 1;
|
||||
// }
|
||||
// if (!verify_token(form.token, secret)) {
|
||||
// res_auth_fail(conn);
|
||||
// logout_form_dtor(&form);
|
||||
// return 1;
|
||||
// }
|
||||
|
||||
// char* user_id = get_payload(form.token);
|
||||
|
||||
// if (form.user_id && strcmp(user_id, form.user_id)) {
|
||||
// int perm1;
|
||||
// int flag = get_user_permission(user_id, &perm1);
|
||||
// if (!flag) {
|
||||
// res_check_permission_fail(conn);
|
||||
// }
|
||||
|
||||
// int perm2;
|
||||
// flag = get_user_permission(form.user_id, &perm2);
|
||||
// if (!flag) {
|
||||
// res_check_permission_fail(conn);
|
||||
// }
|
||||
|
||||
// if (perm1 < perm2) {
|
||||
// int flag = logout_user(form.user_id);
|
||||
// if (!flag) {
|
||||
// res_logout_fail(conn);
|
||||
// } else {
|
||||
// res_logout(conn);
|
||||
// }
|
||||
// } else {
|
||||
// res_permission_denied(conn);
|
||||
// }
|
||||
// } else {
|
||||
// int flag = logout_user(user_id);
|
||||
// if (!flag) {
|
||||
// res_logout_account_fail(conn);
|
||||
// } else {
|
||||
// res_logout_account(conn);
|
||||
// }
|
||||
// }
|
||||
// free(user_id);
|
||||
// logout_form_dtor(&form);
|
||||
return 1;
|
||||
}
|
||||
|
@ -150,6 +150,11 @@ int user_repasswd_handler(mg_connection* conn, void* cbdata)
|
||||
}
|
||||
|
||||
char* user_id = get_payload(form.token);
|
||||
if (!user_id) {
|
||||
res_permission_denied(conn);
|
||||
repasswd_form_dtor(&form);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (form.user_id && strcmp(user_id, form.user_id)) {
|
||||
impl_others(conn, user_id, &form);
|
||||
|
@ -43,17 +43,17 @@ void config_dtor(config_t* this)
|
||||
if (this->secret) free(this->secret);
|
||||
}
|
||||
|
||||
bool config_read(config_t* this, const char* filename)
|
||||
int config_read(config_t* this, const char* filename)
|
||||
{
|
||||
char* json_data = read_file(filename);
|
||||
if (json_data == NULL) {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON* root = cJSON_Parse(json_data);
|
||||
if (root == NULL) {
|
||||
free(json_data);
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
cJSON* server_port = cJSON_GetObjectItem(root, "server_port");
|
||||
@ -73,5 +73,5 @@ bool config_read(config_t* this, const char* filename)
|
||||
cJSON_Delete(root);
|
||||
free(json_data);
|
||||
|
||||
return true;
|
||||
return 1;
|
||||
}
|
||||
|
@ -260,7 +260,7 @@ void res_need_xxx(mg_connection* conn, const char* xxx)
|
||||
void res_500(mg_connection* conn, const char* error)
|
||||
{
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 400 Bad Request\r\n"
|
||||
"HTTP/1.1 500 Internal Server Error\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"error\":\"%s\"}",
|
||||
@ -270,7 +270,7 @@ void res_500(mg_connection* conn, const char* error)
|
||||
void res_404(mg_connection* conn, const char* error)
|
||||
{
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 400 Bad Request\r\n"
|
||||
"HTTP/1.1 404 Not Found\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"error\":\"%s\"}",
|
||||
@ -345,3 +345,103 @@ void res_all_problems(mg_connection* conn, const char* problems)
|
||||
"{\"success\":\"success to get all the problems\", \"result\":%s}",
|
||||
problems);
|
||||
}
|
||||
|
||||
void res_get_record(mg_connection* conn, const char* record)
|
||||
{
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"success\":\"success to get the record\", \"record\":%s}",
|
||||
record);
|
||||
}
|
||||
|
||||
void res_all_records(mg_connection* conn, const char* records)
|
||||
{
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"success\":\"success to get all records\", \"records\":%s}",
|
||||
records);
|
||||
}
|
||||
|
||||
void res_all_records_by(mg_connection* conn, const char* records)
|
||||
{
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"success\":\"success to get all records by the user\", \"records\":%s}",
|
||||
records);
|
||||
}
|
||||
|
||||
void res_all_records_of(mg_connection* conn, const char* records)
|
||||
{
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"success\":\"success to get all records of the problem\", \"records\":%s}",
|
||||
records);
|
||||
}
|
||||
|
||||
void res_records_count(mg_connection* conn, int count)
|
||||
{
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"success\":\"success to get the records count\", \"result\":%d}",
|
||||
count);
|
||||
}
|
||||
|
||||
void res_have_ac(mg_connection* conn, int status)
|
||||
{
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"success\":\"success to check if have ac\", \"result\":%s}",
|
||||
status ? "true" : "false");
|
||||
}
|
||||
|
||||
void res_add_set(mg_connection* conn, int id)
|
||||
{
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"success\":\"success to add a new set\", \"id\":%d}",
|
||||
id);
|
||||
}
|
||||
|
||||
void res_get_set(mg_connection* conn, const char* set)
|
||||
{
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"success\":\"success to get the set\", \"set\":%s}",
|
||||
set);
|
||||
}
|
||||
|
||||
void res_all_sets(mg_connection* conn, const char* sets)
|
||||
{
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"success\":\"success to get all sets\", \"sets\":%s}",
|
||||
sets);
|
||||
}
|
||||
|
||||
void res_sets_count(mg_connection* conn, int count)
|
||||
{
|
||||
mg_printf(conn,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: application/json\r\n"
|
||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||
"{\"success\":\"success to get the sets count\", \"result\":%d}",
|
||||
count);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "db/problems.h"
|
||||
#include "db/records.h"
|
||||
#include "jwt/jwt.h"
|
||||
#include "server/response.h"
|
||||
#include "server/study.h"
|
||||
@ -141,9 +142,12 @@ static void impl_query(mg_connection* conn, problem_form_t* form, int permission
|
||||
}
|
||||
if (pr2) {
|
||||
res_query_problem2(conn, result1, result2, result3);
|
||||
free(result2);
|
||||
} else {
|
||||
res_query_problem(conn, result1, result3);
|
||||
}
|
||||
free(result1);
|
||||
free(result3);
|
||||
}
|
||||
|
||||
static void impl_modify(mg_connection* conn, problem_form_t* form)
|
||||
@ -173,7 +177,7 @@ static void impl_modify(mg_connection* conn, problem_form_t* form)
|
||||
res_200(conn, "success to modify the problem");
|
||||
}
|
||||
|
||||
static void impl_check(mg_connection* conn, problem_form_t* form)
|
||||
static void impl_check(mg_connection* conn, problem_form_t* form, const char* user_id)
|
||||
{
|
||||
if (!form->has_id) {
|
||||
res_need_xxx(conn, "problem id");
|
||||
@ -195,9 +199,15 @@ static void impl_check(mg_connection* conn, problem_form_t* form)
|
||||
|
||||
int flag = check_answer(form->id, form->answer, &result);
|
||||
if (!flag) {
|
||||
add_record_uke(user_id, form->id, form->answer);
|
||||
res_500(conn, "fail to check the answer");
|
||||
return;
|
||||
}
|
||||
if (result) {
|
||||
add_record_ac(user_id, form->id, form->answer);
|
||||
} else {
|
||||
add_record_wa(user_id, form->id, form->answer);
|
||||
}
|
||||
res_check_answer(conn, result);
|
||||
}
|
||||
|
||||
@ -210,6 +220,7 @@ static void impl_all(mg_connection* conn, problem_form_t* form)
|
||||
return;
|
||||
}
|
||||
res_all_problems(conn, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
int problems_handler(mg_connection* conn, void* cbdata)
|
||||
@ -244,6 +255,11 @@ int problems_handler(mg_connection* conn, void* cbdata)
|
||||
res_need_token(conn);
|
||||
} else {
|
||||
char* user_id = get_payload(form.token);
|
||||
if (!user_id) {
|
||||
res_permission_denied(conn);
|
||||
problem_form_dtor(&form);
|
||||
return 1;
|
||||
}
|
||||
int result;
|
||||
int flag = get_user_permission(user_id, &result);
|
||||
if (!flag) {
|
||||
@ -272,17 +288,14 @@ int problems_handler(mg_connection* conn, void* cbdata)
|
||||
impl_modify(conn, &form);
|
||||
}
|
||||
} else if (!strcmp(form.action, "check")) {
|
||||
impl_check(conn, &form);
|
||||
impl_check(conn, &form, user_id);
|
||||
} else if (!strcmp(form.action, "all")) {
|
||||
if (result == 2) {
|
||||
res_permission_denied(conn);
|
||||
} else {
|
||||
impl_all(conn, &form);
|
||||
}
|
||||
impl_all(conn, &form);
|
||||
} else {
|
||||
res_bad_action(conn);
|
||||
}
|
||||
}
|
||||
free(user_id);
|
||||
}
|
||||
|
||||
problem_form_dtor(&form);
|
||||
|
203
src/server/study/records.c
Normal file
203
src/server/study/records.c
Normal file
@ -0,0 +1,203 @@
|
||||
#include "db/records.h"
|
||||
#include "jwt/jwt.h"
|
||||
#include "server/response.h"
|
||||
#include "server/study.h"
|
||||
#include "server/types.h"
|
||||
#include "server/util.h"
|
||||
|
||||
|
||||
#include "db/auth.h"
|
||||
|
||||
#include "hash/hash.h"
|
||||
|
||||
#include <civetweb.h>
|
||||
|
||||
#include <cjson/cJSON.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char* action;
|
||||
int id;
|
||||
int has_id;
|
||||
char* user_id;
|
||||
int problem_id;
|
||||
int has_problem_id;
|
||||
} record_form_t;
|
||||
|
||||
static void record_form_dtor(record_form_t* form)
|
||||
{
|
||||
if (form->action) free(form->action);
|
||||
if (form->user_id) free(form->user_id);
|
||||
}
|
||||
|
||||
static int field_found(const char* key, const char* filename, char* path, size_t pathlen, void* user_data)
|
||||
{
|
||||
return MG_FORM_FIELD_HANDLE_GET;
|
||||
}
|
||||
|
||||
static int field_get(const char* key, const char* value, size_t valuelen, void* user_data)
|
||||
{
|
||||
record_form_t* form = (record_form_t*)user_data;
|
||||
if (strcmp(key, "action") == 0) {
|
||||
form->action = kqm_strndup(value, valuelen);
|
||||
} else if (strcmp(key, "id") == 0) {
|
||||
char* id_str = kqm_strndup(value, valuelen);
|
||||
|
||||
form->id = atoi(id_str);
|
||||
form->has_id = 1;
|
||||
free(id_str);
|
||||
} else if (strcmp(key, "user_id") == 0) {
|
||||
form->user_id = kqm_strndup(value, valuelen);
|
||||
} else if (strcmp(key, "problem_id") == 0) {
|
||||
char* id_str = kqm_strndup(value, valuelen);
|
||||
|
||||
form->problem_id = atoi(id_str);
|
||||
form->has_problem_id = 1;
|
||||
free(id_str);
|
||||
}
|
||||
return MG_FORM_FIELD_HANDLE_GET;
|
||||
}
|
||||
|
||||
static void impl_query(mg_connection* conn, record_form_t* form)
|
||||
{
|
||||
if (!form->has_id) {
|
||||
res_need_xxx(conn, "record id");
|
||||
return;
|
||||
}
|
||||
|
||||
char* result = NULL;
|
||||
int flag = get_record(form->id, &result);
|
||||
if (!flag) {
|
||||
res_500(conn, "failed to get a record");
|
||||
return;
|
||||
}
|
||||
if (!result) {
|
||||
res_404(conn, "the record does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
res_get_record(conn, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
static void impl_all(mg_connection* conn, record_form_t* form)
|
||||
{
|
||||
char* result = NULL;
|
||||
int flag = all_records(&result);
|
||||
if (!flag) {
|
||||
res_500(conn, "failed to get all records");
|
||||
return;
|
||||
}
|
||||
|
||||
res_all_records(conn, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
static void impl_by(mg_connection* conn, record_form_t* form)
|
||||
{
|
||||
if (!form->user_id) {
|
||||
res_need_xxx(conn, "user id");
|
||||
return;
|
||||
}
|
||||
|
||||
char* result = NULL;
|
||||
int flag = all_records_by(form->user_id, &result);
|
||||
if (!flag) {
|
||||
res_500(conn, "failed to get the records by the user");
|
||||
return;
|
||||
}
|
||||
|
||||
res_all_records_by(conn, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
static void impl_of(mg_connection* conn, record_form_t* form)
|
||||
{
|
||||
if (!form->has_problem_id) {
|
||||
res_need_xxx(conn, "problem id");
|
||||
return;
|
||||
}
|
||||
|
||||
char* result = NULL;
|
||||
int flag = all_records_of(form->problem_id, &result);
|
||||
if (!flag) {
|
||||
res_500(conn, "failed to get the records of the problem");
|
||||
return;
|
||||
}
|
||||
|
||||
res_all_records_of(conn, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
static void impl_count(mg_connection* conn)
|
||||
{
|
||||
int count = records_count();
|
||||
res_records_count(conn, count);
|
||||
}
|
||||
|
||||
static void impl_have(mg_connection* conn, record_form_t* form)
|
||||
{
|
||||
if (!form->user_id) {
|
||||
res_need_xxx(conn, "user id");
|
||||
return;
|
||||
}
|
||||
if (!form->has_problem_id) {
|
||||
res_need_xxx(conn, "problem id");
|
||||
return;
|
||||
}
|
||||
|
||||
int status = have_ac(form->user_id, form->problem_id);
|
||||
res_have_ac(conn, status);
|
||||
}
|
||||
|
||||
int records_handler(mg_connection* conn, void* cbdata)
|
||||
{
|
||||
const mg_request_info* post_body = mg_get_request_info(conn);
|
||||
|
||||
if (post_body == NULL) {
|
||||
res_null_req(conn);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(post_body->request_method, "POST")) {
|
||||
res_must_post(conn);
|
||||
return 1;
|
||||
}
|
||||
|
||||
record_form_t form = {NULL, 0, 0, NULL, 0, 0};
|
||||
|
||||
mg_form_data_handler record_callback = {
|
||||
.field_found = field_found,
|
||||
.field_get = field_get,
|
||||
.field_store = NULL,
|
||||
.user_data = &form,
|
||||
};
|
||||
|
||||
mg_handle_form_request(conn, &record_callback);
|
||||
|
||||
|
||||
if (!form.action) {
|
||||
res_need_action(conn);
|
||||
} else {
|
||||
if (!strcmp(form.action, "query")) {
|
||||
impl_query(conn, &form);
|
||||
} else if (!strcmp(form.action, "by")) {
|
||||
impl_by(conn, &form);
|
||||
} else if (!strcmp(form.action, "of")) {
|
||||
impl_of(conn, &form);
|
||||
} else if (!strcmp(form.action, "count")) {
|
||||
impl_count(conn);
|
||||
} else if (!strcmp(form.action, "have")) {
|
||||
impl_have(conn, &form);
|
||||
} else {
|
||||
res_bad_action(conn);
|
||||
}
|
||||
}
|
||||
|
||||
record_form_dtor(&form);
|
||||
return 1;
|
||||
}
|
249
src/server/study/sets.c
Normal file
249
src/server/study/sets.c
Normal file
@ -0,0 +1,249 @@
|
||||
#include "db/sets.h"
|
||||
#include "jwt/jwt.h"
|
||||
#include "server/response.h"
|
||||
#include "server/study.h"
|
||||
#include "server/types.h"
|
||||
#include "server/util.h"
|
||||
|
||||
|
||||
#include "db/auth.h"
|
||||
|
||||
#include "hash/hash.h"
|
||||
|
||||
#include <civetweb.h>
|
||||
|
||||
#include <cjson/cJSON.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char* action;
|
||||
char* token;
|
||||
int id;
|
||||
int has_id;
|
||||
char* name;
|
||||
char* problems;
|
||||
char* data;
|
||||
} set_form_t;
|
||||
|
||||
static void set_form_dtor(set_form_t* form)
|
||||
{
|
||||
if (form->action) free(form->action);
|
||||
if (form->token) free(form->token);
|
||||
if (form->name) free(form->name);
|
||||
if (form->problems) free(form->problems);
|
||||
if (form->data) free(form->data);
|
||||
}
|
||||
|
||||
static int field_found(const char* key, const char* filename, char* path, size_t pathlen, void* user_data)
|
||||
{
|
||||
return MG_FORM_FIELD_HANDLE_GET;
|
||||
}
|
||||
|
||||
static int field_get(const char* key, const char* value, size_t valuelen, void* user_data)
|
||||
{
|
||||
set_form_t* form = (set_form_t*)user_data;
|
||||
if (strcmp(key, "action") == 0) {
|
||||
form->action = kqm_strndup(value, valuelen);
|
||||
} else if (strcmp(key, "token") == 0) {
|
||||
form->token = kqm_strndup(value, valuelen);
|
||||
} else if (strcmp(key, "id") == 0) {
|
||||
char* id_str = kqm_strndup(value, valuelen);
|
||||
|
||||
form->id = atoi(id_str);
|
||||
form->has_id = 1;
|
||||
free(id_str);
|
||||
} else if (strcmp(key, "name") == 0) {
|
||||
form->name = kqm_strndup(value, valuelen);
|
||||
} else if (strcmp(key, "problems") == 0) {
|
||||
form->problems = kqm_strndup(value, valuelen);
|
||||
} else if (strcmp(key, "data") == 0) {
|
||||
form->data = kqm_strndup(value, valuelen);
|
||||
}
|
||||
return MG_FORM_FIELD_HANDLE_GET;
|
||||
}
|
||||
|
||||
static void impl_add(mg_connection* conn, set_form_t* form)
|
||||
{
|
||||
if (!form->name) {
|
||||
res_need_xxx(conn, "name");
|
||||
return;
|
||||
}
|
||||
if (!form->problems) {
|
||||
res_need_xxx(conn, "problem ids");
|
||||
return;
|
||||
}
|
||||
if (!form->data) {
|
||||
res_need_xxx(conn, "other data");
|
||||
return;
|
||||
}
|
||||
|
||||
int result;
|
||||
int flag = add_set(form->name, form->problems, form->data, &result);
|
||||
if (!flag) {
|
||||
res_500(conn, "failed to get a set");
|
||||
return;
|
||||
}
|
||||
|
||||
res_add_set(conn, result);
|
||||
}
|
||||
|
||||
static void impl_delete(mg_connection* conn, set_form_t* form)
|
||||
{
|
||||
if (!form->has_id) {
|
||||
res_need_xxx(conn, "set id");
|
||||
return;
|
||||
}
|
||||
|
||||
int flag = delete_set(form->id);
|
||||
if (flag == -1) {
|
||||
res_404(conn, "the set does not exist");
|
||||
} else if (!flag) {
|
||||
res_500(conn, "failed to delete a set");
|
||||
return;
|
||||
}
|
||||
|
||||
res_200(conn, "successed to delete the set");
|
||||
}
|
||||
|
||||
static void impl_query(mg_connection* conn, set_form_t* form)
|
||||
{
|
||||
if (!form->has_id) {
|
||||
res_need_xxx(conn, "set id");
|
||||
return;
|
||||
}
|
||||
|
||||
char* result = NULL;
|
||||
int flag = get_set(form->id, &result);
|
||||
if (!flag) {
|
||||
res_500(conn, "failed to get a set");
|
||||
return;
|
||||
}
|
||||
if (!result) {
|
||||
res_404(conn, "the set does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
res_get_set(conn, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
static void impl_modify(mg_connection* conn, set_form_t* form)
|
||||
{
|
||||
if (!form->has_id) {
|
||||
res_need_xxx(conn, "set id");
|
||||
return;
|
||||
}
|
||||
if (!form->name) {
|
||||
res_need_xxx(conn, "name");
|
||||
return;
|
||||
}
|
||||
if (!form->problems) {
|
||||
res_need_xxx(conn, "problem ids");
|
||||
return;
|
||||
}
|
||||
if (!form->data) {
|
||||
res_need_xxx(conn, "other data");
|
||||
return;
|
||||
}
|
||||
|
||||
int flag = modify_set(form->id, form->name, form->problems, form->data);
|
||||
if (!flag) {
|
||||
res_500(conn, "failed to modify the set");
|
||||
return;
|
||||
}
|
||||
|
||||
res_200(conn, "successed to modify the set");
|
||||
}
|
||||
|
||||
static void impl_all(mg_connection* conn, set_form_t* form)
|
||||
{
|
||||
char* result = NULL;
|
||||
int flag = all_sets(&result);
|
||||
if (!flag) {
|
||||
res_500(conn, "failed to get all sets");
|
||||
return;
|
||||
}
|
||||
|
||||
res_all_sets(conn, result);
|
||||
free(result);
|
||||
}
|
||||
|
||||
int sets_handler(mg_connection* conn, void* cbdata)
|
||||
{
|
||||
const mg_request_info* post_body = mg_get_request_info(conn);
|
||||
|
||||
if (post_body == NULL) {
|
||||
res_null_req(conn);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(post_body->request_method, "POST")) {
|
||||
res_must_post(conn);
|
||||
return 1;
|
||||
}
|
||||
|
||||
set_form_t form = {NULL, NULL, 0, 0, NULL, NULL, NULL};
|
||||
|
||||
mg_form_data_handler set_callback = {
|
||||
.field_found = field_found,
|
||||
.field_get = field_get,
|
||||
.field_store = NULL,
|
||||
.user_data = &form,
|
||||
};
|
||||
|
||||
mg_handle_form_request(conn, &set_callback);
|
||||
|
||||
|
||||
if (!form.action) {
|
||||
res_need_action(conn);
|
||||
} else if (!form.token) {
|
||||
res_need_token(conn);
|
||||
} else {
|
||||
char* user_id = get_payload(form.token);
|
||||
if (!user_id) {
|
||||
res_permission_denied(conn);
|
||||
set_form_dtor(&form);
|
||||
return 1;
|
||||
}
|
||||
int result;
|
||||
int flag = get_user_permission(user_id, &result);
|
||||
if (!flag) {
|
||||
res_check_permission_fail(conn);
|
||||
} else if (result != 1 && result != 2) {
|
||||
res_permission_denied(conn);
|
||||
} else {
|
||||
if (!strcmp(form.action, "add")) {
|
||||
if (result == 2) {
|
||||
res_permission_denied(conn);
|
||||
} else {
|
||||
impl_add(conn, &form);
|
||||
}
|
||||
} else if (!strcmp(form.action, "delete")) {
|
||||
if (result == 2) {
|
||||
res_permission_denied(conn);
|
||||
} else {
|
||||
impl_delete(conn, &form);
|
||||
}
|
||||
} else if (!strcmp(form.action, "query")) {
|
||||
impl_query(conn, &form);
|
||||
} else if (!strcmp(form.action, "modify")) {
|
||||
if (result == 2) {
|
||||
res_permission_denied(conn);
|
||||
} else {
|
||||
impl_modify(conn, &form);
|
||||
}
|
||||
} else if (!strcmp(form.action, "all")) {
|
||||
impl_all(conn, &form);
|
||||
} else {
|
||||
res_bad_action(conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_form_dtor(&form);
|
||||
return 1;
|
||||
}
|
24
ui/components.d.ts
vendored
24
ui/components.d.ts
vendored
@ -7,15 +7,29 @@ export {}
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Admin: typeof import('./src/components/Admin.vue')['default']
|
||||
AdminPanel: typeof import('./src/components/admin/AdminPanel.vue')['default']
|
||||
Auth: typeof import('./src/components/Auth.vue')['default']
|
||||
DeleteAccountDialog: typeof import('./src/components/admin/DeleteAccountDialog.vue')['default']
|
||||
Dialog: typeof import('./src/components/Dialog.vue')['default']
|
||||
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
|
||||
HelloWorld2: typeof import('./src/components/HelloWorld2.vue')['default']
|
||||
LoginForm: typeof import('./src/components/LoginForm.vue')['default']
|
||||
RegisterForm: typeof import('./src/components/RegisterForm.vue')['default']
|
||||
Homepage: typeof import('./src/components/Homepage.vue')['default']
|
||||
LoginForm: typeof import('./src/components/admin/LoginForm.vue')['default']
|
||||
ManagePanel: typeof import('./src/components/study/ManagePanel.vue')['default']
|
||||
Permission: typeof import('./src/components/admin/Permission.vue')['default']
|
||||
Problem: typeof import('./src/components/study/Problem.vue')['default']
|
||||
ProblemDialog: typeof import('./src/components/study/ProblemDialog.vue')['default']
|
||||
ProblemList: typeof import('./src/components/study/ProblemList.vue')['default']
|
||||
Problems: typeof import('./src/components/Problems.vue')['default']
|
||||
RecordList: typeof import('./src/components/RecordList.vue')['default']
|
||||
RegisterForm: typeof import('./src/components/users/RegisterForm.vue')['default']
|
||||
Repasswd2Dialog: typeof import('./src/components/users/Repasswd2Dialog.vue')['default']
|
||||
'Repasswd2Dialog ': typeof import('./src/components/admin/Repasswd2Dialog .vue')['default']
|
||||
RepasswdDialog: typeof import('./src/components/admin/RepasswdDialog.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SetList: typeof import('./src/components/study/SetList.vue')['default']
|
||||
Study: typeof import('./src/components/Study.vue')['default']
|
||||
Toy: typeof import('./src/components/Toy.vue')['default']
|
||||
UserPanel: typeof import('./src/components/UserPanel.vue')['default']
|
||||
UserPanel: typeof import('./src/components/users/UserPanel.vue')['default']
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Welcome to Vuetify 3</title>
|
||||
<title>KQM Math</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
@ -17,6 +17,7 @@
|
||||
"core-js": "^3.37.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"mathjs": "^14.0.1",
|
||||
"node-forge": "^1.3.1",
|
||||
"pinia": "^2.3.0",
|
||||
"roboto-fontface": "*",
|
||||
|
@ -1,44 +1,52 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-layout class="rounded rounded-md">
|
||||
<v-app-bar color="surface-variant" title="Math">
|
||||
<v-btn icon>
|
||||
<v-icon>mdi-magnify</v-icon>
|
||||
</v-btn>
|
||||
<v-app-bar color="primary" prominent>
|
||||
<v-app-bar-nav-icon variant="text" @click.stop="drawer = !drawer"></v-app-bar-nav-icon>
|
||||
|
||||
<v-btn icon>
|
||||
<v-icon>mdi-heart</v-icon>
|
||||
</v-btn>
|
||||
<v-toolbar-title>KQM Math</v-toolbar-title>
|
||||
|
||||
<v-btn icon>
|
||||
<v-icon>mdi-dots-vertical</v-icon>
|
||||
</v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<template v-if="$vuetify.display.mdAndUp">
|
||||
<v-btn icon="mdi-magnify" variant="text"></v-btn>
|
||||
|
||||
<v-btn icon="mdi-filter" variant="text"></v-btn>
|
||||
</template>
|
||||
|
||||
<v-btn icon="mdi-dots-vertical" variant="text"></v-btn>
|
||||
</v-app-bar>
|
||||
<v-navigation-drawer>
|
||||
<v-navigation-drawer v-model="drawer" expand-on-hover rail
|
||||
:location="$vuetify.display.mobile ? 'bottom' : 'left'">
|
||||
<v-list nav>
|
||||
<v-list-item to="/" prepend-icon="mdi-email" title="Inbox" value="inbox"></v-list-item>
|
||||
<v-list-item to="/auth" prepend-icon="mdi-account-supervisor-circle" title="登录" value="supervisors"></v-list-item>
|
||||
<v-list-item prepend-icon="mdi-clock-start" title="Clock-in" value="clockin"></v-list-item>
|
||||
<v-list-item prepend-icon="mdi-clock-start" :title="token" value="clockin"></v-list-item>
|
||||
<v-list-item prepend-icon="mdi-clock-start" title="Clock-in" value="clockin"></v-list-item>
|
||||
<v-list-item to="/" prepend-icon="mdi-home" title="首页"></v-list-item>
|
||||
<v-list-item to="/auth" prepend-icon="mdi-account-supervisor-circle"
|
||||
:title="authStore.isAuthenticated ? '用户面板' : '登录'" value="supervisors"></v-list-item>
|
||||
<v-list-item v-if="authStore.isAuthenticated" to="/problems" prepend-icon="mdi-bookshelf"
|
||||
title="题库"></v-list-item>
|
||||
<v-list-item v-if="authStore.isAuthenticated" to="/sets" prepend-icon="mdi-book-multiple"
|
||||
title="题单"></v-list-item>
|
||||
<v-list-item v-if="authStore.isAuthenticated" to="/records" prepend-icon="mdi-list-box-outline"
|
||||
title="提交记录"></v-list-item>
|
||||
<v-list-item to="/admin" prepend-icon="mdi-security" title="后台管理"></v-list-item>
|
||||
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
<v-main class="d-flex align-center justify-center" style="min-height: 300px;">
|
||||
<router-view></router-view>
|
||||
</v-main>
|
||||
</v-layout>
|
||||
<!-- <v-main>
|
||||
<router-view />
|
||||
</v-main> -->
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useAuthStore } from './store/auth';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const token = ref(authStore.token);
|
||||
const storedToken = ref(authStore.token);
|
||||
|
||||
const drawer = ref(true);
|
||||
|
||||
</script>
|
||||
|
@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<v-sheet class="d-flex align-center justify-center flex-wrap text-center mx-auto px-4"
|
||||
v-if="!authStore.isAuthenticated" width="640" height="670">
|
||||
<v-carousel v-model="loginMode" height="100%" delimiter-icon="mdi-square" color="blue-darken-2"
|
||||
hide-delimiter-background show-arrows>
|
||||
<v-carousel-item>
|
||||
<v-sheet class="v-center" height="100%">
|
||||
<v-window continuous v-model="loginMode" show-arrows="hover">
|
||||
<v-window-item>
|
||||
<v-sheet class="v-center d-flex align-center justify-center ma-2" height="100%">
|
||||
<LoginForm></LoginForm>
|
||||
</v-sheet>
|
||||
</v-carousel-item>
|
||||
<v-carousel-item>
|
||||
<v-sheet class="v-center" height="100%">
|
||||
</v-window-item>
|
||||
<v-window-item>
|
||||
<v-sheet class="v-center d-flex align-center justify-center ma-2" height="100%">
|
||||
<RegisterForm v-model="loginMode"></RegisterForm>
|
||||
</v-sheet>
|
||||
</v-carousel-item>
|
||||
</v-carousel></v-sheet>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</v-sheet>
|
||||
<UserPanel v-else></UserPanel>
|
||||
</template>
|
||||
|
||||
|
@ -1,7 +0,0 @@
|
||||
<template>
|
||||
点左边登录。
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
//
|
||||
</script>
|
@ -1,157 +0,0 @@
|
||||
<template>
|
||||
<v-container class="fill-height">
|
||||
<v-responsive
|
||||
class="align-centerfill-height mx-auto"
|
||||
max-width="900"
|
||||
>
|
||||
<v-img
|
||||
class="mb-4"
|
||||
height="150"
|
||||
src="@/assets/logo.png"
|
||||
/>
|
||||
|
||||
<div class="text-center">
|
||||
<div class="text-body-2 font-weight-light mb-n1">Welcome to</div>
|
||||
|
||||
<h1 class="text-h2 font-weight-bold">Vuetify 2</h1>
|
||||
</div>
|
||||
|
||||
<div class="py-4"></div>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-card
|
||||
class="py-4"
|
||||
color="surface-variant"
|
||||
image="https://cdn.vuetifyjs.com/docs/images/one/create/feature.png"
|
||||
prepend-icon="mdi-rocket-launch-outline"
|
||||
rounded="lg"
|
||||
variant="outlined"
|
||||
>
|
||||
<template #image>
|
||||
<v-img position="top right" />
|
||||
</template>
|
||||
|
||||
<template #title>
|
||||
<h2 class="text-h5 font-weight-bold">Get started</h2>
|
||||
</template>
|
||||
|
||||
<template #subtitle>
|
||||
<div class="text-subtitle-1">
|
||||
Replace this page by removing <v-kbd>{{ `<HelloWorld />` }}</v-kbd> in <v-kbd>pages/index.vue</v-kbd>.
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<v-overlay
|
||||
opacity=".12"
|
||||
scrim="primary"
|
||||
contained
|
||||
model-value
|
||||
persistent
|
||||
/>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="6">
|
||||
<v-card
|
||||
append-icon="mdi-open-in-new"
|
||||
class="py-4"
|
||||
color="surface-variant"
|
||||
href="https://vuetifyjs.com/"
|
||||
prepend-icon="mdi-text-box-outline"
|
||||
rel="noopener noreferrer"
|
||||
rounded="lg"
|
||||
subtitle="Learn about all things Vuetify in our documentation."
|
||||
target="_blank"
|
||||
title="Documentation"
|
||||
variant="text"
|
||||
>
|
||||
<v-overlay
|
||||
opacity=".06"
|
||||
scrim="primary"
|
||||
contained
|
||||
model-value
|
||||
persistent
|
||||
/>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="6">
|
||||
<v-card
|
||||
append-icon="mdi-open-in-new"
|
||||
class="py-4"
|
||||
color="surface-variant"
|
||||
href="https://vuetifyjs.com/introduction/why-vuetify/#feature-guides"
|
||||
prepend-icon="mdi-star-circle-outline"
|
||||
rel="noopener noreferrer"
|
||||
rounded="lg"
|
||||
subtitle="Explore available framework Features."
|
||||
target="_blank"
|
||||
title="Features"
|
||||
variant="text"
|
||||
>
|
||||
<v-overlay
|
||||
opacity=".06"
|
||||
scrim="primary"
|
||||
contained
|
||||
model-value
|
||||
persistent
|
||||
/>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="6">
|
||||
<v-card
|
||||
append-icon="mdi-open-in-new"
|
||||
class="py-4"
|
||||
color="surface-variant"
|
||||
href="https://vuetifyjs.com/components/all"
|
||||
prepend-icon="mdi-widgets-outline"
|
||||
rel="noopener noreferrer"
|
||||
rounded="lg"
|
||||
subtitle="Discover components in the API Explorer."
|
||||
target="_blank"
|
||||
title="Components"
|
||||
variant="text"
|
||||
>
|
||||
<v-overlay
|
||||
opacity=".06"
|
||||
scrim="primary"
|
||||
contained
|
||||
model-value
|
||||
persistent
|
||||
/>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="6">
|
||||
<v-card
|
||||
append-icon="mdi-open-in-new"
|
||||
class="py-4"
|
||||
color="surface-variant"
|
||||
href="https://discord.vuetifyjs.com"
|
||||
prepend-icon="mdi-account-group-outline"
|
||||
rel="noopener noreferrer"
|
||||
rounded="lg"
|
||||
subtitle="Connect with Vuetify developers."
|
||||
target="_blank"
|
||||
title="Community"
|
||||
variant="text"
|
||||
>
|
||||
<v-overlay
|
||||
opacity=".06"
|
||||
scrim="primary"
|
||||
contained
|
||||
model-value
|
||||
persistent
|
||||
/>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-responsive>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
//
|
||||
</script>
|
71
ui/src/components/Homepage.vue
Normal file
71
ui/src/components/Homepage.vue
Normal file
@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<v-container class="d-flex align-center justify-center fill-height">
|
||||
<!-- 欢迎信息卡片 -->
|
||||
<v-card class="pa-6" outlined elevation="10" style="border-radius: 16px; background-color: #ffffff;">
|
||||
<v-card-title class="headline text-center" style="color: #333;">欢迎使用 KQM Math</v-card-title>
|
||||
<v-card-text class="text-center" style="color: #555;">
|
||||
KQM Math 是一个高效的数学口算系统,帮助你快速解决数学表达式的计算和验证问题。通过简洁的界面,提供准确且快速的计算服务。
|
||||
</v-card-text>
|
||||
<v-card-actions class="justify-center">
|
||||
<v-btn color="deep-purple accent-4" @click="theDialog" large depressed rounded class="white--text">
|
||||
点击左上角 <v-icon icon="mdi-menu"></v-icon> 开始使用!
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-container>
|
||||
<v-dialog v-model="dialogShow" width="auto">
|
||||
<v-card max-width="400" prepend-icon="mdi-update" :text="dialogText" :title="dialogTitle">
|
||||
<template v-slot:actions>
|
||||
<v-btn class="ms-auto" text="Ok" @click="dialogShow = false, dialogClose()"></v-btn>
|
||||
</template>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
|
||||
const dialogShow = ref(false);
|
||||
const dialogTitle = ref('');
|
||||
const dialogText = ref('');
|
||||
const dialogClose = ref(() => { });
|
||||
|
||||
const dialog = (title: string, text: string) => {
|
||||
dialogTitle.value = title;
|
||||
dialogText.value = text;
|
||||
dialogShow.value = true;
|
||||
return new Promise(res => {
|
||||
dialogClose.value = res as () => void;
|
||||
});
|
||||
};
|
||||
|
||||
const theDialog = () => {
|
||||
dialog('提示', '请点左上角。');
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-card {
|
||||
border-radius: 16px;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.v-card-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.v-card-text {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.v-btn {
|
||||
width: auto;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.v-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
</style>
|
80
ui/src/components/Problems.vue
Normal file
80
ui/src/components/Problems.vue
Normal file
@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<v-container class="fill-height">
|
||||
<v-responsive class="align-centerfill-height mx-auto" max-width="900">
|
||||
<v-img class="mb-4" height="150" src="@/assets/logo.png" />
|
||||
|
||||
<div class="text-center">
|
||||
<div class="text-body-2 font-weight-light mb-n1">Welcome to</div>
|
||||
|
||||
<h1 class="text-h2 font-weight-bold">Vuetify 2</h1>
|
||||
</div>
|
||||
|
||||
<div class="py-4"></div>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-card class="py-4" color="surface-variant"
|
||||
image="https://cdn.vuetifyjs.com/docs/images/one/create/feature.png"
|
||||
prepend-icon="mdi-rocket-launch-outline" rounded="lg" variant="outlined">
|
||||
<template #image>
|
||||
<v-img position="top right" />
|
||||
</template>
|
||||
|
||||
<template #title>
|
||||
<h2 class="text-h5 font-weight-bold">Get started</h2>
|
||||
</template>
|
||||
|
||||
<template #subtitle>
|
||||
<div class="text-subtitle-1">
|
||||
Replace this page by removing <v-kbd>{{ `
|
||||
<HelloWorld />` }}
|
||||
</v-kbd> in <v-kbd>pages/index.vue</v-kbd>.
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<v-overlay opacity=".12" scrim="primary" contained model-value persistent />
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="6">
|
||||
<v-card append-icon="mdi-open-in-new" class="py-4" color="surface-variant" href="https://vuetifyjs.com/"
|
||||
prepend-icon="mdi-text-box-outline" rel="noopener noreferrer" rounded="lg"
|
||||
subtitle="Learn about all things Vuetify in our documentation." target="_blank" title="Documentation"
|
||||
variant="text">
|
||||
<v-overlay opacity=".06" scrim="primary" contained model-value persistent />
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="6">
|
||||
<v-card append-icon="mdi-open-in-new" class="py-4" color="surface-variant"
|
||||
href="https://vuetifyjs.com/introduction/why-vuetify/#feature-guides" prepend-icon="mdi-star-circle-outline"
|
||||
rel="noopener noreferrer" rounded="lg" subtitle="Explore available framework Features." target="_blank"
|
||||
title="Features" variant="text">
|
||||
<v-overlay opacity=".06" scrim="primary" contained model-value persistent />
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="6">
|
||||
<v-card append-icon="mdi-open-in-new" class="py-4" color="surface-variant"
|
||||
href="https://vuetifyjs.com/components/all" prepend-icon="mdi-widgets-outline" rel="noopener noreferrer"
|
||||
rounded="lg" subtitle="Discover components in the API Explorer." target="_blank" title="Components"
|
||||
variant="text">
|
||||
<v-overlay opacity=".06" scrim="primary" contained model-value persistent />
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="6">
|
||||
<v-card append-icon="mdi-open-in-new" class="py-4" color="surface-variant"
|
||||
href="https://discord.vuetifyjs.com" prepend-icon="mdi-account-group-outline" rel="noopener noreferrer"
|
||||
rounded="lg" subtitle="Connect with Vuetify developers." target="_blank" title="Community" variant="text">
|
||||
<v-overlay opacity=".06" scrim="primary" contained model-value persistent />
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-responsive>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
//
|
||||
</script>
|
290
ui/src/components/RecordList.vue
Normal file
290
ui/src/components/RecordList.vue
Normal file
@ -0,0 +1,290 @@
|
||||
<template>
|
||||
<v-data-table :loading="loading" :headers="(headers as any)" :items="records" v-model:search="search"
|
||||
:filter-keys="['', 'user_id', 'problem'][filterMode]" :custom-filter="filter"
|
||||
:sort-by="[{ key: 'id', order: 'desc' }]" multi-sort items-per-page-text="每页" no-data-text="暂时没有提交记录">
|
||||
<template v-slot:loading>
|
||||
<v-skeleton-loader></v-skeleton-loader>
|
||||
</template>
|
||||
<template v-slot:top>
|
||||
<v-toolbar flat>
|
||||
<v-toolbar-title>所有提交记录</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-text-field v-model="search" density="compact" label="过滤" prepend-inner-icon="mdi-magnify"
|
||||
variant="solo-filled" flat hide-details single-line :append-inner-icon="filterIcon"
|
||||
@click:append-inner="++filterMode, filterMode %= 3">
|
||||
<v-tooltip activator="parent" location="bottom">
|
||||
当前为{{ ['不', '按用户 ID ', '按题号'][filterMode] }}过滤,点击右侧
|
||||
<v-icon :icon="filterIcon"></v-icon>
|
||||
切换模式。
|
||||
</v-tooltip>
|
||||
</v-text-field>
|
||||
<v-btn @click="refresh" class="mb-2" color="primary" dark>
|
||||
刷新
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
</template>
|
||||
<template v-slot:item="{ item }">
|
||||
<tr>
|
||||
<td>{{ item.id }}</td>
|
||||
<td>{{ item.user_id }}</td>
|
||||
<td>{{ item.problem }}</td>
|
||||
<td>
|
||||
<v-chip label :color="statusColor(item.status)" variant="flat">
|
||||
{{ item.status.toUpperCase() }}
|
||||
</v-chip>
|
||||
</td>
|
||||
<td>{{ decorateAnswer(item.user_id, item.answer) }}</td>
|
||||
<td>{{ convertTime(item.timestamp) }}</td>
|
||||
</tr>
|
||||
</template>
|
||||
</v-data-table>
|
||||
<v-dialog v-model="dialogShow" width="auto">
|
||||
<v-card max-width="400" prepend-icon="mdi-update" :text="dialogText" :title="dialogTitle">
|
||||
<template v-slot:actions>
|
||||
<v-btn class="ms-auto" text="Ok" @click="dialogShow = false"></v-btn>
|
||||
</template>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useAuthStore } from '@/store/auth';
|
||||
import axios, { Axios, AxiosError } from 'axios';
|
||||
import { computed, nextTick, onMounted, ref, watch } from 'vue';
|
||||
import { jwtDecode, type JwtPayload } from 'jwt-decode';
|
||||
|
||||
const search = ref('');
|
||||
const filterMode = ref(0);
|
||||
const filterIcon = computed(() => {
|
||||
if (filterMode.value==1) {
|
||||
return 'mdi-account';
|
||||
} else if (filterMode.value==2){
|
||||
return 'mdi-brain';
|
||||
} else {
|
||||
return 'mdi-cancel';
|
||||
}
|
||||
});
|
||||
const filter = (value: string, query: string, item?: any) => {
|
||||
if (filterMode.value != 0) {
|
||||
return item.columns[['', 'user_id', 'problem'][filterMode.value]] == query;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const storedToken = ref(authStore.token);
|
||||
|
||||
interface KqmJwt extends JwtPayload {
|
||||
user_id: string;
|
||||
};
|
||||
|
||||
const userId = computed(() => {
|
||||
if (storedToken.value != '') {
|
||||
let data: KqmJwt = jwtDecode(storedToken.value);
|
||||
return data.user_id;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
type UserPermissionResponse = { success?: string, permission?: string, error?: string };
|
||||
|
||||
const queryPermission = async () => {
|
||||
try {
|
||||
const formData = new FormData;
|
||||
formData.append("user_id", userId.value);
|
||||
let res = await axios.post('/api/auth/permission', formData);
|
||||
return res.data as UserPermissionResponse;
|
||||
} catch (e) {
|
||||
let ex = e as AxiosError;
|
||||
return ex.response?.data as UserPermissionResponse;
|
||||
}
|
||||
}
|
||||
|
||||
const userPermission = ref('');
|
||||
|
||||
const updateUserPermission = async () => {
|
||||
let res = await queryPermission();
|
||||
if (res?.success) {
|
||||
userPermission.value = res.permission as string;
|
||||
} else {
|
||||
userPermission.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
watch(userId, updateUserPermission, { immediate: true });
|
||||
|
||||
const statusColor = (status: 'ac' | 'wa' | 'uke') => {
|
||||
switch (status) {
|
||||
case 'ac': return 'green';
|
||||
case 'wa': return 'red';
|
||||
case 'uke': return 'black';
|
||||
}
|
||||
}
|
||||
|
||||
const decorateAnswer = (user_id: string, answer: string) => {
|
||||
if (userPermission.value == '1') {
|
||||
return answer;
|
||||
} else if (userPermission.value == '2' && userId.value == user_id) {
|
||||
return answer;
|
||||
} else if (!authStore.isAuthenticated) {
|
||||
return '登录后查看';
|
||||
} else {
|
||||
return '权限不足';
|
||||
}
|
||||
}
|
||||
|
||||
const convertTime = (timestamp: number) => {
|
||||
let date = new Date(timestamp);
|
||||
let Y = date.getFullYear();
|
||||
let M = date.getMonth() + 1;
|
||||
let D = date.getDate();
|
||||
let H = date.getHours();
|
||||
let m = date.getMinutes();
|
||||
let s = date.getSeconds();
|
||||
return `${Y}-${M}-${D} ${H}:${m < 10 ? '0' : ''}${m}:${s}`;
|
||||
}
|
||||
|
||||
const loading = ref(false);
|
||||
const refresh = async () => {
|
||||
loading.value = true;
|
||||
await initialize();
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const dialogShow = ref(false);
|
||||
const dialogTitle = ref('');
|
||||
const dialogText = ref('');
|
||||
|
||||
const dialog = (title: string, text: string) => {
|
||||
dialogTitle.value = title;
|
||||
dialogText.value = text;
|
||||
dialogShow.value = true;
|
||||
};
|
||||
|
||||
const headers = ref([
|
||||
{
|
||||
title: '记录 ID',
|
||||
align: 'center',
|
||||
key: 'id'
|
||||
},
|
||||
{
|
||||
title: '用户',
|
||||
align: 'center',
|
||||
key: 'user_id'
|
||||
},
|
||||
{
|
||||
title: '题号',
|
||||
key: 'problem',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '答案',
|
||||
key: 'answer',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '时间',
|
||||
key: 'timestamp',
|
||||
align: 'center'
|
||||
},
|
||||
]);
|
||||
|
||||
interface Record {
|
||||
id: number,
|
||||
user_id: string,
|
||||
problem: number,
|
||||
status: 'ac' | 'wa' | 'uke',
|
||||
answer: string,
|
||||
timestamp: number
|
||||
};
|
||||
|
||||
const records = ref<Record[]>([]);
|
||||
|
||||
const decorate = <T>(ex: AxiosError) => {
|
||||
if (ex.response?.data) {
|
||||
return ex.response?.data as T;
|
||||
} else {
|
||||
return { error: ex.message } as T;
|
||||
}
|
||||
}
|
||||
|
||||
type RecordCountResponse = { success?: string, result?: number, error?: string };
|
||||
|
||||
const requestRecordCount = async () => {
|
||||
try {
|
||||
const formData = new FormData;
|
||||
formData.append("action", "count");
|
||||
let res = await axios.post('/api/study/records', formData);
|
||||
return res.data as RecordCountResponse;
|
||||
} catch (e) {
|
||||
return decorate<RecordCountResponse>(e as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
type RecordData = {
|
||||
problem: number,
|
||||
user_id: string,
|
||||
data: {
|
||||
answer: string,
|
||||
status: "ac" | "wa" | "uke",
|
||||
timestamp: number
|
||||
}
|
||||
};
|
||||
|
||||
type QueryRecordResponse = {
|
||||
success?: string,
|
||||
error?: string,
|
||||
record?: RecordData
|
||||
};
|
||||
|
||||
const requestQueryRecord = async (id: number) => {
|
||||
try {
|
||||
const formData = new FormData;
|
||||
formData.append("action", "query");
|
||||
formData.append("id", `${id}`);
|
||||
let res = await axios.post('/api/study/records', formData);
|
||||
return res.data as QueryRecordResponse;
|
||||
} catch (e) {
|
||||
return decorate<QueryRecordResponse>(e as AxiosError);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const initialize = async () => {
|
||||
let res = await requestRecordCount();
|
||||
if (res?.error) {
|
||||
dialog('发生异常', res.error);
|
||||
return;
|
||||
}
|
||||
let cnt = res.result as number;
|
||||
records.value = [];
|
||||
for (let id = cnt; id >= 0; --id) {
|
||||
let resp = await requestQueryRecord(id);
|
||||
if (resp?.success) {
|
||||
let recordData = resp.record as RecordData;
|
||||
let record: Record = {
|
||||
id,
|
||||
user_id: recordData.user_id,
|
||||
problem: recordData.problem,
|
||||
status: recordData.data.status,
|
||||
answer: recordData.data.answer,
|
||||
timestamp: recordData.data.timestamp
|
||||
}
|
||||
records.value.push(record);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(initialize);
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
td {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
74
ui/src/components/Study.vue
Normal file
74
ui/src/components/Study.vue
Normal file
@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<v-sheet v-if="userPermission == '2'" class="d-flex align-center justify-center flex-wrap mx-auto px-4">
|
||||
<div style="margin: 5px;" v-for="(id, idx) in ids">
|
||||
<Problem v-model="ans[idx]" :id="id" :err="err[idx]" :submit="() => err[idx] = '' + Math.random()"></Problem>
|
||||
</div>
|
||||
</v-sheet>
|
||||
<ManagePanel v-else-if="userPermission == '1'"></ManagePanel>
|
||||
<div v-else>未知的权限,请联系管理员重置权限状态。</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from '@/store/auth';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import Problem from './study/Problem.vue';
|
||||
import { jwtDecode, type JwtPayload } from 'jwt-decode';
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import ManagePanel from './study/ManagePanel.vue';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const storedToken = ref(authStore.token);
|
||||
|
||||
const ids = ref([20,21,22]);
|
||||
const ans = ref<number[]>([]);
|
||||
const err = ref<string[]>([]);
|
||||
|
||||
interface KqmJwt extends JwtPayload {
|
||||
user_id: string;
|
||||
};
|
||||
|
||||
const userId = computed(() => {
|
||||
if (storedToken.value != '') {
|
||||
let data: KqmJwt = jwtDecode(storedToken.value);
|
||||
return data.user_id;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
type UserPermissionResponse = { success?: string, permission?: string, error?: string };
|
||||
|
||||
const queryPermission = async () => {
|
||||
try {
|
||||
const formData = new FormData;
|
||||
formData.append("user_id", userId.value);
|
||||
let res = await axios.post('/api/auth/permission', formData);
|
||||
return res.data as UserPermissionResponse;
|
||||
} catch (e) {
|
||||
let ex = e as AxiosError;
|
||||
return ex.response?.data as UserPermissionResponse;
|
||||
}
|
||||
}
|
||||
|
||||
const userPermission = ref('');
|
||||
|
||||
const updateUserPermission = async () => {
|
||||
let res = await queryPermission();
|
||||
if (res?.success) {
|
||||
userPermission.value = res.permission as string;
|
||||
} else {
|
||||
userPermission.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
watch(userId, updateUserPermission, { immediate: true });
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
@ -1,80 +0,0 @@
|
||||
<template>
|
||||
|
||||
<v-row align="center" justify="center" dense>
|
||||
<v-col cols="12" md="6">
|
||||
<v-card append-icon="mdi-check" class="mx-auto" prepend-icon="mdi-account" subtitle="占位符。占位符。" title="占位符">
|
||||
<v-card-text>占位符。占位符。占位符。占位符。</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-card class="mx-auto" subtitle="占位符。占位符。" title="占位符">
|
||||
<template v-slot:prepend>
|
||||
<v-icon color="primary" icon="mdi-account"></v-icon>
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<v-icon color="success" icon="mdi-check"></v-icon>
|
||||
</template>
|
||||
<v-card-text>占位符。占位符。占位符。占位符。</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-card append-avatar="https://cdn.vuetifyjs.com/images/john.jpg" class="mx-auto"
|
||||
prepend-avatar="https://cdn.vuetifyjs.com/images/logos/v-alt.svg" subtitle="占位符。占位符。" title="占位符">
|
||||
<v-card-text>占位符。占位符。占位符。占位符。</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-card class="mx-auto" subtitle="占位符。占位符。" title="占位符">
|
||||
<template v-slot:prepend>
|
||||
<v-avatar color="blue-darken-2">
|
||||
<v-icon icon="mdi-alarm"></v-icon>
|
||||
</v-avatar>
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<v-avatar size="24">
|
||||
<v-img alt="John" src="https://file.aiquickdraw.com/inpaint/1735136797_9819bf1a-7f1d-4a32-be6a-f5f7f3977b71.png"></v-img>
|
||||
</v-avatar>
|
||||
</template>
|
||||
<v-card-text>占位符。占位符。占位符。占位符。</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-divider :thickness="10" class="border-opacity-0"></v-divider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
const labels = ref([
|
||||
'12am',
|
||||
'3am',
|
||||
'6am',
|
||||
'9am',
|
||||
'12pm',
|
||||
'3pm',
|
||||
'6pm',
|
||||
'9pm',
|
||||
])
|
||||
|
||||
const value = ref([
|
||||
200,
|
||||
675,
|
||||
410,
|
||||
390,
|
||||
310,
|
||||
460,
|
||||
250,
|
||||
240,
|
||||
]);
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-sheet--offset {
|
||||
top: -24px;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-card subtitle="占位符占位符占位符" title="管理员面板" max-width="80%">
|
||||
<v-card title="管理员面板" max-width="80%">
|
||||
<v-card-item>
|
||||
<v-chip class="ma-2" color="pink" label>
|
||||
<v-icon icon="mdi-human" start></v-icon>
|
||||
@ -22,7 +22,7 @@
|
||||
修改指定用户的身份
|
||||
</v-chip>
|
||||
<v-chip class="chips" color="red" @click="dialogDeleteAccountShow = true">
|
||||
删除指定用户的权限
|
||||
删除指定用户的账号
|
||||
</v-chip>
|
||||
</v-card-item>
|
||||
</v-card>
|
||||
|
@ -112,7 +112,7 @@ const requestDeleteAccount = async (userId: string) => {
|
||||
let ex = e as AxiosError;
|
||||
if (ex.response?.data) {
|
||||
return ex.response?.data as DeleteAccountResponse;
|
||||
} {
|
||||
} else {
|
||||
return { error: ex.message };
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ const login = async () => {
|
||||
let ex = e as AxiosError;
|
||||
if (ex.response?.data) {
|
||||
return ex.response?.data as LoginResponse;
|
||||
} {
|
||||
} else {
|
||||
return { error: ex.message };
|
||||
}
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ const requestPermission = async () => {
|
||||
let ex = e as AxiosError;
|
||||
if (ex.response?.data) {
|
||||
return ex.response?.data as PermissionResponse;
|
||||
} {
|
||||
} else {
|
||||
return { error: ex.message };
|
||||
}
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ const requestRepasswd = async () => {
|
||||
let ex = e as AxiosError;
|
||||
if (ex.response?.data) {
|
||||
return ex.response?.data as RepasswdResponse;
|
||||
} {
|
||||
} else {
|
||||
return { error: ex.message };
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ const requestRepasswd = async () => {
|
||||
let ex = e as AxiosError;
|
||||
if (ex.response?.data) {
|
||||
return ex.response?.data as RepasswdResponse;
|
||||
} {
|
||||
} else {
|
||||
return { error: ex.message };
|
||||
}
|
||||
}
|
||||
@ -93,7 +93,7 @@ const repasswd = async () => {
|
||||
await dialog('错误', `修改密码失败:${res.error}`);
|
||||
} else {
|
||||
await dialog('信息', `修改密码成功,请重新登录。`);
|
||||
authStore.clearToken();
|
||||
authStore.clearAdminToken();
|
||||
}
|
||||
}
|
||||
|
||||
|
49
ui/src/components/study/ManagePanel.vue
Normal file
49
ui/src/components/study/ManagePanel.vue
Normal file
@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<v-window v-model="onboarding" show-arrows="hover">
|
||||
<v-window-item>
|
||||
<v-card class="d-flex align-center justify-center ma-2" elevation="2">
|
||||
<!-- <ProblemDialog></ProblemDialog> -->
|
||||
<ProblemList></ProblemList>
|
||||
</v-card>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
<v-dialog v-model="dialogShow" width="auto">
|
||||
<v-card max-width="400" prepend-icon="mdi-update" :text="dialogText" :title="dialogTitle">
|
||||
<template v-slot:actions>
|
||||
<v-btn class="ms-auto" text="Ok" @click="dialogShow = false"></v-btn>
|
||||
</template>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from '@/store/auth';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import ProblemList from './ProblemList.vue';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const storedToken = ref(authStore.token);
|
||||
|
||||
const dialogShow = ref(false);
|
||||
const dialogTitle = ref('');
|
||||
const dialogText = ref('');
|
||||
|
||||
const dialog = (title: string, text: string) => {
|
||||
dialogTitle.value = title;
|
||||
dialogText.value = text;
|
||||
dialogShow.value = true;
|
||||
};
|
||||
|
||||
const onboarding = ref(0);
|
||||
const length = ref(3);
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
142
ui/src/components/study/Problem.vue
Normal file
142
ui/src/components/study/Problem.vue
Normal file
@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<v-card class="mx-auto" max-width="344">
|
||||
<v-card-text>
|
||||
<div>{{ title }}</div>
|
||||
|
||||
<p class="text-h4 font-weight-black">{{ main }}</p>
|
||||
|
||||
<p>{{ info }}</p>
|
||||
|
||||
<div class="text-medium-emphasis">
|
||||
{{ specification }}
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
<v-text-field v-model="ans" prepend-inner-icon="mdi-numeric" label="你的答案" :rules="notEmptyRules"></v-text-field>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="deep-purple-accent-4" text="提交" variant="text" @click="submit"></v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
<v-dialog v-model="dialogShow" width="auto">
|
||||
<v-card max-width="400" prepend-icon="mdi-update" :text="dialogText" :title="dialogTitle">
|
||||
<template v-slot:actions>
|
||||
<v-btn class="ms-auto" text="Ok" @click="dialogShow = false"></v-btn>
|
||||
</template>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from '@/store/auth';
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
|
||||
const dialogShow = ref(false);
|
||||
const dialogTitle = ref('');
|
||||
const dialogText = ref('');
|
||||
|
||||
const dialog = (title: string, text: string) => {
|
||||
dialogTitle.value = title;
|
||||
dialogText.value = text;
|
||||
dialogShow.value = true;
|
||||
};
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const storedToken = ref(authStore.token);
|
||||
|
||||
const { id } = defineProps<{ id: number }>();
|
||||
|
||||
const title = ref('');
|
||||
const main = ref('');
|
||||
const info = ref('');
|
||||
const specification = ref('');
|
||||
|
||||
const ans = defineModel({ required: true });
|
||||
|
||||
type ProblemResponse = { success?: string, problem?: string, check_error?: number, error?: string };
|
||||
|
||||
const requestQueryProblem = async () => {
|
||||
try {
|
||||
const formData = new FormData;
|
||||
formData.append("action", "query");
|
||||
formData.append("token", storedToken.value);
|
||||
formData.append("id", `${id}`);
|
||||
let res = await axios.post('/api/study/problems', formData);
|
||||
return res.data as ProblemResponse;
|
||||
} catch (e) {
|
||||
let ex = e as AxiosError;
|
||||
if (ex.response?.data) {
|
||||
return ex.response?.data as ProblemResponse;
|
||||
} else {
|
||||
return { error: ex.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type CheckProblemResponse = { success?: string, result?: boolean, error?: string };
|
||||
|
||||
const requestCheckProblem = async () => {
|
||||
try {
|
||||
const formData = new FormData;
|
||||
formData.append("action", "check");
|
||||
formData.append("token", storedToken.value);
|
||||
formData.append("id", `${id}`);
|
||||
formData.append("answer", `${ans.value}`);
|
||||
let res = await axios.post('/api/study/problems', formData);
|
||||
return res.data as CheckProblemResponse;
|
||||
} catch (e) {
|
||||
let ex = e as AxiosError;
|
||||
if (ex.response?.data) {
|
||||
return ex.response?.data as CheckProblemResponse;
|
||||
} else {
|
||||
return { error: ex.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const submit = async () => {
|
||||
if (ans.value == '') {
|
||||
return;
|
||||
}
|
||||
let res = await requestCheckProblem();
|
||||
if (res?.error) {
|
||||
dialog('发生异常', res.error);
|
||||
} else {
|
||||
dialog('提交成功', res.result ? '并且答案正确。' : '但是答案错误。')
|
||||
}
|
||||
};
|
||||
|
||||
const notEmptyRules: any = [(value: string) => {
|
||||
if (value != '') {
|
||||
return true;
|
||||
} else {
|
||||
return '不能为空';
|
||||
}
|
||||
}];
|
||||
|
||||
onMounted(async () => {
|
||||
let res = await requestQueryProblem();
|
||||
if (res?.error) {
|
||||
title.value = `题目 ${id}`;
|
||||
main.value = '未知题目';
|
||||
info.value = `错误信息:'${res.error}'`;
|
||||
specification.value = `未知题目 (ID = ${id}),题号有误,请联系老师或管理员处理。`;
|
||||
} else {
|
||||
title.value = `题目 ${id}`;
|
||||
main.value = `${res.problem} = ?`;
|
||||
info.value = res.check_error == 0 ? '精确匹配' : `误差不超过 ${res.check_error}`;
|
||||
specification.value = '对于精确匹配的题目,你的答案必须和预设的标准答案完全一致;对于按误差匹配的题目,只要你的答案与标准答案的差值的绝对值不超过误差即可。';
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<v-data-table :loading="loading" :headers="(headers as any)" :items="problems"
|
||||
:sort-by="[{ key: 'id', order: 'asc' }, { key: 'problem', order: 'asc' }]" multi-sort items-per-page-text="每页">
|
||||
:sort-by="[{ key: 'id', order: 'asc' }]" multi-sort items-per-page-text="每页" no-data-text="题库里面没有题目">
|
||||
<template v-slot:loading>
|
||||
<v-skeleton-loader></v-skeleton-loader>
|
||||
</template>
|
||||
@ -11,7 +11,7 @@
|
||||
刷新
|
||||
</v-btn>
|
||||
<v-dialog v-model="dialog0" max-width="500px">
|
||||
<template v-slot:activator="{ props }">
|
||||
<template v-if="userPermission == '1'" v-slot:activator="{ props }">
|
||||
<v-btn class="mb-2" color="primary" dark v-bind="props">
|
||||
新建题目
|
||||
</v-btn>
|
||||
@ -30,12 +30,12 @@
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field v-model="editedItem.answer" prepend-inner-icon="mdi-numeric" label="答案"
|
||||
<v-text-field v-model="editedItem.answer" prepend-inner-icon="mdi-numeric" :label="answerLabel"
|
||||
append-inner-icon="mdi-auto-fix" @click:append-inner="generateAnswer" :rules="notEmptyRules">
|
||||
<v-tooltip activator="parent" location="bottom">
|
||||
点击右侧 <v-icon icon="mdi-auto-fix"></v-icon> 自动生成答案的{{ useFraction ? '带循环节' : '舍入' }}表示
|
||||
点击右侧 <v-icon icon="mdi-auto-fix"></v-icon> 自动生成答案的{{ useFraction ? '有理数' : '舍入' }}表示
|
||||
<br />
|
||||
再次点击还能生成其{{ useFraction ? '舍入' : '带循环节' }}表示。
|
||||
再次点击还能生成其{{ useFraction ? '舍入' : '有理数' }}表示。
|
||||
</v-tooltip>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
@ -82,18 +82,27 @@
|
||||
</v-dialog>
|
||||
</v-toolbar>
|
||||
</template>
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<v-icon class="me-2" size="small" @click="editItem(item)">
|
||||
mdi-pencil
|
||||
</v-icon>
|
||||
<v-icon size="small" @click="deleteItem(item)">
|
||||
mdi-delete
|
||||
</v-icon>
|
||||
</template>
|
||||
<template v-slot:no-data>
|
||||
<v-btn color="primary" @click="initialize">
|
||||
Reset
|
||||
</v-btn>
|
||||
<template v-slot:item="{ item }">
|
||||
<tr>
|
||||
<td>{{ item.id }}</td>
|
||||
<td>{{ item.problem }}</td>
|
||||
<td v-if="userPermission == '1'">{{ item.answer }}</td>
|
||||
<td>{{ decorateCheckingError(item.check_error) }}</td>
|
||||
<td class="text-center">
|
||||
<div v-if="userPermission == '1'">
|
||||
<v-icon class="me-2" size="small" @click="editItem(item)">
|
||||
mdi-pencil
|
||||
</v-icon>
|
||||
<v-icon size="small" @click="deleteItem(item)">
|
||||
mdi-delete
|
||||
</v-icon>
|
||||
</div>
|
||||
<v-chip v-else class="chips" :color="colorsCache[item.id]"
|
||||
@click="problemId = item.id, dialogProblemShow = true">
|
||||
答题
|
||||
</v-chip>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</v-data-table>
|
||||
<v-dialog v-model="dialogShow" width="auto">
|
||||
@ -103,12 +112,68 @@
|
||||
</template>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<v-dialog v-model="dialogProblemShow" width="auto">
|
||||
<v-card max-width="400">
|
||||
<Problem v-model="answer" :id="problemId"></Problem>
|
||||
<!-- <template v-slot:actions>
|
||||
<v-btn class="ms-auto" text="Ok" @click="dialogProblemShow = false"></v-btn>
|
||||
</template> -->
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useAuthStore } from '@/store/auth';
|
||||
import axios, { Axios, AxiosError } from 'axios';
|
||||
import { computed, nextTick, onMounted, ref, watch } from 'vue';
|
||||
import * as mathjs from 'mathjs';
|
||||
import { jwtDecode, type JwtPayload } from 'jwt-decode';
|
||||
import Problem from './Problem.vue';
|
||||
|
||||
const dialogProblemShow = ref(false);
|
||||
const answer = ref('');
|
||||
const problemId = ref(0);
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const storedToken = ref(authStore.token);
|
||||
|
||||
interface KqmJwt extends JwtPayload {
|
||||
user_id: string;
|
||||
};
|
||||
|
||||
const userId = computed(() => {
|
||||
if (storedToken.value != '') {
|
||||
let data: KqmJwt = jwtDecode(storedToken.value);
|
||||
return data.user_id;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
type UserPermissionResponse = { success?: string, permission?: string, error?: string };
|
||||
|
||||
const queryPermission = async () => {
|
||||
try {
|
||||
const formData = new FormData;
|
||||
formData.append("user_id", userId.value);
|
||||
let res = await axios.post('/api/auth/permission', formData);
|
||||
return res.data as UserPermissionResponse;
|
||||
} catch (e) {
|
||||
let ex = e as AxiosError;
|
||||
return ex.response?.data as UserPermissionResponse;
|
||||
}
|
||||
}
|
||||
|
||||
const userPermission = ref('');
|
||||
|
||||
const updateUserPermission = async () => {
|
||||
let res = await queryPermission();
|
||||
if (res?.success) {
|
||||
userPermission.value = res.permission as string;
|
||||
} else {
|
||||
userPermission.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
watch(userId, updateUserPermission, { immediate: true });
|
||||
|
||||
const loading = ref(false);
|
||||
const refresh = async () => {
|
||||
@ -129,36 +194,72 @@ const dialog = (title: string, text: string) => {
|
||||
|
||||
const dialog0 = ref(false);
|
||||
const dialogDelete = ref(false);
|
||||
const headers = ref([
|
||||
{
|
||||
title: '题号',
|
||||
align: 'center',
|
||||
key: 'id'
|
||||
},
|
||||
{
|
||||
title: '题面',
|
||||
align: 'center',
|
||||
key: 'problem'
|
||||
},
|
||||
{
|
||||
title: '答案',
|
||||
key: 'answer',
|
||||
align: 'center',
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
title: '误差',
|
||||
key: 'check_error',
|
||||
align: 'center',
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
align: 'center',
|
||||
sortable: false
|
||||
},
|
||||
]);
|
||||
// const headers = ref([
|
||||
// {
|
||||
// title: '题号',
|
||||
// align: 'center',
|
||||
// key: 'id'
|
||||
// },
|
||||
// {
|
||||
// title: '题面',
|
||||
// align: 'center',
|
||||
// key: 'problem'
|
||||
// },
|
||||
// {
|
||||
// title: '答案',
|
||||
// key: 'answer',
|
||||
// align: 'center',
|
||||
// sortable: false
|
||||
// },
|
||||
// {
|
||||
// title: '误差',
|
||||
// key: 'check_error',
|
||||
// align: 'center',
|
||||
// sortable: false
|
||||
// },
|
||||
// {
|
||||
// title: '操作',
|
||||
// key: 'actions',
|
||||
// align: 'center',
|
||||
// sortable: false
|
||||
// },
|
||||
// ]);
|
||||
const headers = computed(() => {
|
||||
let result = [
|
||||
{
|
||||
title: '题号',
|
||||
align: 'center',
|
||||
key: 'id'
|
||||
},
|
||||
{
|
||||
title: '题面',
|
||||
align: 'center',
|
||||
key: 'problem'
|
||||
},
|
||||
{
|
||||
title: '答案',
|
||||
key: 'answer',
|
||||
align: 'center',
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
title: '误差',
|
||||
key: 'check_error',
|
||||
align: 'center',
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
align: 'center',
|
||||
sortable: false
|
||||
},
|
||||
];
|
||||
if (userPermission.value == '2') {
|
||||
result.splice(2, 1);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
const problems = ref<{ id: number, problem: string, answer: string, check_error: number }[]>([]);
|
||||
const editedIndex = ref(-1);
|
||||
@ -180,6 +281,13 @@ const checkingErrorTitle = ref('');
|
||||
const checkingErrorIcon = ref('');
|
||||
const checkingError = ref('');
|
||||
|
||||
const decorateCheckingError = (checkError: number) => {
|
||||
if (checkError == 0) {
|
||||
return '精确匹配';
|
||||
}
|
||||
return checkError;
|
||||
}
|
||||
|
||||
watch(preciseMode, () => {
|
||||
if (preciseMode.value) {
|
||||
checkingError.value = '精确匹配';
|
||||
@ -221,13 +329,25 @@ const mathFraction = mathjs.create(mathjs.all, {
|
||||
});
|
||||
|
||||
let useFraction = false;
|
||||
const answerLabel = ref('答案');
|
||||
|
||||
watch(editedItem, (newValue, oldValue) => {
|
||||
if (newValue.answer != oldValue.answer) {
|
||||
answerLabel.value = '答案';
|
||||
}
|
||||
});
|
||||
|
||||
let timeout: number = setTimeout(() => { }, 0);
|
||||
const generateAnswer = () => {
|
||||
try {
|
||||
editedItem.value.answer = (useFraction ? mathFraction : mathBigNumber).evaluate(editedItem.value.problem);
|
||||
useFraction = !useFraction;
|
||||
} catch (e) {
|
||||
dialog('发生异常', `${e}`);
|
||||
editedItem.value.answer = '';
|
||||
}
|
||||
answerLabel.value = `答案(${useFraction ? '有理数' : '舍入'}表示)`;
|
||||
useFraction = !useFraction;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => answerLabel.value = '答案', 1000);
|
||||
};
|
||||
|
||||
const formTitle = computed(() => {
|
||||
@ -241,13 +361,10 @@ watch(dialogDelete, (val) => {
|
||||
val || closeDelete();
|
||||
});
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const storedToken = ref(authStore.token);
|
||||
|
||||
const decorate = <T>(ex: AxiosError) => {
|
||||
if (ex.response?.data) {
|
||||
return ex.response?.data as T;
|
||||
} {
|
||||
} else {
|
||||
return { error: ex.message } as T;
|
||||
}
|
||||
}
|
||||
@ -316,15 +433,56 @@ const requestDeleteProblem = async (id: number) => {
|
||||
}
|
||||
}
|
||||
|
||||
type HaveAcResponse = {
|
||||
success?: string,
|
||||
error?: string,
|
||||
result?: boolean
|
||||
};
|
||||
|
||||
const requestHaveAc = async (user_id: string, problem_id: number) => {
|
||||
try {
|
||||
const formData = new FormData;
|
||||
formData.append("action", "have");
|
||||
formData.append("user_id", `${user_id}`);
|
||||
formData.append("problem_id", `${problem_id}`);
|
||||
let res = await axios.post('/api/study/records', formData);
|
||||
return res.data as HaveAcResponse;
|
||||
} catch (e) {
|
||||
return decorate<HaveAcResponse>(e as AxiosError);
|
||||
}
|
||||
}
|
||||
|
||||
const calcColor = async (problem_id: number) => {
|
||||
let res = await requestHaveAc(userId.value, problem_id);
|
||||
if (res?.success && res.result) {
|
||||
return 'green';
|
||||
}
|
||||
return 'blue';
|
||||
}
|
||||
|
||||
const colorsCache = ref<any>({});
|
||||
|
||||
const model = defineModel();
|
||||
|
||||
const getAllProblem = async () => {
|
||||
if (model.value == undefined) {
|
||||
return await requestAllProblem();
|
||||
} else {
|
||||
return { success: 'success', result: model.value } as AllProblemResponse;
|
||||
}
|
||||
}
|
||||
|
||||
const initialize = async () => {
|
||||
let res = await requestAllProblem();
|
||||
let res = await getAllProblem();
|
||||
if (res?.error) {
|
||||
dialog('发生异常', res.error);
|
||||
return;
|
||||
}
|
||||
problems.value = [];
|
||||
let ids = res.result as number[];
|
||||
for (let id of ids) {
|
||||
colorsCache.value[id] = 'blue';
|
||||
}
|
||||
for (let i in ids) {
|
||||
let id = ids[i];
|
||||
let resp = await requestQueryProblem(id);
|
||||
@ -336,6 +494,9 @@ const initialize = async () => {
|
||||
}
|
||||
problems.value.push(pro);
|
||||
}
|
||||
for (let id of ids) {
|
||||
colorsCache.value[id] = await calcColor(id);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(initialize);
|
||||
@ -395,4 +556,9 @@ const save = async (event: SubmitEvent) => {
|
||||
close1();
|
||||
};
|
||||
|
||||
</script>
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
td {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
409
ui/src/components/study/SetList.vue
Normal file
409
ui/src/components/study/SetList.vue
Normal file
@ -0,0 +1,409 @@
|
||||
<template>
|
||||
<v-data-table :loading="loading" :headers="(headers as any)" :items="sets" :sort-by="[{ key: 'id', order: 'asc' }]"
|
||||
multi-sort items-per-page-text="每页" no-data-text="暂时没有题单">
|
||||
<template v-slot:loading>
|
||||
<v-skeleton-loader></v-skeleton-loader>
|
||||
</template>
|
||||
<template v-slot:top>
|
||||
<v-toolbar flat>
|
||||
<v-toolbar-title>所有题单</v-toolbar-title>
|
||||
<v-btn @click="refresh" class="mb-2" color="primary" dark>
|
||||
刷新
|
||||
</v-btn>
|
||||
<v-dialog v-model="dialog0" max-width="500px">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn class="mb-2" color="primary" dark v-bind="props">
|
||||
新建题单
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-form fast-fail @submit.prevent="save">
|
||||
<v-card class="mx-auto pa-12 pb-8" width="660" elevation="8" rounded="lg">
|
||||
<v-card-title>
|
||||
<span class="text-h5">{{ formTitle }}</span>
|
||||
</v-card-title>
|
||||
<v-container>
|
||||
<v-text-field v-model="editedItem.name" prepend-inner-icon="mdi-alphabetical" label="名称"
|
||||
:rules="notEmptyRules">
|
||||
</v-text-field>
|
||||
<v-combobox prepend-inner-icon="mdi-numeric" v-model="editedItem.problems" clearable chips multiple
|
||||
label="题号" :delimiters="[',', ' ', ';']" :rules="isProblemIds">
|
||||
</v-combobox>
|
||||
</v-container>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue-darken-1" variant="text" @click="close1">
|
||||
取消
|
||||
</v-btn>
|
||||
<v-btn color="blue-darken-1" variant="text" type="submit">
|
||||
保存
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-form>
|
||||
</v-dialog>
|
||||
<v-dialog v-model="dialogDelete" max-width="500px">
|
||||
<v-card>
|
||||
<v-card-title class="text-h5">再次确认</v-card-title>
|
||||
<v-card-text>确定要删除这个题单吗?这是不可逆操作。</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue-darken-1" variant="text" @click="closeDelete">放弃操作</v-btn>
|
||||
<v-btn color="blue-darken-1" variant="text" @click="deleteItemConfirm">确定删除</v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-toolbar>
|
||||
</template>
|
||||
<template v-slot:item="{ item }">
|
||||
<tr>
|
||||
<td>{{ item.id }}</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.problems.length }}</td>
|
||||
<td class="text-center">
|
||||
<div v-if="userPermission == '1'">
|
||||
<v-icon class="me-2" size="small" @click="editItem(item)">
|
||||
mdi-pencil
|
||||
</v-icon>
|
||||
<v-icon size="small" @click="deleteItem(item)">
|
||||
mdi-delete
|
||||
</v-icon>
|
||||
</div>
|
||||
<v-chip v-else class="chips" color="blue" @click="theProblems = item.problems, dialogProblemsShow = true">
|
||||
答题
|
||||
</v-chip>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</v-data-table>
|
||||
<v-dialog v-model="dialogShow" width="auto">
|
||||
<v-card max-width="400" prepend-icon="mdi-update" :text="dialogText" :title="dialogTitle">
|
||||
<template v-slot:actions>
|
||||
<v-btn class="ms-auto" text="Ok" @click="dialogShow = false"></v-btn>
|
||||
</template>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<v-dialog v-model="dialogProblemsShow" width="auto">
|
||||
<ProblemList v-model="theProblems"></ProblemList>
|
||||
</v-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useAuthStore } from '@/store/auth';
|
||||
import axios, { Axios, AxiosError } from 'axios';
|
||||
import { computed, nextTick, onMounted, ref, watch } from 'vue';
|
||||
import { jwtDecode, type JwtPayload } from 'jwt-decode';
|
||||
import ProblemList from './ProblemList.vue';
|
||||
|
||||
const theProblems = ref<number[]>([]);
|
||||
|
||||
const loading = ref(false);
|
||||
const refresh = async () => {
|
||||
loading.value = true;
|
||||
await initialize();
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
const dialogProblemsShow = ref(false);
|
||||
|
||||
const dialogShow = ref(false);
|
||||
const dialogTitle = ref('');
|
||||
const dialogText = ref('');
|
||||
|
||||
const dialog = (title: string, text: string) => {
|
||||
dialogTitle.value = title;
|
||||
dialogText.value = text;
|
||||
dialogShow.value = true;
|
||||
};
|
||||
|
||||
const dialog0 = ref(false);
|
||||
const dialogDelete = ref(false);
|
||||
const headers = ref([
|
||||
{
|
||||
title: '编号',
|
||||
align: 'center',
|
||||
key: 'id'
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
align: 'center',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '题数',
|
||||
key: 'problems',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
align: 'center',
|
||||
sortable: false
|
||||
},
|
||||
]);
|
||||
|
||||
type ProblemSet = { id: number, name: string, problems: string[], data: any };
|
||||
|
||||
const sets = ref<ProblemSet[]>([]);
|
||||
const editedIndex = ref(-1);
|
||||
const editedItem = ref<ProblemSet>({
|
||||
id: 0,
|
||||
name: '',
|
||||
problems: [],
|
||||
data: {}
|
||||
});
|
||||
const defaultItem = ref<ProblemSet>({
|
||||
id: 0,
|
||||
name: '',
|
||||
problems: [],
|
||||
data: {}
|
||||
});
|
||||
|
||||
const notEmptyRules = [(value: string) => {
|
||||
if (value == '') return '不能为空';
|
||||
return true;
|
||||
}];
|
||||
|
||||
const isProblemIds = [() => {
|
||||
for (let id of editedItem.value.problems) {
|
||||
let num = parseInt(id, 10);
|
||||
let flag = num > 0 && num.toString() == id;
|
||||
if (!flag) {
|
||||
return '必须全部是正整数。';
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}];
|
||||
|
||||
const formTitle = computed(() => {
|
||||
return editedIndex.value === -1 ? '新建题单' : `编辑题单 ${editedItem.value.id}`;
|
||||
});
|
||||
|
||||
watch(dialog0, (val) => {
|
||||
val || close1();
|
||||
});
|
||||
watch(dialogDelete, (val) => {
|
||||
val || closeDelete();
|
||||
});
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const storedToken = ref(authStore.token);
|
||||
|
||||
const decorate = <T>(ex: AxiosError) => {
|
||||
if (ex.response?.data) {
|
||||
return ex.response?.data as T;
|
||||
} else {
|
||||
return { error: ex.message } as T;
|
||||
}
|
||||
}
|
||||
|
||||
// type AllProblemResponse = { success?: string, result?: number[], error?: string };
|
||||
|
||||
// const requestAllProblem = async () => {
|
||||
// try {
|
||||
// const formData = new FormData;
|
||||
// formData.append("action", "all");
|
||||
// formData.append("token", storedToken.value);
|
||||
// let res = await axios.post('/api/study/problems', formData);
|
||||
// return res.data as AllProblemResponse;
|
||||
// } catch (e) {
|
||||
// return decorate<AllProblemResponse>(e as AxiosError);
|
||||
// }
|
||||
// };
|
||||
|
||||
type AllSetsResponse = { success?: string, sets?: number[], error?: string };
|
||||
|
||||
const requestAllSets = async () => {
|
||||
try {
|
||||
const formData = new FormData;
|
||||
formData.append("action", "all");
|
||||
formData.append("token", storedToken.value);
|
||||
let res = await axios.post('/api/study/sets', formData);
|
||||
return res.data as AllSetsResponse;
|
||||
} catch (e) {
|
||||
return decorate<AllSetsResponse>(e as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
type SetResponse = {
|
||||
success?: string,
|
||||
set?: {
|
||||
name: string,
|
||||
problems: number[],
|
||||
data: any
|
||||
},
|
||||
error?: string
|
||||
};
|
||||
|
||||
const requestQuerySet = async (id: number) => {
|
||||
try {
|
||||
const formData = new FormData;
|
||||
formData.append("action", "query");
|
||||
formData.append("token", storedToken.value);
|
||||
formData.append("id", `${id}`);
|
||||
let res = await axios.post('/api/study/sets', formData);
|
||||
return res.data as SetResponse;
|
||||
} catch (e) {
|
||||
return decorate<SetResponse>(e as AxiosError);
|
||||
}
|
||||
}
|
||||
|
||||
type AddSetResponse = { success?: string, id?: number, error?: string };
|
||||
|
||||
const requestAddSet = async () => {
|
||||
try {
|
||||
const formData = new FormData;
|
||||
formData.append("action", editedItem.value.id == 0 ? "add" : 'modify');
|
||||
formData.append("token", storedToken.value);
|
||||
if (editedItem.value.id != 0) {
|
||||
formData.append("id", `${editedItem.value.id}`);
|
||||
}
|
||||
formData.append("name", `${editedItem.value.name}`);
|
||||
formData.append("problems", `[${editedItem.value.problems}]`);
|
||||
formData.append("data", `{}`);
|
||||
let res = await axios.post('/api/study/sets', formData);
|
||||
return res.data as AddSetResponse;
|
||||
} catch (e) {
|
||||
return decorate<AddSetResponse>(e as AxiosError);
|
||||
}
|
||||
}
|
||||
|
||||
type DeleteSetResponse = { success?: string, error?: string };
|
||||
|
||||
const requestDeleteSet = async (id: number) => {
|
||||
try {
|
||||
const formData = new FormData;
|
||||
formData.append("action", 'delete');
|
||||
formData.append("token", storedToken.value);
|
||||
formData.append("id", `${id}`);
|
||||
let res = await axios.post('/api/study/sets', formData);
|
||||
return res.data as DeleteSetResponse;
|
||||
} catch (e) {
|
||||
return decorate<DeleteSetResponse>(e as AxiosError);
|
||||
}
|
||||
}
|
||||
|
||||
const initialize = async () => {
|
||||
let res = await requestAllSets();
|
||||
if (res?.error) {
|
||||
dialog('发生异常', res.error);
|
||||
return;
|
||||
}
|
||||
sets.value = [];
|
||||
let ids = res.sets as number[];
|
||||
for (let i in ids) {
|
||||
let id = ids[i];
|
||||
let resp = await requestQuerySet(id);
|
||||
if (resp?.success) {
|
||||
let pro = {
|
||||
id,
|
||||
name: resp.set?.name as string,
|
||||
problems: resp.set?.problems as any,
|
||||
data: {}
|
||||
};
|
||||
sets.value.push(pro);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(initialize);
|
||||
|
||||
const editItem = (item: any) => {
|
||||
editedIndex.value = sets.value.indexOf(item);
|
||||
editedItem.value = Object.assign({}, item);
|
||||
dialog0.value = true;
|
||||
};
|
||||
|
||||
const deleteItem = (item: any) => {
|
||||
editedIndex.value = sets.value.indexOf(item);
|
||||
editedItem.value = Object.assign({}, item);
|
||||
dialogDelete.value = true;
|
||||
};
|
||||
|
||||
const deleteItemConfirm = async () => {
|
||||
let res = await requestDeleteSet(editedItem.value.id);
|
||||
if (res?.error) {
|
||||
dialog('发生异常', res.error);
|
||||
return;
|
||||
}
|
||||
sets.value.splice(editedIndex.value, 1);
|
||||
closeDelete();
|
||||
};
|
||||
|
||||
const close1 = () => {
|
||||
dialog0.value = false;
|
||||
nextTick(() => {
|
||||
editedItem.value = Object.assign({}, defaultItem.value);
|
||||
editedIndex.value = -1;
|
||||
});
|
||||
};
|
||||
|
||||
const closeDelete = () => {
|
||||
dialogDelete.value = false;
|
||||
nextTick(() => {
|
||||
editedItem.value = Object.assign({}, defaultItem.value);
|
||||
editedIndex.value = -1;
|
||||
})
|
||||
};
|
||||
|
||||
const save = async (event: SubmitEvent) => {
|
||||
const results: any = await event;
|
||||
if (!results.valid) return;
|
||||
let res = await requestAddSet();
|
||||
if (res?.error) {
|
||||
dialog(`${editedIndex.value > -1 ? '修改' : '添加'}失败`, res.error);
|
||||
} else {
|
||||
if (editedIndex.value > -1) {
|
||||
Object.assign(sets.value[editedIndex.value], editedItem.value);
|
||||
} else {
|
||||
editedItem.value.id = res.id as number;
|
||||
sets.value.push(editedItem.value);
|
||||
}
|
||||
}
|
||||
close1();
|
||||
};
|
||||
|
||||
interface KqmJwt extends JwtPayload {
|
||||
user_id: string;
|
||||
};
|
||||
|
||||
const userId = computed(() => {
|
||||
if (storedToken.value != '') {
|
||||
let data: KqmJwt = jwtDecode(storedToken.value);
|
||||
return data.user_id;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
type UserPermissionResponse = { success?: string, permission?: string, error?: string };
|
||||
|
||||
const queryPermission = async () => {
|
||||
try {
|
||||
const formData = new FormData;
|
||||
formData.append("user_id", userId.value);
|
||||
let res = await axios.post('/api/auth/permission', formData);
|
||||
return res.data as UserPermissionResponse;
|
||||
} catch (e) {
|
||||
let ex = e as AxiosError;
|
||||
return ex.response?.data as UserPermissionResponse;
|
||||
}
|
||||
}
|
||||
|
||||
const userPermission = ref('');
|
||||
|
||||
const updateUserPermission = async () => {
|
||||
let res = await queryPermission();
|
||||
if (res?.success) {
|
||||
userPermission.value = res.permission as string;
|
||||
} else {
|
||||
userPermission.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
watch(userId, updateUserPermission, { immediate: true });
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
td {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
@ -80,7 +80,7 @@ const requestDeleteAccount = async () => {
|
||||
let ex = e as AxiosError;
|
||||
if (ex.response?.data) {
|
||||
return ex.response?.data as DeleteAccountResponse;
|
||||
} {
|
||||
} else {
|
||||
return { error: ex.message };
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<v-card class="mx-auto pa-12 pb-8" width="448" elevation="8" rounded="lg">
|
||||
<v-form fast-fail @submit.prevent="submit">
|
||||
<!-- <v-img class="mx-auto my-6" max-width="228"
|
||||
src="https://cdn.vuetifyjs.com/docs/images/logos/vuetify-logo-v3-slim-text-light.svg"></v-img> -->
|
||||
|
||||
<v-text-field v-model="userId" prepend-inner-icon="mdi-account-circle" :rules="userIdRules"
|
||||
label="账号"></v-text-field>
|
||||
|
||||
@ -80,7 +77,7 @@ const login = async (userId: string, password: string) => {
|
||||
let ex = e as AxiosError;
|
||||
if (ex.response?.data) {
|
||||
return ex.response?.data as LoginResponse;
|
||||
} {
|
||||
} else {
|
||||
return { error: ex.message };
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-form fast-fail @submit.prevent="submit">
|
||||
<!-- <v-img class="mx-auto my-6" max-width="228"
|
||||
src="https://cdn.vuetifyjs.com/docs/images/logos/vuetify-logo-v3-slim-text-light.svg"></v-img> -->
|
||||
|
||||
<v-card class="mx-auto pa-12 pb-8" elevation="8" max-width="448" rounded="lg">
|
||||
<v-text-field v-model="userId" prepend-inner-icon="mdi-account-circle" :rules="userIdRules"
|
||||
label="账号"></v-text-field>
|
||||
@ -16,15 +13,15 @@
|
||||
|
||||
<v-divider :thickness="10" class="border-opacity-0"></v-divider>
|
||||
|
||||
<v-text-field :rules="passwordRules2" label="重复输入密码" :append-inner-icon="visible ? 'mdi-eye' : 'mdi-eye-off'"
|
||||
:type="visible ? 'text' : 'password'" @click:append-inner="visible = !visible"
|
||||
<v-text-field :rules="passwordRules2" label="重复输入密码" :append-inner-icon="visibleR ? 'mdi-eye' : 'mdi-eye-off'"
|
||||
:type="visibleR ? 'text' : 'password'" @click:append-inner="visibleR = !visibleR"
|
||||
prepend-inner-icon="mdi-lock-outline"></v-text-field>
|
||||
|
||||
<v-divider :thickness="10" class="border-opacity-0"></v-divider>
|
||||
|
||||
<v-card class="mb-12" color="surface-variant" variant="tonal">
|
||||
<v-card-text class="text-medium-emphasis text-caption">
|
||||
这只是个占位符文本,不知道为什么没有这段话整个布局就会乱成一坨,我还在研究怎么让它表现得正常一点,这个组件库的文档太抽象了,看不懂,唉。
|
||||
新注册的账号默认是学生身份,老师请联系管理员提权。
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
@ -50,6 +47,7 @@ import axios, { AxiosError } from 'axios';
|
||||
import { inject, ref } from 'vue';
|
||||
|
||||
const visible = ref(false);
|
||||
const visibleR = ref(false);
|
||||
const model = defineModel();
|
||||
|
||||
const authStore = useAuthStore();
|
||||
@ -86,7 +84,7 @@ const register = async (userId: string, password: string) => {
|
||||
let ex = e as AxiosError;
|
||||
if (ex.response?.data) {
|
||||
return ex.response?.data as RegisterResponse;
|
||||
} {
|
||||
} else {
|
||||
return { error: ex.message };
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
<v-divider :thickness="10" class="border-opacity-0"></v-divider>
|
||||
|
||||
<v-btn :loading="loading" class="mb-8" color="blue" size="large" type="submit" variant="tonal" block>
|
||||
修改密码1
|
||||
修改密码
|
||||
</v-btn>
|
||||
|
||||
</v-form>
|
||||
@ -67,14 +67,12 @@ const requestRepasswd = async () => {
|
||||
formData.append("user_id", userId.value);
|
||||
formData.append("new_passwd", password.value);
|
||||
let res = await axios.post('/api/auth/repasswd', formData);
|
||||
console.log(res.data);
|
||||
return res.data as RepasswdResponse;
|
||||
} catch (e) {
|
||||
let ex = e as AxiosError;
|
||||
if (ex.response?.data) {
|
||||
console.log(ex.response?.data);
|
||||
return ex.response?.data as RepasswdResponse;
|
||||
} {
|
||||
} else {
|
||||
return { error: ex.message };
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ const requestRepasswd = async () => {
|
||||
let ex = e as AxiosError;
|
||||
if (ex.response?.data) {
|
||||
return ex.response?.data as RepasswdResponse;
|
||||
} {
|
||||
} else {
|
||||
return { error: ex.message };
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-card subtitle="占位符占位符占位符" title="用户信息面板" max-width="80%">
|
||||
<v-card title="用户信息面板" max-width="80%">
|
||||
<v-card-item>
|
||||
<v-chip class="ma-2" color="primary" label>
|
||||
<v-icon icon="mdi-account-circle-outline" start></v-icon>
|
||||
@ -21,11 +21,10 @@
|
||||
删除账号
|
||||
</v-chip>
|
||||
</v-card-item>
|
||||
<v-card-item>
|
||||
<v-expansion-panels>
|
||||
<v-expansion-panel title="Token (放出来玩玩,之后会换个机制)" :text="token">
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
<v-card-item v-if="userPermission == '1'">
|
||||
<v-chip class="chips" color="blue" @click="dialogRepasswd2Show = true">
|
||||
修改学生账号的密码
|
||||
</v-chip>
|
||||
</v-card-item>
|
||||
</v-card>
|
||||
<v-dialog v-model="dialogShow" width="auto">
|
||||
@ -36,6 +35,7 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<RepasswdDialog v-model="dialogRepasswdShow"></RepasswdDialog>
|
||||
<Repasswd2Dialog v-model="dialogRepasswd2Show"></Repasswd2Dialog>
|
||||
<DeleteAccountDialog v-model="dialogDeleteAccountShow"></DeleteAccountDialog>
|
||||
</template>
|
||||
|
||||
@ -46,6 +46,7 @@ import { jwtDecode, type JwtPayload } from 'jwt-decode';
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import DeleteAccountDialog from './DeleteAccountDialog.vue';
|
||||
import RepasswdDialog from './RepasswdDialog.vue';
|
||||
import Repasswd2Dialog from './Repasswd2Dialog.vue';
|
||||
|
||||
const dialogShow = ref(false);
|
||||
const dialogTitle = ref('');
|
||||
@ -55,6 +56,7 @@ const dialogClose = ref(() => { });
|
||||
const dialogDeleteAccountShow = ref(false);
|
||||
|
||||
const dialogRepasswdShow = ref(false);
|
||||
const dialogRepasswd2Show = ref(false);
|
||||
|
||||
const dialog = (title: string, text: string) => {
|
||||
dialogTitle.value = title;
|
||||
|
@ -5,22 +5,50 @@
|
||||
*/
|
||||
|
||||
// Composables
|
||||
import Admin from '@/components/Admin.vue'
|
||||
import Auth from '@/components/Auth.vue'
|
||||
import HelloWorld from '@/components/HelloWorld.vue'
|
||||
import HelloWorld2 from '@/components/HelloWorld2.vue'
|
||||
import Login from '@/components/RegisterForm.vue'
|
||||
import Study from '@/components/Study.vue'
|
||||
import Homepage from '@/components/Homepage.vue'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import RecordList from '@/components/RecordList.vue'
|
||||
import ProblemList from '@/components/study/ProblemList.vue'
|
||||
import SetList from '@/components/study/SetList.vue'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: HelloWorld
|
||||
component: Homepage
|
||||
},
|
||||
{
|
||||
path: '/auth',
|
||||
name: 'home2',
|
||||
name: 'auth',
|
||||
component: Auth
|
||||
},
|
||||
{
|
||||
path: '/admin',
|
||||
name: 'admin',
|
||||
component: Admin
|
||||
},
|
||||
{
|
||||
path: '/study',
|
||||
name: 'study',
|
||||
component: Study
|
||||
},
|
||||
{
|
||||
path: '/problems',
|
||||
name: 'problems',
|
||||
component: ProblemList
|
||||
},
|
||||
{
|
||||
path: '/sets',
|
||||
name: 'sets',
|
||||
component: SetList
|
||||
},
|
||||
{
|
||||
path: '/records',
|
||||
name: 'sub',
|
||||
component: RecordList
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -1,20 +1,37 @@
|
||||
import axios from "axios";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const useAuthStore = defineStore("auth", {
|
||||
state: () => ({
|
||||
token: localStorage.getItem('token') || '',
|
||||
adminToken: sessionStorage.getItem('adminToken') || ''
|
||||
}),
|
||||
actions: {
|
||||
setToken(token: string) {
|
||||
this.token = token;
|
||||
localStorage.setItem('token', token);
|
||||
},
|
||||
clearToken() {
|
||||
async clearToken() {
|
||||
try {
|
||||
const formData = new FormData;
|
||||
formData.append("token", this.token);
|
||||
await axios.post('/api/auth/logout', formData);
|
||||
} catch (e) {
|
||||
}
|
||||
this.token = '';
|
||||
localStorage.removeItem('token');
|
||||
},
|
||||
setAdminToken(token: string) {
|
||||
this.adminToken = token;
|
||||
sessionStorage.setItem('adminToken', token);
|
||||
},
|
||||
clearAdminToken() {
|
||||
this.adminToken = '';
|
||||
sessionStorage.removeItem('adminToken');
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
isAuthenticated: (state) => !!state.token,
|
||||
isAdmin: (state) => !!state.adminToken,
|
||||
},
|
||||
});
|
||||
|
@ -50,6 +50,12 @@ export default defineConfig({
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8081',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
|
@ -4,7 +4,7 @@ add_rules("plugin.compile_commands.autoupdate")
|
||||
set_warnings("all")
|
||||
set_warnings("error")
|
||||
|
||||
add_requires("civetweb", "cjson", "leveldb", "jwt-cpp", "cryptopp")
|
||||
add_requires("civetweb", "cjson", "leveldb", "jwt-cpp", "cryptopp", "nlohmann_json")
|
||||
|
||||
local npm = "npm"
|
||||
if is_host("windows") then
|
||||
@ -28,7 +28,7 @@ target("hash")
|
||||
target("db")
|
||||
set_languages("c++23")
|
||||
set_kind("static")
|
||||
add_packages("leveldb")
|
||||
add_packages("leveldb", "nlohmann_json")
|
||||
add_deps("hash")
|
||||
add_includedirs("include/db", "include")
|
||||
add_files("src/db/**.cpp")
|
||||
|
Loading…
Reference in New Issue
Block a user