添加记录处理模块,支持记录的增删查改功能,优化记录管理流程
This commit is contained in:
parent
02f2c73eee
commit
2d0dacd495
33
include/db/records.h
Normal file
33
include/db/records.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#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();
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
@ -53,4 +53,10 @@ void res_query_problem2(mg_connection* conn, const char* problem, const char* an
|
|||||||
void res_check_answer(mg_connection* conn, int flag);
|
void res_check_answer(mg_connection* conn, int flag);
|
||||||
void res_all_problems(mg_connection* conn, const char* problems);
|
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);
|
||||||
|
|
||||||
#endif
|
#endif
|
@ -4,6 +4,7 @@
|
|||||||
#include "server/types.h"
|
#include "server/types.h"
|
||||||
|
|
||||||
int problems_handler(mg_connection* conn, void* cbdata);
|
int problems_handler(mg_connection* conn, void* cbdata);
|
||||||
|
int records_handler(mg_connection* conn, void* cbdata);
|
||||||
|
|
||||||
extern char* secret;
|
extern char* secret;
|
||||||
|
|
||||||
|
166
src/db/records.cpp
Normal file
166
src/db/records.cpp
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
#include "records.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <print>
|
||||||
|
|
||||||
|
|
||||||
|
#include <leveldb/db.h>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
using leveldb_ptr = leveldb::DB*;
|
||||||
|
|
||||||
|
auto records_db = leveldb_ptr{nullptr};
|
||||||
|
auto records_count_ = 0;
|
||||||
|
|
||||||
|
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 it = records_db->NewIterator(leveldb::ReadOptions());
|
||||||
|
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||||
|
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_;
|
||||||
|
}
|
||||||
|
}
|
@ -260,7 +260,7 @@ void res_need_xxx(mg_connection* conn, const char* xxx)
|
|||||||
void res_500(mg_connection* conn, const char* error)
|
void res_500(mg_connection* conn, const char* error)
|
||||||
{
|
{
|
||||||
mg_printf(conn,
|
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"
|
"Content-Type: application/json\r\n"
|
||||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||||
"{\"error\":\"%s\"}",
|
"{\"error\":\"%s\"}",
|
||||||
@ -270,7 +270,7 @@ void res_500(mg_connection* conn, const char* error)
|
|||||||
void res_404(mg_connection* conn, const char* error)
|
void res_404(mg_connection* conn, const char* error)
|
||||||
{
|
{
|
||||||
mg_printf(conn,
|
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"
|
"Content-Type: application/json\r\n"
|
||||||
"Access-Control-Allow-Origin: *\r\n\r\n"
|
"Access-Control-Allow-Origin: *\r\n\r\n"
|
||||||
"{\"error\":\"%s\"}",
|
"{\"error\":\"%s\"}",
|
||||||
@ -345,3 +345,53 @@ void res_all_problems(mg_connection* conn, const char* problems)
|
|||||||
"{\"success\":\"success to get all the problems\", \"result\":%s}",
|
"{\"success\":\"success to get all the problems\", \"result\":%s}",
|
||||||
problems);
|
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);
|
||||||
|
}
|
||||||
|
186
src/server/study/records.c
Normal file
186
src/server/study/records.c
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
res_bad_action(conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record_form_dtor(&form);
|
||||||
|
return 1;
|
||||||
|
}
|
269
ui/src/components/RecordList.vue
Normal file
269
ui/src/components/RecordList.vue
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
<template>
|
||||||
|
<v-data-table :loading="loading" :headers="(headers as any)" :items="records"
|
||||||
|
:sort-by="[{ key: 'id', order: 'desc' }]" multi-sort items-per-page-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-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 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;
|
||||||
|
} {
|
||||||
|
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();
|
||||||
|
console.log(res);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
records.value.push({
|
||||||
|
id: 114514,
|
||||||
|
user_id: '114514',
|
||||||
|
problem: 1919810,
|
||||||
|
status: 'uke',
|
||||||
|
answer: 'NaN',
|
||||||
|
timestamp: 114514
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(initialize);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
td {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue
Block a user