feat(theme): complete Minecraft overhaul for all pages including admin/utility
这个提交包含在:
189
build_log.txt
普通文件
189
build_log.txt
普通文件
@@ -0,0 +1,189 @@
|
|||||||
|
#0 building with "desktop-linux" instance using docker driver
|
||||||
|
|
||||||
|
#1 [internal] load build definition from Dockerfile.backend
|
||||||
|
#1 transferring dockerfile: 1.20kB done
|
||||||
|
#1 DONE 0.0s
|
||||||
|
|
||||||
|
#2 resolve image config for docker-image://docker.io/docker/dockerfile:1
|
||||||
|
#2 DONE 0.5s
|
||||||
|
|
||||||
|
#3 docker-image://docker.io/docker/dockerfile:1@sha256:b6afd42430b15f2d2a4c5a02b919e98a525b785b1aaff16747d2f623364e39b6
|
||||||
|
#3 resolve docker.io/docker/dockerfile:1@sha256:b6afd42430b15f2d2a4c5a02b919e98a525b785b1aaff16747d2f623364e39b6 done
|
||||||
|
#3 CACHED
|
||||||
|
|
||||||
|
#4 [internal] load metadata for docker.io/library/ubuntu:24.04
|
||||||
|
#4 DONE 0.3s
|
||||||
|
|
||||||
|
#5 [internal] load .dockerignore
|
||||||
|
#5 transferring context: 2B done
|
||||||
|
#5 DONE 0.0s
|
||||||
|
|
||||||
|
#6 [internal] load build context
|
||||||
|
#6 transferring context: 6.76kB done
|
||||||
|
#6 DONE 0.0s
|
||||||
|
|
||||||
|
#7 [build 1/6] FROM docker.io/library/ubuntu:24.04@sha256:cd1dba651b3080c3686ecf4e3c4220f026b521fb76978881737d24f200828b2b
|
||||||
|
#7 resolve docker.io/library/ubuntu:24.04@sha256:cd1dba651b3080c3686ecf4e3c4220f026b521fb76978881737d24f200828b2b done
|
||||||
|
#7 DONE 0.0s
|
||||||
|
|
||||||
|
#8 [build 2/6] RUN apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential cmake ninja-build pkg-config libdrogon-dev libjsoncpp-dev libyaml-cpp-dev libhiredis-dev libpq-dev libmariadb-dev libmariadb-dev-compat libsqlite3-dev sqlite3 libssl-dev uuid-dev libbrotli-dev catch2 && rm -rf /var/lib/apt/lists/*
|
||||||
|
#8 CACHED
|
||||||
|
|
||||||
|
#9 [build 4/6] COPY backend/ ./backend/
|
||||||
|
#9 CACHED
|
||||||
|
|
||||||
|
#10 [build 3/6] WORKDIR /src
|
||||||
|
#10 CACHED
|
||||||
|
|
||||||
|
#11 [build 5/6] COPY CMakeLists.txt ./
|
||||||
|
#11 CACHED
|
||||||
|
|
||||||
|
#12 [build 6/6] RUN cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build build --target csp_server
|
||||||
|
#12 0.121 -- The CXX compiler identification is GNU 13.3.0
|
||||||
|
#12 0.128 -- Detecting CXX compiler ABI info
|
||||||
|
#12 0.151 -- Detecting CXX compiler ABI info - done
|
||||||
|
#12 0.154 -- Check for working CXX compiler: /usr/bin/c++ - skipped
|
||||||
|
#12 0.154 -- Detecting CXX compile features
|
||||||
|
#12 0.155 -- Detecting CXX compile features - done
|
||||||
|
#12 0.162 -- Found Jsoncpp: /usr/include/jsoncpp
|
||||||
|
#12 0.164 -- jsoncpp verson:1.9.5
|
||||||
|
#12 0.165 -- Performing Test CMAKE_HAVE_LIBC_PTHREAD
|
||||||
|
#12 0.188 -- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success
|
||||||
|
#12 0.188 -- Found Threads: TRUE
|
||||||
|
#12 0.190 -- Found UUID: /usr/lib/aarch64-linux-gnu/libuuid.so
|
||||||
|
#12 0.192 -- Found ZLIB: /usr/lib/aarch64-linux-gnu/libz.so (found version "1.3")
|
||||||
|
#12 0.197 -- Found PostgreSQL: /usr/lib/aarch64-linux-gnu/libpq.so (found version "16.11")
|
||||||
|
#12 0.197 -- pg inc: /usr/include/postgresql
|
||||||
|
#12 0.197 -- Found pg: /usr/lib/aarch64-linux-gnu/libpq.so
|
||||||
|
#12 0.198 -- Found SQLite3: /usr/lib/aarch64-linux-gnu/libsqlite3.so
|
||||||
|
#12 0.198 -- MySQL Include dir: /usr/include/mysql
|
||||||
|
#12 0.198 -- MySQL client libraries: /usr/lib/aarch64-linux-gnu/libmysqlclient_r.so
|
||||||
|
#12 0.198 -- Found MySQL: /usr/lib/aarch64-linux-gnu/libmysqlclient_r.so
|
||||||
|
#12 0.199 -- Found Brotli: /usr/lib/aarch64-linux-gnu/libbrotlidec.so
|
||||||
|
#12 0.200 -- Found Hiredis: /usr/lib/aarch64-linux-gnu/libhiredis.so
|
||||||
|
#12 0.201 -- Looking for C++ include filesystem
|
||||||
|
#12 0.335 -- Looking for C++ include filesystem - found
|
||||||
|
#12 0.335 -- Performing Test CXX_FILESYSTEM_NO_LINK_NEEDED
|
||||||
|
#12 0.484 -- Performing Test CXX_FILESYSTEM_NO_LINK_NEEDED - Success
|
||||||
|
#12 0.499 -- Found OpenSSL: /usr/lib/aarch64-linux-gnu/libcrypto.so (found version "3.0.13")
|
||||||
|
#12 0.500 -- Configuring done (0.4s)
|
||||||
|
#12 0.505 -- Generating done (0.0s)
|
||||||
|
#12 0.506 -- Build files have been written to: /src/build
|
||||||
|
#12 0.517 [1/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/version.cc.o
|
||||||
|
#12 0.825 [2/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/crypto.cc.o
|
||||||
|
#12 0.911 [3/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/kb_service.cc.o
|
||||||
|
#12 0.914 [4/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/app_state.cc.o
|
||||||
|
#12 0.934 [5/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/user_service.cc.o
|
||||||
|
#12 1.138 [6/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/solution_access_service.cc.o
|
||||||
|
#12 1.139 FAILED: backend/CMakeFiles/csp_core.dir/src/services/solution_access_service.cc.o
|
||||||
|
#12 1.139 /usr/bin/c++ -I/src/backend/include -I/usr/include/jsoncpp -O3 -DNDEBUG -std=c++20 -MD -MT backend/CMakeFiles/csp_core.dir/src/services/solution_access_service.cc.o -MF backend/CMakeFiles/csp_core.dir/src/services/solution_access_service.cc.o.d -o backend/CMakeFiles/csp_core.dir/src/services/solution_access_service.cc.o -c /src/backend/src/services/solution_access_service.cc
|
||||||
|
#12 1.139 In file included from /src/backend/src/services/solution_access_service.cc:1:
|
||||||
|
#12 1.139 /src/backend/include/csp/services/solution_access_service.h:40:3: error: 'SolutionViewStats' does not name a type
|
||||||
|
#12 1.139 40 | SolutionViewStats QueryUserProblemViewStats(int64_t user_id,
|
||||||
|
#12 1.139 | ^~~~~~~~~~~~~~~~~
|
||||||
|
#12 1.139 /src/backend/include/csp/services/solution_access_service.h:43:8: error: 'vector' in namespace 'std' does not name a template type
|
||||||
|
#12 1.139 43 | std::vector<RatingHistoryItem> ListRatingHistory(int64_t user_id, int limit);
|
||||||
|
#12 1.139 | ^~~~~~
|
||||||
|
#12 1.139 /src/backend/include/csp/services/solution_access_service.h:7:1: note: 'std::vector' is defined in header '<vector>'; did you forget to '#include <vector>'?
|
||||||
|
#12 1.139 6 | #include <optional>
|
||||||
|
#12 1.139 +++ |+#include <vector>
|
||||||
|
#12 1.139 7 | #include <string>
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc:128:1: error: 'SolutionViewChargeResult' does not name a type
|
||||||
|
#12 1.139 128 | SolutionViewChargeResult SolutionAccessService::ConsumeSolutionView(
|
||||||
|
#12 1.139 | ^~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc:215:1: error: 'SolutionViewStats' does not name a type
|
||||||
|
#12 1.139 215 | SolutionViewStats SolutionAccessService::QueryUserProblemViewStats(
|
||||||
|
#12 1.139 | ^~~~~~~~~~~~~~~~~
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc:242:13: error: 'RatingHistoryItem' was not declared in this scope; did you mean 'csp::services::RatingHistoryItem'?
|
||||||
|
#12 1.139 242 | std::vector<RatingHistoryItem> SolutionAccessService::ListRatingHistory(int64_t user_id, int limit) {
|
||||||
|
#12 1.139 | ^~~~~~~~~~~~~~~~~
|
||||||
|
#12 1.139 | csp::services::RatingHistoryItem
|
||||||
|
#12 1.139 /src/backend/include/csp/services/solution_access_service.h:24:8: note: 'csp::services::RatingHistoryItem' declared here
|
||||||
|
#12 1.139 24 | struct RatingHistoryItem {
|
||||||
|
#12 1.139 | ^~~~~~~~~~~~~~~~~
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc:242:30: error: template argument 1 is invalid
|
||||||
|
#12 1.139 242 | std::vector<RatingHistoryItem> SolutionAccessService::ListRatingHistory(int64_t user_id, int limit) {
|
||||||
|
#12 1.139 | ^
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc:242:30: error: template argument 2 is invalid
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc:242:32: error: 'SolutionAccessService' has not been declared
|
||||||
|
#12 1.139 242 | std::vector<RatingHistoryItem> SolutionAccessService::ListRatingHistory(int64_t user_id, int limit) {
|
||||||
|
#12 1.139 | ^~~~~~~~~~~~~~~~~~~~~
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc: In function 'int ListRatingHistory(int64_t, int)':
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc:246:17: error: 'RatingHistoryItem' was not declared in this scope; did you mean 'csp::services::RatingHistoryItem'?
|
||||||
|
#12 1.139 246 | std::vector<RatingHistoryItem> items;
|
||||||
|
#12 1.139 | ^~~~~~~~~~~~~~~~~
|
||||||
|
#12 1.139 | csp::services::RatingHistoryItem
|
||||||
|
#12 1.139 /src/backend/include/csp/services/solution_access_service.h:24:8: note: 'csp::services::RatingHistoryItem' declared here
|
||||||
|
#12 1.139 24 | struct RatingHistoryItem {
|
||||||
|
#12 1.139 | ^~~~~~~~~~~~~~~~~
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc:246:34: error: template argument 1 is invalid
|
||||||
|
#12 1.139 246 | std::vector<RatingHistoryItem> items;
|
||||||
|
#12 1.139 | ^
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc:246:34: error: template argument 2 is invalid
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc:247:19: error: 'db_' was not declared in this scope; did you mean 'db'?
|
||||||
|
#12 1.139 247 | sqlite3* db = db_.raw();
|
||||||
|
#12 1.139 | ^~~
|
||||||
|
#12 1.139 | db
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc:262:5: error: 'CheckSqlite' was not declared in this scope; did you mean 'csp::services::{anonymous}::CheckSqlite'?
|
||||||
|
#12 1.139 262 | CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db, "prepare history query");
|
||||||
|
#12 1.139 | ^~~~~~~~~~~
|
||||||
|
#12 1.139 | csp::services::{anonymous}::CheckSqlite
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc:21:6: note: 'csp::services::{anonymous}::CheckSqlite' declared here
|
||||||
|
#12 1.139 21 | void CheckSqlite(int rc, sqlite3* db, const char* what) {
|
||||||
|
#12 1.139 | ^~~~~~~~~~~
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc:269:26: error: expected ';' before 'item'
|
||||||
|
#12 1.139 269 | RatingHistoryItem item;
|
||||||
|
#12 1.139 | ^~~~~
|
||||||
|
#12 1.139 | ;
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc:270:9: error: 'item' was not declared in this scope; did you mean 'items'?
|
||||||
|
#12 1.139 270 | item.type = ColText(stmt, 0);
|
||||||
|
#12 1.139 | ^~~~
|
||||||
|
#12 1.139 | items
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc:270:21: error: 'ColText' was not declared in this scope; did you mean 'csp::services::{anonymous}::ColText'?
|
||||||
|
#12 1.139 270 | item.type = ColText(stmt, 0);
|
||||||
|
#12 1.139 | ^~~~~~~
|
||||||
|
#12 1.139 | csp::services::{anonymous}::ColText
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc:26:13: note: 'csp::services::{anonymous}::ColText' declared here
|
||||||
|
#12 1.139 26 | std::string ColText(sqlite3_stmt* stmt, int col) {
|
||||||
|
#12 1.139 | ^~~~~~~
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc:274:15: error: request for member 'push_back' in 'items', which is of non-class type 'int'
|
||||||
|
#12 1.139 274 | items.push_back(item);
|
||||||
|
#12 1.139 | ^~~~~~~~~
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc: At global scope:
|
||||||
|
#12 1.139 /src/backend/src/services/solution_access_service.cc:280:1: error: expected declaration before '}' token
|
||||||
|
#12 1.139 280 | } // namespace csp::services
|
||||||
|
#12 1.139 | ^
|
||||||
|
#12 1.185 [7/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/wrong_book_service.cc.o
|
||||||
|
#12 1.255 [8/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/auth_service.cc.o
|
||||||
|
#12 1.265 [9/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/db/sqlite_db.cc.o
|
||||||
|
#12 1.308 [10/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/contest_service.cc.o
|
||||||
|
#12 1.522 [11/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_service.cc.o
|
||||||
|
#12 1.585 [12/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/daily_task_service.cc.o
|
||||||
|
#12 1.680 [13/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/redeem_service.cc.o
|
||||||
|
#12 1.733 [14/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_workspace_service.cc.o
|
||||||
|
#12 1.747 [15/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/submission_service.cc.o
|
||||||
|
#12 2.456 [16/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/kb_import_runner.cc.o
|
||||||
|
#12 2.619 [17/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_solution_runner.cc.o
|
||||||
|
#12 2.619 ninja: build stopped: subcommand failed.
|
||||||
|
#12 ERROR: process "/bin/sh -c cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build build --target csp_server" did not complete successfully: exit code: 1
|
||||||
|
------
|
||||||
|
> [build 6/6] RUN cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build build --target csp_server:
|
||||||
|
1.265 [9/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/db/sqlite_db.cc.o
|
||||||
|
1.308 [10/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/contest_service.cc.o
|
||||||
|
1.522 [11/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_service.cc.o
|
||||||
|
1.585 [12/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/daily_task_service.cc.o
|
||||||
|
1.680 [13/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/redeem_service.cc.o
|
||||||
|
1.733 [14/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_workspace_service.cc.o
|
||||||
|
1.747 [15/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/submission_service.cc.o
|
||||||
|
2.456 [16/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/kb_import_runner.cc.o
|
||||||
|
2.619 [17/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_solution_runner.cc.o
|
||||||
|
2.619 ninja: build stopped: subcommand failed.
|
||||||
|
------
|
||||||
|
Dockerfile.backend:18
|
||||||
|
--------------------
|
||||||
|
17 |
|
||||||
|
18 | >>> RUN cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && \
|
||||||
|
19 | >>> cmake --build build --target csp_server
|
||||||
|
20 |
|
||||||
|
--------------------
|
||||||
|
ERROR: failed to build: failed to solve: process "/bin/sh -c cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build build --target csp_server" did not complete successfully: exit code: 1
|
||||||
180
build_log_2.txt
普通文件
180
build_log_2.txt
普通文件
@@ -0,0 +1,180 @@
|
|||||||
|
#0 building with "desktop-linux" instance using docker driver
|
||||||
|
|
||||||
|
#1 [internal] load build definition from Dockerfile.backend
|
||||||
|
#1 transferring dockerfile: 1.20kB done
|
||||||
|
#1 DONE 0.0s
|
||||||
|
|
||||||
|
#2 resolve image config for docker-image://docker.io/docker/dockerfile:1
|
||||||
|
#2 DONE 0.3s
|
||||||
|
|
||||||
|
#3 docker-image://docker.io/docker/dockerfile:1@sha256:b6afd42430b15f2d2a4c5a02b919e98a525b785b1aaff16747d2f623364e39b6
|
||||||
|
#3 resolve docker.io/docker/dockerfile:1@sha256:b6afd42430b15f2d2a4c5a02b919e98a525b785b1aaff16747d2f623364e39b6 done
|
||||||
|
#3 CACHED
|
||||||
|
|
||||||
|
#4 [internal] load metadata for docker.io/library/ubuntu:24.04
|
||||||
|
#4 DONE 0.3s
|
||||||
|
|
||||||
|
#5 [internal] load .dockerignore
|
||||||
|
#5 transferring context: 2B done
|
||||||
|
#5 DONE 0.0s
|
||||||
|
|
||||||
|
#6 [internal] load build context
|
||||||
|
#6 transferring context: 6.76kB done
|
||||||
|
#6 DONE 0.0s
|
||||||
|
|
||||||
|
#7 [build 1/6] FROM docker.io/library/ubuntu:24.04@sha256:cd1dba651b3080c3686ecf4e3c4220f026b521fb76978881737d24f200828b2b
|
||||||
|
#7 resolve docker.io/library/ubuntu:24.04@sha256:cd1dba651b3080c3686ecf4e3c4220f026b521fb76978881737d24f200828b2b done
|
||||||
|
#7 DONE 0.0s
|
||||||
|
|
||||||
|
#8 [build 2/6] RUN apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential cmake ninja-build pkg-config libdrogon-dev libjsoncpp-dev libyaml-cpp-dev libhiredis-dev libpq-dev libmariadb-dev libmariadb-dev-compat libsqlite3-dev sqlite3 libssl-dev uuid-dev libbrotli-dev catch2 && rm -rf /var/lib/apt/lists/*
|
||||||
|
#8 CACHED
|
||||||
|
|
||||||
|
#9 [build 3/6] WORKDIR /src
|
||||||
|
#9 CACHED
|
||||||
|
|
||||||
|
#10 [build 4/6] COPY backend/ ./backend/
|
||||||
|
#10 CACHED
|
||||||
|
|
||||||
|
#11 [build 5/6] COPY CMakeLists.txt ./
|
||||||
|
#11 CACHED
|
||||||
|
|
||||||
|
#12 [build 6/6] RUN cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build build --target csp_server
|
||||||
|
#12 0.107 -- The CXX compiler identification is GNU 13.3.0
|
||||||
|
#12 0.113 -- Detecting CXX compiler ABI info
|
||||||
|
#12 0.136 -- Detecting CXX compiler ABI info - done
|
||||||
|
#12 0.139 -- Check for working CXX compiler: /usr/bin/c++ - skipped
|
||||||
|
#12 0.139 -- Detecting CXX compile features
|
||||||
|
#12 0.139 -- Detecting CXX compile features - done
|
||||||
|
#12 0.142 -- Found Jsoncpp: /usr/include/jsoncpp
|
||||||
|
#12 0.143 -- jsoncpp verson:1.9.5
|
||||||
|
#12 0.144 -- Performing Test CMAKE_HAVE_LIBC_PTHREAD
|
||||||
|
#12 0.167 -- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success
|
||||||
|
#12 0.168 -- Found Threads: TRUE
|
||||||
|
#12 0.169 -- Found UUID: /usr/lib/aarch64-linux-gnu/libuuid.so
|
||||||
|
#12 0.170 -- Found ZLIB: /usr/lib/aarch64-linux-gnu/libz.so (found version "1.3")
|
||||||
|
#12 0.174 -- Found PostgreSQL: /usr/lib/aarch64-linux-gnu/libpq.so (found version "16.11")
|
||||||
|
#12 0.174 -- pg inc: /usr/include/postgresql
|
||||||
|
#12 0.174 -- Found pg: /usr/lib/aarch64-linux-gnu/libpq.so
|
||||||
|
#12 0.175 -- Found SQLite3: /usr/lib/aarch64-linux-gnu/libsqlite3.so
|
||||||
|
#12 0.175 -- MySQL Include dir: /usr/include/mysql
|
||||||
|
#12 0.175 -- MySQL client libraries: /usr/lib/aarch64-linux-gnu/libmysqlclient_r.so
|
||||||
|
#12 0.176 -- Found MySQL: /usr/lib/aarch64-linux-gnu/libmysqlclient_r.so
|
||||||
|
#12 0.176 -- Found Brotli: /usr/lib/aarch64-linux-gnu/libbrotlidec.so
|
||||||
|
#12 0.177 -- Found Hiredis: /usr/lib/aarch64-linux-gnu/libhiredis.so
|
||||||
|
#12 0.178 -- Looking for C++ include filesystem
|
||||||
|
#12 0.311 -- Looking for C++ include filesystem - found
|
||||||
|
#12 0.311 -- Performing Test CXX_FILESYSTEM_NO_LINK_NEEDED
|
||||||
|
#12 0.457 -- Performing Test CXX_FILESYSTEM_NO_LINK_NEEDED - Success
|
||||||
|
#12 0.468 -- Found OpenSSL: /usr/lib/aarch64-linux-gnu/libcrypto.so (found version "3.0.13")
|
||||||
|
#12 0.469 -- Configuring done (0.4s)
|
||||||
|
#12 0.474 -- Generating done (0.0s)
|
||||||
|
#12 0.475 -- Build files have been written to: /src/build
|
||||||
|
#12 0.488 [1/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/version.cc.o
|
||||||
|
#12 0.706 [2/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/app_state.cc.o
|
||||||
|
#12 0.802 [3/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/crypto.cc.o
|
||||||
|
#12 0.871 [4/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/user_service.cc.o
|
||||||
|
#12 0.891 [5/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/kb_service.cc.o
|
||||||
|
#12 1.096 [6/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/wrong_book_service.cc.o
|
||||||
|
#12 1.191 [7/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/solution_access_service.cc.o
|
||||||
|
#12 1.191 FAILED: backend/CMakeFiles/csp_core.dir/src/services/solution_access_service.cc.o
|
||||||
|
#12 1.191 /usr/bin/c++ -I/src/backend/include -I/usr/include/jsoncpp -O3 -DNDEBUG -std=c++20 -MD -MT backend/CMakeFiles/csp_core.dir/src/services/solution_access_service.cc.o -MF backend/CMakeFiles/csp_core.dir/src/services/solution_access_service.cc.o.d -o backend/CMakeFiles/csp_core.dir/src/services/solution_access_service.cc.o -c /src/backend/src/services/solution_access_service.cc
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc:128:1: error: 'SolutionViewChargeResult' does not name a type
|
||||||
|
#12 1.191 128 | SolutionViewChargeResult SolutionAccessService::ConsumeSolutionView(
|
||||||
|
#12 1.191 | ^~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc:215:1: error: 'SolutionViewStats' does not name a type
|
||||||
|
#12 1.191 215 | SolutionViewStats SolutionAccessService::QueryUserProblemViewStats(
|
||||||
|
#12 1.191 | ^~~~~~~~~~~~~~~~~
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc:242:13: error: 'RatingHistoryItem' was not declared in this scope; did you mean 'csp::services::RatingHistoryItem'?
|
||||||
|
#12 1.191 242 | std::vector<RatingHistoryItem> SolutionAccessService::ListRatingHistory(int64_t user_id, int limit) {
|
||||||
|
#12 1.191 | ^~~~~~~~~~~~~~~~~
|
||||||
|
#12 1.191 | csp::services::RatingHistoryItem
|
||||||
|
#12 1.191 In file included from /src/backend/src/services/solution_access_service.cc:1:
|
||||||
|
#12 1.191 /src/backend/include/csp/services/solution_access_service.h:32:8: note: 'csp::services::RatingHistoryItem' declared here
|
||||||
|
#12 1.191 32 | struct RatingHistoryItem {
|
||||||
|
#12 1.191 | ^~~~~~~~~~~~~~~~~
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc:242:30: error: template argument 1 is invalid
|
||||||
|
#12 1.191 242 | std::vector<RatingHistoryItem> SolutionAccessService::ListRatingHistory(int64_t user_id, int limit) {
|
||||||
|
#12 1.191 | ^
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc:242:30: error: template argument 2 is invalid
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc:242:32: error: 'SolutionAccessService' has not been declared
|
||||||
|
#12 1.191 242 | std::vector<RatingHistoryItem> SolutionAccessService::ListRatingHistory(int64_t user_id, int limit) {
|
||||||
|
#12 1.191 | ^~~~~~~~~~~~~~~~~~~~~
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc: In function 'int ListRatingHistory(int64_t, int)':
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc:246:17: error: 'RatingHistoryItem' was not declared in this scope; did you mean 'csp::services::RatingHistoryItem'?
|
||||||
|
#12 1.191 246 | std::vector<RatingHistoryItem> items;
|
||||||
|
#12 1.191 | ^~~~~~~~~~~~~~~~~
|
||||||
|
#12 1.191 | csp::services::RatingHistoryItem
|
||||||
|
#12 1.191 /src/backend/include/csp/services/solution_access_service.h:32:8: note: 'csp::services::RatingHistoryItem' declared here
|
||||||
|
#12 1.191 32 | struct RatingHistoryItem {
|
||||||
|
#12 1.191 | ^~~~~~~~~~~~~~~~~
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc:246:34: error: template argument 1 is invalid
|
||||||
|
#12 1.191 246 | std::vector<RatingHistoryItem> items;
|
||||||
|
#12 1.191 | ^
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc:246:34: error: template argument 2 is invalid
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc:247:19: error: 'db_' was not declared in this scope; did you mean 'db'?
|
||||||
|
#12 1.191 247 | sqlite3* db = db_.raw();
|
||||||
|
#12 1.191 | ^~~
|
||||||
|
#12 1.191 | db
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc:262:5: error: 'CheckSqlite' was not declared in this scope; did you mean 'csp::services::{anonymous}::CheckSqlite'?
|
||||||
|
#12 1.191 262 | CheckSqlite(sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr), db, "prepare history query");
|
||||||
|
#12 1.191 | ^~~~~~~~~~~
|
||||||
|
#12 1.191 | csp::services::{anonymous}::CheckSqlite
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc:21:6: note: 'csp::services::{anonymous}::CheckSqlite' declared here
|
||||||
|
#12 1.191 21 | void CheckSqlite(int rc, sqlite3* db, const char* what) {
|
||||||
|
#12 1.191 | ^~~~~~~~~~~
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc:269:26: error: expected ';' before 'item'
|
||||||
|
#12 1.191 269 | RatingHistoryItem item;
|
||||||
|
#12 1.191 | ^~~~~
|
||||||
|
#12 1.191 | ;
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc:270:9: error: 'item' was not declared in this scope; did you mean 'items'?
|
||||||
|
#12 1.191 270 | item.type = ColText(stmt, 0);
|
||||||
|
#12 1.191 | ^~~~
|
||||||
|
#12 1.191 | items
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc:270:21: error: 'ColText' was not declared in this scope; did you mean 'csp::services::{anonymous}::ColText'?
|
||||||
|
#12 1.191 270 | item.type = ColText(stmt, 0);
|
||||||
|
#12 1.191 | ^~~~~~~
|
||||||
|
#12 1.191 | csp::services::{anonymous}::ColText
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc:26:13: note: 'csp::services::{anonymous}::ColText' declared here
|
||||||
|
#12 1.191 26 | std::string ColText(sqlite3_stmt* stmt, int col) {
|
||||||
|
#12 1.191 | ^~~~~~~
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc:274:15: error: request for member 'push_back' in 'items', which is of non-class type 'int'
|
||||||
|
#12 1.191 274 | items.push_back(item);
|
||||||
|
#12 1.191 | ^~~~~~~~~
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc: At global scope:
|
||||||
|
#12 1.191 /src/backend/src/services/solution_access_service.cc:280:1: error: expected declaration before '}' token
|
||||||
|
#12 1.191 280 | } // namespace csp::services
|
||||||
|
#12 1.191 | ^
|
||||||
|
#12 1.213 [8/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_service.cc.o
|
||||||
|
#12 1.278 [9/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/db/sqlite_db.cc.o
|
||||||
|
#12 1.290 [10/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/contest_service.cc.o
|
||||||
|
#12 1.512 [11/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/auth_service.cc.o
|
||||||
|
#12 1.562 [12/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/redeem_service.cc.o
|
||||||
|
#12 1.606 [13/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/daily_task_service.cc.o
|
||||||
|
#12 1.631 [14/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_workspace_service.cc.o
|
||||||
|
#12 1.769 [15/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/submission_service.cc.o
|
||||||
|
#12 2.397 [16/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/kb_import_runner.cc.o
|
||||||
|
#12 2.508 [17/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_gen_runner.cc.o
|
||||||
|
#12 2.607 [18/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_solution_runner.cc.o
|
||||||
|
#12 2.607 ninja: build stopped: subcommand failed.
|
||||||
|
#12 ERROR: process "/bin/sh -c cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build build --target csp_server" did not complete successfully: exit code: 1
|
||||||
|
------
|
||||||
|
> [build 6/6] RUN cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build build --target csp_server:
|
||||||
|
1.290 [10/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/contest_service.cc.o
|
||||||
|
1.512 [11/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/auth_service.cc.o
|
||||||
|
1.562 [12/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/redeem_service.cc.o
|
||||||
|
1.606 [13/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/daily_task_service.cc.o
|
||||||
|
1.631 [14/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_workspace_service.cc.o
|
||||||
|
1.769 [15/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/submission_service.cc.o
|
||||||
|
2.397 [16/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/kb_import_runner.cc.o
|
||||||
|
2.508 [17/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_gen_runner.cc.o
|
||||||
|
2.607 [18/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_solution_runner.cc.o
|
||||||
|
2.607 ninja: build stopped: subcommand failed.
|
||||||
|
------
|
||||||
|
Dockerfile.backend:18
|
||||||
|
--------------------
|
||||||
|
17 |
|
||||||
|
18 | >>> RUN cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && \
|
||||||
|
19 | >>> cmake --build build --target csp_server
|
||||||
|
20 |
|
||||||
|
--------------------
|
||||||
|
ERROR: failed to build: failed to solve: process "/bin/sh -c cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build build --target csp_server" did not complete successfully: exit code: 1
|
||||||
134
build_log_3.txt
普通文件
134
build_log_3.txt
普通文件
@@ -0,0 +1,134 @@
|
|||||||
|
#0 building with "desktop-linux" instance using docker driver
|
||||||
|
|
||||||
|
#1 [internal] load build definition from Dockerfile.backend
|
||||||
|
#1 transferring dockerfile: 1.20kB done
|
||||||
|
#1 DONE 0.0s
|
||||||
|
|
||||||
|
#2 resolve image config for docker-image://docker.io/docker/dockerfile:1
|
||||||
|
#2 DONE 1.1s
|
||||||
|
|
||||||
|
#3 docker-image://docker.io/docker/dockerfile:1@sha256:b6afd42430b15f2d2a4c5a02b919e98a525b785b1aaff16747d2f623364e39b6
|
||||||
|
#3 resolve docker.io/docker/dockerfile:1@sha256:b6afd42430b15f2d2a4c5a02b919e98a525b785b1aaff16747d2f623364e39b6 0.0s done
|
||||||
|
#3 CACHED
|
||||||
|
|
||||||
|
#4 [internal] load metadata for docker.io/library/ubuntu:24.04
|
||||||
|
#4 DONE 1.1s
|
||||||
|
|
||||||
|
#5 [internal] load .dockerignore
|
||||||
|
#5 transferring context: 2B done
|
||||||
|
#5 DONE 0.0s
|
||||||
|
|
||||||
|
#6 [internal] load build context
|
||||||
|
#6 transferring context: 16.62kB done
|
||||||
|
#6 DONE 0.0s
|
||||||
|
|
||||||
|
#7 [build 1/6] FROM docker.io/library/ubuntu:24.04@sha256:cd1dba651b3080c3686ecf4e3c4220f026b521fb76978881737d24f200828b2b
|
||||||
|
#7 resolve docker.io/library/ubuntu:24.04@sha256:cd1dba651b3080c3686ecf4e3c4220f026b521fb76978881737d24f200828b2b done
|
||||||
|
#7 DONE 0.0s
|
||||||
|
|
||||||
|
#8 [build 2/6] RUN apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential cmake ninja-build pkg-config libdrogon-dev libjsoncpp-dev libyaml-cpp-dev libhiredis-dev libpq-dev libmariadb-dev libmariadb-dev-compat libsqlite3-dev sqlite3 libssl-dev uuid-dev libbrotli-dev catch2 && rm -rf /var/lib/apt/lists/*
|
||||||
|
#8 CACHED
|
||||||
|
|
||||||
|
#9 [build 3/6] WORKDIR /src
|
||||||
|
#9 CACHED
|
||||||
|
|
||||||
|
#10 [build 4/6] COPY backend/ ./backend/
|
||||||
|
#10 DONE 0.0s
|
||||||
|
|
||||||
|
#11 [build 5/6] COPY CMakeLists.txt ./
|
||||||
|
#11 DONE 0.0s
|
||||||
|
|
||||||
|
#12 [build 6/6] RUN cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build build --target csp_server
|
||||||
|
#12 0.100 -- The CXX compiler identification is GNU 13.3.0
|
||||||
|
#12 0.105 -- Detecting CXX compiler ABI info
|
||||||
|
#12 0.128 -- Detecting CXX compiler ABI info - done
|
||||||
|
#12 0.131 -- Check for working CXX compiler: /usr/bin/c++ - skipped
|
||||||
|
#12 0.131 -- Detecting CXX compile features
|
||||||
|
#12 0.132 -- Detecting CXX compile features - done
|
||||||
|
#12 0.134 -- Found Jsoncpp: /usr/include/jsoncpp
|
||||||
|
#12 0.135 -- jsoncpp verson:1.9.5
|
||||||
|
#12 0.136 -- Performing Test CMAKE_HAVE_LIBC_PTHREAD
|
||||||
|
#12 0.159 -- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success
|
||||||
|
#12 0.159 -- Found Threads: TRUE
|
||||||
|
#12 0.160 -- Found UUID: /usr/lib/aarch64-linux-gnu/libuuid.so
|
||||||
|
#12 0.162 -- Found ZLIB: /usr/lib/aarch64-linux-gnu/libz.so (found version "1.3")
|
||||||
|
#12 0.165 -- Found PostgreSQL: /usr/lib/aarch64-linux-gnu/libpq.so (found version "16.11")
|
||||||
|
#12 0.165 -- pg inc: /usr/include/postgresql
|
||||||
|
#12 0.166 -- Found pg: /usr/lib/aarch64-linux-gnu/libpq.so
|
||||||
|
#12 0.166 -- Found SQLite3: /usr/lib/aarch64-linux-gnu/libsqlite3.so
|
||||||
|
#12 0.167 -- MySQL Include dir: /usr/include/mysql
|
||||||
|
#12 0.167 -- MySQL client libraries: /usr/lib/aarch64-linux-gnu/libmysqlclient_r.so
|
||||||
|
#12 0.167 -- Found MySQL: /usr/lib/aarch64-linux-gnu/libmysqlclient_r.so
|
||||||
|
#12 0.167 -- Found Brotli: /usr/lib/aarch64-linux-gnu/libbrotlidec.so
|
||||||
|
#12 0.168 -- Found Hiredis: /usr/lib/aarch64-linux-gnu/libhiredis.so
|
||||||
|
#12 0.169 -- Looking for C++ include filesystem
|
||||||
|
#12 0.302 -- Looking for C++ include filesystem - found
|
||||||
|
#12 0.302 -- Performing Test CXX_FILESYSTEM_NO_LINK_NEEDED
|
||||||
|
#12 0.449 -- Performing Test CXX_FILESYSTEM_NO_LINK_NEEDED - Success
|
||||||
|
#12 0.461 -- Found OpenSSL: /usr/lib/aarch64-linux-gnu/libcrypto.so (found version "3.0.13")
|
||||||
|
#12 0.462 -- Configuring done (0.4s)
|
||||||
|
#12 0.466 -- Generating done (0.0s)
|
||||||
|
#12 0.467 -- Build files have been written to: /src/build
|
||||||
|
#12 0.478 [1/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/version.cc.o
|
||||||
|
#12 0.804 [2/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/crypto.cc.o
|
||||||
|
#12 0.886 [3/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/user_service.cc.o
|
||||||
|
#12 0.914 [4/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/app_state.cc.o
|
||||||
|
#12 1.102 [5/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/kb_service.cc.o
|
||||||
|
#12 1.116 [6/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/wrong_book_service.cc.o
|
||||||
|
#12 1.159 [7/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_service.cc.o
|
||||||
|
#12 1.230 [8/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/auth_service.cc.o
|
||||||
|
#12 1.269 [9/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/daily_task_service.cc.o
|
||||||
|
#12 1.421 [10/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/solution_access_service.cc.o
|
||||||
|
#12 1.482 [11/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/contest_service.cc.o
|
||||||
|
#12 1.624 [12/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/domain/enum_strings.cc.o
|
||||||
|
#12 1.670 [13/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/db/sqlite_db.cc.o
|
||||||
|
#12 1.775 [14/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/redeem_service.cc.o
|
||||||
|
#12 1.876 [15/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/domain/json.cc.o
|
||||||
|
#12 2.009 [16/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/submission_service.cc.o
|
||||||
|
#12 2.083 [17/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/import_service.cc.o
|
||||||
|
#12 2.244 [18/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/submission_feedback_service.cc.o
|
||||||
|
#12 2.313 [19/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_workspace_service.cc.o
|
||||||
|
#12 3.237 [20/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/submission_controller.cc.o
|
||||||
|
#12 3.238 FAILED: backend/CMakeFiles/csp_web.dir/src/controllers/submission_controller.cc.o
|
||||||
|
#12 3.238 /usr/bin/c++ -DHAS_YAML_CPP -I/src/backend/include -isystem /usr/include/jsoncpp -O3 -DNDEBUG -std=c++20 -MD -MT backend/CMakeFiles/csp_web.dir/src/controllers/submission_controller.cc.o -MF backend/CMakeFiles/csp_web.dir/src/controllers/submission_controller.cc.o.d -o backend/CMakeFiles/csp_web.dir/src/controllers/submission_controller.cc.o -c /src/backend/src/controllers/submission_controller.cc
|
||||||
|
#12 3.239 /src/backend/src/controllers/submission_controller.cc: In member function 'void csp::controllers::SubmissionController::getSubmission(const drogon::HttpRequestPtr&, std::function<void(const std::shared_ptr<drogon::HttpResponse>&)>&&, int64_t)':
|
||||||
|
#12 3.239 /src/backend/src/controllers/submission_controller.cc:196:30: error: request for member 'has_value' in 'stats.csp::services::SolutionViewStats::last_viewed_at', which is of non-class type 'const int64_t' {aka 'const long int'}
|
||||||
|
#12 3.239 196 | if (stats.last_viewed_at.has_value()) {
|
||||||
|
#12 3.239 | ^~~~~~~~~
|
||||||
|
#12 3.239 /src/backend/src/controllers/submission_controller.cc:197:52: error: invalid type argument of unary '*' (have 'int64_t' {aka 'long int'})
|
||||||
|
#12 3.239 197 | payload["last_answer_view_at"] = Json::Int64(*stats.last_viewed_at);
|
||||||
|
#12 3.239 | ^~~~~~~~~~~~~~~~~~~~~
|
||||||
|
#12 3.927 [21/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/kb_import_runner.cc.o
|
||||||
|
#12 3.949 [22/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/auth_controller.cc.o
|
||||||
|
#12 3.981 [23/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/leaderboard_controller.cc.o
|
||||||
|
#12 3.984 [24/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/import_runner.cc.o
|
||||||
|
#12 4.183 [25/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_gen_runner.cc.o
|
||||||
|
#12 4.201 [26/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_solution_runner.cc.o
|
||||||
|
#12 4.309 [27/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/contest_controller.cc.o
|
||||||
|
#12 4.355 [28/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/kb_controller.cc.o
|
||||||
|
#12 4.523 [29/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/problem_controller.cc.o
|
||||||
|
#12 4.569 [30/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/admin_controller.cc.o
|
||||||
|
#12 4.781 [31/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/me_controller.cc.o
|
||||||
|
#12 4.781 ninja: build stopped: subcommand failed.
|
||||||
|
#12 ERROR: process "/bin/sh -c cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build build --target csp_server" did not complete successfully: exit code: 1
|
||||||
|
------
|
||||||
|
> [build 6/6] RUN cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build build --target csp_server:
|
||||||
|
3.981 [23/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/leaderboard_controller.cc.o
|
||||||
|
3.984 [24/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/import_runner.cc.o
|
||||||
|
4.183 [25/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_gen_runner.cc.o
|
||||||
|
4.201 [26/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_solution_runner.cc.o
|
||||||
|
4.309 [27/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/contest_controller.cc.o
|
||||||
|
4.355 [28/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/kb_controller.cc.o
|
||||||
|
4.523 [29/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/problem_controller.cc.o
|
||||||
|
4.569 [30/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/admin_controller.cc.o
|
||||||
|
4.781 [31/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/me_controller.cc.o
|
||||||
|
4.781 ninja: build stopped: subcommand failed.
|
||||||
|
------
|
||||||
|
Dockerfile.backend:18
|
||||||
|
--------------------
|
||||||
|
17 |
|
||||||
|
18 | >>> RUN cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && \
|
||||||
|
19 | >>> cmake --build build --target csp_server
|
||||||
|
20 |
|
||||||
|
--------------------
|
||||||
|
ERROR: failed to build: failed to solve: process "/bin/sh -c cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build build --target csp_server" did not complete successfully: exit code: 1
|
||||||
133
build_log_4.txt
普通文件
133
build_log_4.txt
普通文件
@@ -0,0 +1,133 @@
|
|||||||
|
#0 building with "desktop-linux" instance using docker driver
|
||||||
|
|
||||||
|
#1 [internal] load build definition from Dockerfile.backend
|
||||||
|
#1 transferring dockerfile: 1.20kB done
|
||||||
|
#1 DONE 0.0s
|
||||||
|
|
||||||
|
#2 resolve image config for docker-image://docker.io/docker/dockerfile:1
|
||||||
|
#2 DONE 1.2s
|
||||||
|
|
||||||
|
#3 docker-image://docker.io/docker/dockerfile:1@sha256:b6afd42430b15f2d2a4c5a02b919e98a525b785b1aaff16747d2f623364e39b6
|
||||||
|
#3 resolve docker.io/docker/dockerfile:1@sha256:b6afd42430b15f2d2a4c5a02b919e98a525b785b1aaff16747d2f623364e39b6 0.0s done
|
||||||
|
#3 CACHED
|
||||||
|
|
||||||
|
#4 [internal] load metadata for docker.io/library/ubuntu:24.04
|
||||||
|
#4 DONE 0.8s
|
||||||
|
|
||||||
|
#5 [internal] load .dockerignore
|
||||||
|
#5 transferring context: 2B done
|
||||||
|
#5 DONE 0.0s
|
||||||
|
|
||||||
|
#6 [internal] load build context
|
||||||
|
#6 transferring context: 8.05kB done
|
||||||
|
#6 DONE 0.0s
|
||||||
|
|
||||||
|
#7 [build 1/6] FROM docker.io/library/ubuntu:24.04@sha256:cd1dba651b3080c3686ecf4e3c4220f026b521fb76978881737d24f200828b2b
|
||||||
|
#7 resolve docker.io/library/ubuntu:24.04@sha256:cd1dba651b3080c3686ecf4e3c4220f026b521fb76978881737d24f200828b2b done
|
||||||
|
#7 DONE 0.0s
|
||||||
|
|
||||||
|
#8 [build 2/6] RUN apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential cmake ninja-build pkg-config libdrogon-dev libjsoncpp-dev libyaml-cpp-dev libhiredis-dev libpq-dev libmariadb-dev libmariadb-dev-compat libsqlite3-dev sqlite3 libssl-dev uuid-dev libbrotli-dev catch2 && rm -rf /var/lib/apt/lists/*
|
||||||
|
#8 CACHED
|
||||||
|
|
||||||
|
#9 [build 3/6] WORKDIR /src
|
||||||
|
#9 CACHED
|
||||||
|
|
||||||
|
#10 [build 4/6] COPY backend/ ./backend/
|
||||||
|
#10 DONE 0.0s
|
||||||
|
|
||||||
|
#11 [build 5/6] COPY CMakeLists.txt ./
|
||||||
|
#11 DONE 0.0s
|
||||||
|
|
||||||
|
#12 [build 6/6] RUN cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build build --target csp_server
|
||||||
|
#12 0.122 -- The CXX compiler identification is GNU 13.3.0
|
||||||
|
#12 0.130 -- Detecting CXX compiler ABI info
|
||||||
|
#12 0.155 -- Detecting CXX compiler ABI info - done
|
||||||
|
#12 0.159 -- Check for working CXX compiler: /usr/bin/c++ - skipped
|
||||||
|
#12 0.159 -- Detecting CXX compile features
|
||||||
|
#12 0.159 -- Detecting CXX compile features - done
|
||||||
|
#12 0.167 -- Found Jsoncpp: /usr/include/jsoncpp
|
||||||
|
#12 0.169 -- jsoncpp verson:1.9.5
|
||||||
|
#12 0.170 -- Performing Test CMAKE_HAVE_LIBC_PTHREAD
|
||||||
|
#12 0.194 -- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success
|
||||||
|
#12 0.195 -- Found Threads: TRUE
|
||||||
|
#12 0.196 -- Found UUID: /usr/lib/aarch64-linux-gnu/libuuid.so
|
||||||
|
#12 0.199 -- Found ZLIB: /usr/lib/aarch64-linux-gnu/libz.so (found version "1.3")
|
||||||
|
#12 0.203 -- Found PostgreSQL: /usr/lib/aarch64-linux-gnu/libpq.so (found version "16.11")
|
||||||
|
#12 0.203 -- pg inc: /usr/include/postgresql
|
||||||
|
#12 0.203 -- Found pg: /usr/lib/aarch64-linux-gnu/libpq.so
|
||||||
|
#12 0.204 -- Found SQLite3: /usr/lib/aarch64-linux-gnu/libsqlite3.so
|
||||||
|
#12 0.204 -- MySQL Include dir: /usr/include/mysql
|
||||||
|
#12 0.204 -- MySQL client libraries: /usr/lib/aarch64-linux-gnu/libmysqlclient_r.so
|
||||||
|
#12 0.205 -- Found MySQL: /usr/lib/aarch64-linux-gnu/libmysqlclient_r.so
|
||||||
|
#12 0.206 -- Found Brotli: /usr/lib/aarch64-linux-gnu/libbrotlidec.so
|
||||||
|
#12 0.206 -- Found Hiredis: /usr/lib/aarch64-linux-gnu/libhiredis.so
|
||||||
|
#12 0.208 -- Looking for C++ include filesystem
|
||||||
|
#12 0.333 -- Looking for C++ include filesystem - found
|
||||||
|
#12 0.333 -- Performing Test CXX_FILESYSTEM_NO_LINK_NEEDED
|
||||||
|
#12 0.471 -- Performing Test CXX_FILESYSTEM_NO_LINK_NEEDED - Success
|
||||||
|
#12 0.488 -- Found OpenSSL: /usr/lib/aarch64-linux-gnu/libcrypto.so (found version "3.0.13")
|
||||||
|
#12 0.490 -- Configuring done (0.4s)
|
||||||
|
#12 0.495 -- Generating done (0.0s)
|
||||||
|
#12 0.496 -- Build files have been written to: /src/build
|
||||||
|
#12 0.508 [1/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/version.cc.o
|
||||||
|
#12 0.731 [2/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/app_state.cc.o
|
||||||
|
#12 0.918 [3/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/kb_service.cc.o
|
||||||
|
#12 0.931 [4/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/user_service.cc.o
|
||||||
|
#12 1.049 [5/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/crypto.cc.o
|
||||||
|
#12 1.108 [6/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/wrong_book_service.cc.o
|
||||||
|
#12 1.165 [7/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/auth_service.cc.o
|
||||||
|
#12 1.332 [8/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/contest_service.cc.o
|
||||||
|
#12 1.352 [9/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/solution_access_service.cc.o
|
||||||
|
#12 1.386 [10/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/daily_task_service.cc.o
|
||||||
|
#12 1.551 [11/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/db/sqlite_db.cc.o
|
||||||
|
#12 1.609 [12/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/domain/enum_strings.cc.o
|
||||||
|
#12 1.648 [13/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/redeem_service.cc.o
|
||||||
|
#12 1.971 [14/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/import_service.cc.o
|
||||||
|
#12 1.997 [15/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_workspace_service.cc.o
|
||||||
|
#12 2.010 [16/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_service.cc.o
|
||||||
|
#12 2.045 [17/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/domain/json.cc.o
|
||||||
|
#12 2.377 [18/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/submission_feedback_service.cc.o
|
||||||
|
#12 2.681 [19/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/submission_service.cc.o
|
||||||
|
#12 3.305 [20/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/kb_import_runner.cc.o
|
||||||
|
#12 3.504 [21/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_gen_runner.cc.o
|
||||||
|
#12 3.565 [22/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/auth_controller.cc.o
|
||||||
|
#12 3.919 [23/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/leaderboard_controller.cc.o
|
||||||
|
#12 3.924 [24/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/import_runner.cc.o
|
||||||
|
#12 4.325 [25/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/problem_controller.cc.o
|
||||||
|
#12 4.487 [26/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/me_controller.cc.o
|
||||||
|
#12 4.490 [27/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/contest_controller.cc.o
|
||||||
|
#12 4.735 [28/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/kb_controller.cc.o
|
||||||
|
#12 4.820 [29/39] Building CXX object backend/CMakeFiles/csp_core.dir/src/services/problem_solution_runner.cc.o
|
||||||
|
#12 4.844 [30/39] Linking CXX static library backend/libcsp_core.a
|
||||||
|
#12 5.041 [31/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/admin_controller.cc.o
|
||||||
|
#12 5.063 [32/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/problem_gen_controller.cc.o
|
||||||
|
#12 5.221 [33/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/import_controller.cc.o
|
||||||
|
#12 5.286 [34/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/health_controller.cc.o
|
||||||
|
#12 5.313 [35/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/submission_controller.cc.o
|
||||||
|
#12 5.480 [36/39] Building CXX object backend/CMakeFiles/csp_web.dir/src/controllers/meta_controller.cc.o
|
||||||
|
#12 5.490 [37/39] Linking CXX static library backend/libcsp_web.a
|
||||||
|
#12 5.585 [38/39] Building CXX object backend/CMakeFiles/csp_server.dir/src/main.cc.o
|
||||||
|
#12 5.651 [39/39] Linking CXX executable backend/csp_server
|
||||||
|
#12 DONE 5.7s
|
||||||
|
|
||||||
|
#13 [runtime 2/5] RUN apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y libdrogon1t64 libjsoncpp25 libyaml-cpp0.8 libhiredis1.1.0 libpq5 libmariadb3 libsqlite3-0 libssl3t64 libuuid1 ca-certificates g++ python3 python3-requests curl poppler-utils && rm -rf /var/lib/apt/lists/*
|
||||||
|
#13 CACHED
|
||||||
|
|
||||||
|
#14 [runtime 3/5] WORKDIR /app
|
||||||
|
#14 CACHED
|
||||||
|
|
||||||
|
#15 [runtime 4/5] COPY --from=build /src/build/backend/csp_server /app/csp_server
|
||||||
|
#15 DONE 0.0s
|
||||||
|
|
||||||
|
#16 [runtime 5/5] COPY scripts/ /app/scripts/
|
||||||
|
#16 DONE 0.0s
|
||||||
|
|
||||||
|
#17 exporting to image
|
||||||
|
#17 exporting layers 0.0s done
|
||||||
|
#17 exporting manifest sha256:3e6ae247b75c340ad6d4b4012cda30a46a1bed1ed49812c302cf93638f605d83 done
|
||||||
|
#17 exporting config sha256:83fdffca6fc19370c879d0a70582b6ee4085e6da60e701b14f7c29eb37c71dfc done
|
||||||
|
#17 exporting attestation manifest sha256:4146efbd2d242f9cfd3c4d0fd278a676352f49a1c03dcaa0974cc458358d24cf done
|
||||||
|
#17 exporting manifest list sha256:cd1c5234b803f57010cb76045f63ea8cab1e22fe331834b30b2da88ebd049156 done
|
||||||
|
#17 naming to moby-dangling@sha256:cd1c5234b803f57010cb76045f63ea8cab1e22fe331834b30b2da88ebd049156 done
|
||||||
|
#17 unpacking to moby-dangling@sha256:cd1c5234b803f57010cb76045f63ea8cab1e22fe331834b30b2da88ebd049156 done
|
||||||
|
#17 DONE 0.1s
|
||||||
10
frontend/package-lock.json
自动生成的
10
frontend/package-lock.json
自动生成的
@@ -11,6 +11,7 @@
|
|||||||
"@monaco-editor/react": "^4.7.0",
|
"@monaco-editor/react": "^4.7.0",
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"katex": "^0.16.28",
|
"katex": "^0.16.28",
|
||||||
|
"lucide-react": "^0.564.0",
|
||||||
"next": "16.1.6",
|
"next": "16.1.6",
|
||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
"react-dom": "19.2.3",
|
"react-dom": "19.2.3",
|
||||||
@@ -6321,6 +6322,15 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lucide-react": {
|
||||||
|
"version": "0.564.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.564.0.tgz",
|
||||||
|
"integrity": "sha512-JJ8GVTQqFwuliifD48U6+h7DXEHdkhJ/E87kksGByII3qHxtPciVb8T8woQONHBQgHVOl7rSMrrip3SeVNy7Fg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.21",
|
"version": "0.30.21",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"@monaco-editor/react": "^4.7.0",
|
"@monaco-editor/react": "^4.7.0",
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"katex": "^0.16.28",
|
"katex": "^0.16.28",
|
||||||
|
"lucide-react": "^0.564.0",
|
||||||
"next": "16.1.6",
|
"next": "16.1.6",
|
||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
"react-dom": "19.2.3",
|
"react-dom": "19.2.3",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { apiFetch } from "@/lib/api";
|
import { apiFetch } from "@/lib/api";
|
||||||
import { readToken } from "@/lib/auth";
|
import { readToken } from "@/lib/auth";
|
||||||
import { useI18nText } from "@/lib/i18n";
|
import { useI18nText } from "@/lib/i18n";
|
||||||
|
import { Edit, Gift, Plus, RefreshCw, ScrollText, Search, Trash2, Coins } from "lucide-react";
|
||||||
|
|
||||||
type RedeemItem = {
|
type RedeemItem = {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -167,7 +168,8 @@ export default function AdminRedeemPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-7xl px-3 py-6 max-[390px]:px-2 sm:px-4 md:px-6 md:py-8">
|
<main className="mx-auto max-w-7xl px-3 py-6 max-[390px]:px-2 sm:px-4 md:px-6 md:py-8">
|
||||||
<h1 className="text-xl font-semibold max-[390px]:text-lg sm:text-2xl">
|
<h1 className="text-xl font-semibold max-[390px]:text-lg sm:text-2xl flex items-center gap-2">
|
||||||
|
<Gift size={24} />
|
||||||
{tx("管理员:积分兑换管理", "Admin: Redeem Management")}
|
{tx("管理员:积分兑换管理", "Admin: Redeem Management")}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-1 text-sm text-zinc-600">
|
<p className="mt-1 text-sm text-zinc-600">
|
||||||
@@ -241,10 +243,17 @@ export default function AdminRedeemPage() {
|
|||||||
|
|
||||||
<div className="mt-3 flex flex-wrap gap-2">
|
<div className="mt-3 flex flex-wrap gap-2">
|
||||||
<button
|
<button
|
||||||
className="rounded bg-zinc-900 px-4 py-2 text-sm text-white disabled:opacity-50"
|
className="rounded bg-zinc-900 px-4 py-2 text-sm text-white disabled:opacity-50 flex items-center gap-2"
|
||||||
onClick={() => void submit()}
|
onClick={() => void submit()}
|
||||||
disabled={saving}
|
disabled={saving}
|
||||||
>
|
>
|
||||||
|
{saving ? (
|
||||||
|
<RefreshCw size={14} className="animate-spin" />
|
||||||
|
) : editingId ? (
|
||||||
|
<Edit size={14} />
|
||||||
|
) : (
|
||||||
|
<Plus size={14} />
|
||||||
|
)}
|
||||||
{saving
|
{saving
|
||||||
? tx("保存中...", "Saving...")
|
? tx("保存中...", "Saving...")
|
||||||
: editingId
|
: editingId
|
||||||
@@ -261,17 +270,21 @@ export default function AdminRedeemPage() {
|
|||||||
{tx("清空表单", "Clear form")}
|
{tx("清空表单", "Clear form")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="rounded border px-4 py-2 text-sm"
|
className="rounded border px-4 py-2 text-sm flex items-center gap-2"
|
||||||
onClick={() => void load()}
|
onClick={() => void load()}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
|
<RefreshCw size={14} className={loading ? "animate-spin" : ""} />
|
||||||
{tx("刷新数据", "Refresh data")}
|
{tx("刷新数据", "Refresh data")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="mt-4 rounded-xl border bg-white p-4">
|
<section className="mt-4 rounded-xl border bg-white p-4">
|
||||||
<h2 className="text-base font-semibold">{tx("兑换物品列表", "Redeem Items")}</h2>
|
<h2 className="text-base font-semibold flex items-center gap-2">
|
||||||
|
<Coins size={18} />
|
||||||
|
{tx("兑换物品列表", "Redeem Items")}
|
||||||
|
</h2>
|
||||||
<div className="mt-3 divide-y">
|
<div className="mt-3 divide-y">
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<article key={item.id} className="py-2 text-sm">
|
<article key={item.id} className="py-2 text-sm">
|
||||||
@@ -291,10 +304,12 @@ export default function AdminRedeemPage() {
|
|||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-zinc-500">{item.description || "-"}</p>
|
<p className="text-xs text-zinc-500">{item.description || "-"}</p>
|
||||||
<div className="mt-1 flex gap-2">
|
<div className="mt-1 flex gap-2">
|
||||||
<button className="rounded border px-2 py-1 text-xs" onClick={() => edit(item)}>
|
<button className="rounded border px-2 py-1 text-xs flex items-center gap-1" onClick={() => edit(item)}>
|
||||||
|
<Edit size={12} />
|
||||||
{tx("编辑", "Edit")}
|
{tx("编辑", "Edit")}
|
||||||
</button>
|
</button>
|
||||||
<button className="rounded border px-2 py-1 text-xs" onClick={() => void deactivate(item.id)}>
|
<button className="rounded border px-2 py-1 text-xs flex items-center gap-1" onClick={() => void deactivate(item.id)}>
|
||||||
|
<Trash2 size={12} />
|
||||||
{tx("下架", "Disable")}
|
{tx("下架", "Disable")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -308,7 +323,10 @@ export default function AdminRedeemPage() {
|
|||||||
|
|
||||||
<section className="mt-4 rounded-xl border bg-white p-4">
|
<section className="mt-4 rounded-xl border bg-white p-4">
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<h2 className="text-base font-semibold">{tx("兑换记录", "Redeem Records")}</h2>
|
<h2 className="text-base font-semibold flex items-center gap-2">
|
||||||
|
<ScrollText size={18} />
|
||||||
|
{tx("兑换记录", "Redeem Records")}
|
||||||
|
</h2>
|
||||||
<input
|
<input
|
||||||
className="rounded border px-3 py-1 text-xs"
|
className="rounded border px-3 py-1 text-xs"
|
||||||
placeholder={tx("按 user_id 筛选(可选)", "Filter by user_id (optional)")}
|
placeholder={tx("按 user_id 筛选(可选)", "Filter by user_id (optional)")}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { apiFetch } from "@/lib/api";
|
import { apiFetch } from "@/lib/api";
|
||||||
import { readToken } from "@/lib/auth";
|
import { readToken } from "@/lib/auth";
|
||||||
import { useI18nText } from "@/lib/i18n";
|
import { useI18nText } from "@/lib/i18n";
|
||||||
|
import { RefreshCw, Save, Shield, UserCog, Users } from "lucide-react";
|
||||||
|
|
||||||
type AdminUser = {
|
type AdminUser = {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -75,7 +76,8 @@ export default function AdminUsersPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-6xl px-3 py-6 max-[390px]:px-2 sm:px-4 md:px-6 md:py-8">
|
<main className="mx-auto max-w-6xl px-3 py-6 max-[390px]:px-2 sm:px-4 md:px-6 md:py-8">
|
||||||
<h1 className="text-xl font-semibold max-[390px]:text-lg sm:text-2xl">
|
<h1 className="text-xl font-semibold max-[390px]:text-lg sm:text-2xl flex items-center gap-2">
|
||||||
|
<Users size={24} />
|
||||||
{tx("管理员用户与积分", "Admin Users & Rating")}
|
{tx("管理员用户与积分", "Admin Users & Rating")}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-2 text-sm text-zinc-600">
|
<p className="mt-2 text-sm text-zinc-600">
|
||||||
@@ -85,10 +87,11 @@ export default function AdminUsersPage() {
|
|||||||
|
|
||||||
<div className="mt-4 flex flex-wrap gap-2">
|
<div className="mt-4 flex flex-wrap gap-2">
|
||||||
<button
|
<button
|
||||||
className="rounded bg-zinc-900 px-4 py-2 text-sm text-white disabled:opacity-50"
|
className="rounded bg-zinc-900 px-4 py-2 text-sm text-white disabled:opacity-50 flex items-center gap-2"
|
||||||
onClick={() => void load()}
|
onClick={() => void load()}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
|
<RefreshCw size={16} className={loading ? "animate-spin" : ""} />
|
||||||
{loading ? tx("刷新中...", "Refreshing...") : tx("刷新用户列表", "Refresh users")}
|
{loading ? tx("刷新中...", "Refreshing...") : tx("刷新用户列表", "Refresh users")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -103,7 +106,10 @@ export default function AdminUsersPage() {
|
|||||||
<tr>
|
<tr>
|
||||||
<th className="px-3 py-2">ID</th>
|
<th className="px-3 py-2">ID</th>
|
||||||
<th className="px-3 py-2">{tx("用户名", "Username")}</th>
|
<th className="px-3 py-2">{tx("用户名", "Username")}</th>
|
||||||
<th className="px-3 py-2">Rating</th>
|
<th className="px-3 py-2 flex items-center gap-1">
|
||||||
|
<Shield size={14} />
|
||||||
|
Rating
|
||||||
|
</th>
|
||||||
<th className="px-3 py-2">{tx("创建时间", "Created At")}</th>
|
<th className="px-3 py-2">{tx("创建时间", "Created At")}</th>
|
||||||
<th className="px-3 py-2">{tx("操作", "Action")}</th>
|
<th className="px-3 py-2">{tx("操作", "Action")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -130,9 +136,10 @@ export default function AdminUsersPage() {
|
|||||||
<td className="px-3 py-2 text-zinc-600">{fmtTs(user.created_at)}</td>
|
<td className="px-3 py-2 text-zinc-600">{fmtTs(user.created_at)}</td>
|
||||||
<td className="px-3 py-2">
|
<td className="px-3 py-2">
|
||||||
<button
|
<button
|
||||||
className="rounded border px-3 py-1 text-xs hover:bg-zinc-100"
|
className="rounded border px-3 py-1 text-xs hover:bg-zinc-100 flex items-center gap-1"
|
||||||
onClick={() => void updateRating(user.id, Math.max(0, Number(user.rating) || 0))}
|
onClick={() => void updateRating(user.id, Math.max(0, Number(user.rating) || 0))}
|
||||||
>
|
>
|
||||||
|
<Save size={12} />
|
||||||
{tx("保存", "Save")}
|
{tx("保存", "Save")}
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { FileCode, ArrowLeft } from "lucide-react";
|
||||||
|
|
||||||
import { API_BASE, apiFetch } from "@/lib/api";
|
import { API_BASE, apiFetch } from "@/lib/api";
|
||||||
import { readToken } from "@/lib/auth";
|
import { readToken } from "@/lib/auth";
|
||||||
@@ -71,9 +73,16 @@ export default function ApiDocsPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-7xl px-3 py-5 max-[390px]:px-2 sm:px-4 md:px-6 md:py-6">
|
<main className="mx-auto max-w-7xl px-3 py-5 max-[390px]:px-2 sm:px-4 md:px-6 md:py-6">
|
||||||
<h1 className="mb-4 text-xl font-semibold max-[390px]:text-lg sm:text-2xl">
|
<div className="mb-4 flex flex-wrap items-center justify-between gap-3">
|
||||||
|
<h1 className="text-xl font-semibold max-[390px]:text-lg sm:text-2xl flex items-center gap-2">
|
||||||
|
<FileCode size={24} />
|
||||||
{tx("API 文档(Swagger)", "API Docs (Swagger)")}
|
{tx("API 文档(Swagger)", "API Docs (Swagger)")}
|
||||||
</h1>
|
</h1>
|
||||||
|
<Link href="/imports" className="flex items-center gap-1 text-sm text-zinc-600 hover:text-zinc-900">
|
||||||
|
<ArrowLeft size={16} />
|
||||||
|
{tx("返回", "Back")}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
<div className="overflow-x-auto rounded-xl border bg-white p-2">
|
<div className="overflow-x-auto rounded-xl border bg-white p-2">
|
||||||
<SwaggerUI url={specUrl} docExpansion="list" defaultModelsExpandDepth={1} />
|
<SwaggerUI url={specUrl} docExpansion="list" defaultModelsExpandDepth={1} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import { Eye, EyeOff, Key, LogIn, User, UserPlus } from "lucide-react";
|
||||||
|
|
||||||
import { API_BASE, apiFetch } from "@/lib/api";
|
import { API_BASE, apiFetch } from "@/lib/api";
|
||||||
import { readToken, saveToken } from "@/lib/auth";
|
import { readToken, saveToken } from "@/lib/auth";
|
||||||
@@ -104,7 +105,7 @@ export default function AuthPage() {
|
|||||||
<div className="grid grid-cols-2 gap-2 rounded-none bg-black/20 p-1 text-sm">
|
<div className="grid grid-cols-2 gap-2 rounded-none bg-black/20 p-1 text-sm">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`rounded-none px-3 py-2 border-[2px] transition-all ${mode === "login"
|
className={`rounded-none px-3 py-2 border-[2px] transition-all flex items-center justify-center gap-2 ${mode === "login"
|
||||||
? "bg-[color:var(--mc-wood)] border-black text-white shadow-[2px_2px_0_rgba(0,0,0,0.4)]"
|
? "bg-[color:var(--mc-wood)] border-black text-white shadow-[2px_2px_0_rgba(0,0,0,0.4)]"
|
||||||
: "border-transparent text-zinc-500 hover:text-zinc-300"
|
: "border-transparent text-zinc-500 hover:text-zinc-300"
|
||||||
}`}
|
}`}
|
||||||
@@ -114,11 +115,12 @@ export default function AuthPage() {
|
|||||||
}}
|
}}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
|
<LogIn size={16} />
|
||||||
{tx("登录服务器", "Login")}
|
{tx("登录服务器", "Login")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`rounded-none px-3 py-2 border-[2px] transition-all ${mode === "register"
|
className={`rounded-none px-3 py-2 border-[2px] transition-all flex items-center justify-center gap-2 ${mode === "register"
|
||||||
? "bg-[color:var(--mc-wood)] border-black text-white shadow-[2px_2px_0_rgba(0,0,0,0.4)]"
|
? "bg-[color:var(--mc-wood)] border-black text-white shadow-[2px_2px_0_rgba(0,0,0,0.4)]"
|
||||||
: "border-transparent text-zinc-500 hover:text-zinc-300"
|
: "border-transparent text-zinc-500 hover:text-zinc-300"
|
||||||
}`}
|
}`}
|
||||||
@@ -128,13 +130,17 @@ export default function AuthPage() {
|
|||||||
}}
|
}}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
|
<UserPlus size={16} />
|
||||||
{tx("新玩家注册", "New Player")}
|
{tx("新玩家注册", "New Player")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-5 space-y-4 font-mono">
|
<div className="mt-5 space-y-4 font-mono">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm font-bold text-[color:var(--mc-stone)]">{tx("玩家代号", "Username")}</label>
|
<label className="text-sm font-bold text-[color:var(--mc-stone)] flex items-center gap-2">
|
||||||
|
<User size={14} />
|
||||||
|
{tx("玩家代号", "Username")}
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
className="mt-1 w-full"
|
className="mt-1 w-full"
|
||||||
value={username}
|
value={username}
|
||||||
@@ -146,7 +152,10 @@ export default function AuthPage() {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="text-sm font-bold text-[color:var(--mc-stone)]">{tx("极其机密的口令", "Secret Password")}</label>
|
<label className="text-sm font-bold text-[color:var(--mc-stone)] flex items-center gap-2">
|
||||||
|
<Key size={14} />
|
||||||
|
{tx("极其机密的口令", "Secret Password")}
|
||||||
|
</label>
|
||||||
<span className={`text-xs ${strength.color}`}>{strength.label}</span>
|
<span className={`text-xs ${strength.color}`}>{strength.label}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
@@ -161,7 +170,10 @@ export default function AuthPage() {
|
|||||||
|
|
||||||
{mode === "register" && (
|
{mode === "register" && (
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm font-bold text-[color:var(--mc-stone)]">{tx("确认口令", "Confirm Secret")}</label>
|
<label className="text-sm font-bold text-[color:var(--mc-stone)] flex items-center gap-2">
|
||||||
|
<Key size={14} />
|
||||||
|
{tx("确认口令", "Confirm Secret")}
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type={showPassword ? "text" : "password"}
|
type={showPassword ? "text" : "password"}
|
||||||
className="mt-1 w-full"
|
className="mt-1 w-full"
|
||||||
@@ -180,6 +192,7 @@ export default function AuthPage() {
|
|||||||
onChange={(e) => setShowPassword(e.target.checked)}
|
onChange={(e) => setShowPassword(e.target.checked)}
|
||||||
className="accent-[color:var(--mc-wood)]"
|
className="accent-[color:var(--mc-wood)]"
|
||||||
/>
|
/>
|
||||||
|
{showPassword ? <EyeOff size={14} /> : <Eye size={14} />}
|
||||||
{tx("显示口令", "Reveal Secret")}
|
{tx("显示口令", "Reveal Secret")}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { apiFetch } from "@/lib/api";
|
import { apiFetch } from "@/lib/api";
|
||||||
import { readToken } from "@/lib/auth";
|
import { readToken } from "@/lib/auth";
|
||||||
import { useI18nText } from "@/lib/i18n";
|
import { useI18nText } from "@/lib/i18n";
|
||||||
|
import { Activity, AlertCircle, List, Play, RefreshCw, Server, Trash2, Zap } from "lucide-react";
|
||||||
|
|
||||||
type BackendLogItem = {
|
type BackendLogItem = {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -277,7 +278,8 @@ export default function BackendLogsPage() {
|
|||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-7xl px-3 py-6 max-[390px]:px-2 sm:px-4 md:px-6 md:py-8">
|
<main className="mx-auto max-w-7xl px-3 py-6 max-[390px]:px-2 sm:px-4 md:px-6 md:py-8">
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||||
<h1 className="text-xl font-semibold max-[390px]:text-lg sm:text-2xl">
|
<h1 className="text-xl font-semibold max-[390px]:text-lg sm:text-2xl flex items-center gap-2">
|
||||||
|
<Server size={24} />
|
||||||
{tx("后台日志(题解异步队列)", "Backend Logs (Async Solution Queue)")}
|
{tx("后台日志(题解异步队列)", "Backend Logs (Async Solution Queue)")}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="flex w-full flex-wrap items-center gap-2 text-sm sm:w-auto sm:justify-end">
|
<div className="flex w-full flex-wrap items-center gap-2 text-sm sm:w-auto sm:justify-end">
|
||||||
@@ -288,10 +290,11 @@ export default function BackendLogsPage() {
|
|||||||
{tx("缺失答案题目", "Problems missing answers")} {missingProblems}
|
{tx("缺失答案题目", "Problems missing answers")} {missingProblems}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
className="rounded border px-3 py-1 disabled:opacity-50"
|
className="rounded border px-3 py-1 disabled:opacity-50 flex items-center gap-1"
|
||||||
onClick={() => void triggerMissingSolutions()}
|
onClick={() => void triggerMissingSolutions()}
|
||||||
disabled={triggerLoading}
|
disabled={triggerLoading}
|
||||||
>
|
>
|
||||||
|
<Zap size={14} />
|
||||||
{triggerLoading ? tx("手动补全中...", "Triggering...") : tx("手动补全(可选)", "Manual fill (optional)")}
|
{triggerLoading ? tx("手动补全中...", "Triggering...") : tx("手动补全(可选)", "Manual fill (optional)")}
|
||||||
</button>
|
</button>
|
||||||
<select
|
<select
|
||||||
@@ -303,7 +306,8 @@ export default function BackendLogsPage() {
|
|||||||
<option value={100}>{tx("最近 100 条", "Latest 100")}</option>
|
<option value={100}>{tx("最近 100 条", "Latest 100")}</option>
|
||||||
<option value={200}>{tx("最近 200 条", "Latest 200")}</option>
|
<option value={200}>{tx("最近 200 条", "Latest 200")}</option>
|
||||||
</select>
|
</select>
|
||||||
<button className="rounded border px-3 py-1 sm:ml-auto" onClick={() => void refresh()} disabled={loading}>
|
<button className="rounded border px-3 py-1 sm:ml-auto flex items-center gap-1" onClick={() => void refresh()} disabled={loading}>
|
||||||
|
<RefreshCw size={14} className={loading ? "animate-spin" : ""} />
|
||||||
{tx("刷新", "Refresh")}
|
{tx("刷新", "Refresh")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -401,7 +405,10 @@ export default function BackendLogsPage() {
|
|||||||
|
|
||||||
<section className="mt-4 grid gap-3 md:grid-cols-2">
|
<section className="mt-4 grid gap-3 md:grid-cols-2">
|
||||||
<article className="rounded-xl border bg-white p-3">
|
<article className="rounded-xl border bg-white p-3">
|
||||||
<h2 className="text-sm font-medium">{tx("正在处理(Running)", "Running Jobs")}</h2>
|
<h2 className="text-sm font-medium flex items-center gap-2">
|
||||||
|
<Activity size={16} className="text-emerald-600" />
|
||||||
|
{tx("正在处理(Running)", "Running Jobs")}
|
||||||
|
</h2>
|
||||||
<p className="mt-1 text-xs text-zinc-600">
|
<p className="mt-1 text-xs text-zinc-600">
|
||||||
{tx("当前题目 ID:", "Current problem IDs:")}
|
{tx("当前题目 ID:", "Current problem IDs:")}
|
||||||
{runningIds.length ? runningIds.join(", ") : tx("无", "None")}
|
{runningIds.length ? runningIds.join(", ") : tx("无", "None")}
|
||||||
@@ -423,7 +430,10 @@ export default function BackendLogsPage() {
|
|||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article className="rounded-xl border bg-white p-3">
|
<article className="rounded-xl border bg-white p-3">
|
||||||
<h2 className="text-sm font-medium">{tx("待处理队列(Queued)", "Queued Jobs")}</h2>
|
<h2 className="text-sm font-medium flex items-center gap-2">
|
||||||
|
<List size={16} className="text-amber-600" />
|
||||||
|
{tx("待处理队列(Queued)", "Queued Jobs")}
|
||||||
|
</h2>
|
||||||
<p className="mt-1 text-xs text-zinc-600">
|
<p className="mt-1 text-xs text-zinc-600">
|
||||||
{tx("待处理题目 ID(预览):", "Queued problem IDs (preview):")}
|
{tx("待处理题目 ID(预览):", "Queued problem IDs (preview):")}
|
||||||
{queuedIds.length ? queuedIds.join(", ") : tx("无", "None")}
|
{queuedIds.length ? queuedIds.join(", ") : tx("无", "None")}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { apiFetch } from "@/lib/api";
|
import { apiFetch } from "@/lib/api";
|
||||||
import { useI18nText } from "@/lib/i18n";
|
import { useI18nText } from "@/lib/i18n";
|
||||||
import { useUiPreferences } from "@/components/ui-preference-provider";
|
import { useUiPreferences } from "@/components/ui-preference-provider";
|
||||||
|
import { Calendar, Swords, Timer, Trophy, Shield } from "lucide-react";
|
||||||
|
|
||||||
type Contest = {
|
type Contest = {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -44,7 +45,7 @@ export default function ContestsPage() {
|
|||||||
<h1 className={`text-xl font-bold max-[390px]:text-lg sm:text-2xl ${isMc ? "text-[color:var(--mc-diamond)] mc-text-shadow" : ""}`}>
|
<h1 className={`text-xl font-bold max-[390px]:text-lg sm:text-2xl ${isMc ? "text-[color:var(--mc-diamond)] mc-text-shadow" : ""}`}>
|
||||||
{isMc ? (
|
{isMc ? (
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<span>⚔️</span>
|
<Swords size={24} />
|
||||||
{tx("突袭公告板", "Raid Board")}
|
{tx("突袭公告板", "Raid Board")}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
@@ -66,13 +67,19 @@ export default function ContestsPage() {
|
|||||||
>
|
>
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className={`text-lg font-medium ${isMc ? "text-[color:var(--mc-gold)]" : ""}`}>
|
<h2 className={`text-lg font-medium flex items-center gap-2 ${isMc ? "text-[color:var(--mc-gold)]" : ""}`}>
|
||||||
{isMc && <span className="mr-2">🛡️</span>}
|
{isMc && <Shield size={20} />}
|
||||||
{c.title}
|
{c.title}
|
||||||
</h2>
|
</h2>
|
||||||
<div className={`mt-2 text-xs ${isMc ? "text-zinc-400" : "text-zinc-500"}`}>
|
<div className={`mt-2 text-xs flex flex-col gap-1 ${isMc ? "text-zinc-400" : "text-zinc-500"}`}>
|
||||||
<p>{tx("开始", "Start")}: {new Date(c.starts_at * 1000).toLocaleString()}</p>
|
<p className="flex items-center gap-2">
|
||||||
<p>{tx("结束", "End")}: {new Date(c.ends_at * 1000).toLocaleString()}</p>
|
{isMc && <Calendar size={14} />}
|
||||||
|
{tx("开始", "Start")}: {new Date(c.starts_at * 1000).toLocaleString()}
|
||||||
|
</p>
|
||||||
|
<p className="flex items-center gap-2">
|
||||||
|
{isMc && <Timer size={14} />}
|
||||||
|
{tx("结束", "End")}: {new Date(c.ends_at * 1000).toLocaleString()}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isMc && (
|
{isMc && (
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useEffect, useMemo, useState } from "react";
|
|||||||
import { apiFetch } from "@/lib/api";
|
import { apiFetch } from "@/lib/api";
|
||||||
import { readToken } from "@/lib/auth";
|
import { readToken } from "@/lib/auth";
|
||||||
import { useI18nText } from "@/lib/i18n";
|
import { useI18nText } from "@/lib/i18n";
|
||||||
|
import { Activity, HardDrive, Play, RefreshCw, Server, FileText, CheckCircle, XCircle, Clock } from "lucide-react";
|
||||||
|
|
||||||
type ImportJob = {
|
type ImportJob = {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -253,7 +254,8 @@ export default function ImportsPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-7xl px-3 py-6 max-[390px]:px-2 sm:px-4 md:px-6 md:py-8">
|
<main className="mx-auto max-w-7xl px-3 py-6 max-[390px]:px-2 sm:px-4 md:px-6 md:py-8">
|
||||||
<h1 className="text-xl font-semibold max-[390px]:text-lg sm:text-2xl">
|
<h1 className="text-xl font-semibold max-[390px]:text-lg sm:text-2xl flex items-center gap-2">
|
||||||
|
<HardDrive size={24} />
|
||||||
{tx("题库导入/出题任务", "Import / Generation Jobs")}
|
{tx("题库导入/出题任务", "Import / Generation Jobs")}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@@ -336,7 +338,17 @@ export default function ImportsPage() {
|
|||||||
onClick={() => void runImport()}
|
onClick={() => void runImport()}
|
||||||
disabled={loading || running}
|
disabled={loading || running}
|
||||||
>
|
>
|
||||||
{running ? tx("导入中...", "Importing...") : tx("启动导入任务", "Start Import Job")}
|
{running ? (
|
||||||
|
<span className="flex items-center justify-center gap-2">
|
||||||
|
<Activity size={16} className="animate-spin" />
|
||||||
|
{tx("导入中...", "Importing...")}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="flex items-center justify-center gap-2">
|
||||||
|
<Play size={16} />
|
||||||
|
{tx("启动导入任务", "Start Import Job")}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
{runMode === "luogu" && (
|
{runMode === "luogu" && (
|
||||||
<label className="flex w-full items-center gap-2 text-sm sm:w-auto">
|
<label className="flex w-full items-center gap-2 text-sm sm:w-auto">
|
||||||
@@ -348,10 +360,12 @@ export default function ImportsPage() {
|
|||||||
{tx("启动前清空历史题库", "Clear old problem set before start")}
|
{tx("启动前清空历史题库", "Clear old problem set before start")}
|
||||||
</label>
|
</label>
|
||||||
)}
|
)}
|
||||||
<button className="rounded border px-3 py-2 text-sm" onClick={() => void refresh()} disabled={loading}>
|
<button className="rounded border px-3 py-2 text-sm flex items-center gap-2" onClick={() => void refresh()} disabled={loading}>
|
||||||
|
<RefreshCw size={14} className={loading ? "animate-spin" : ""} />
|
||||||
{tx("刷新", "Refresh")}
|
{tx("刷新", "Refresh")}
|
||||||
</button>
|
</button>
|
||||||
<span className={`text-sm ${running ? "text-emerald-700" : "text-zinc-600"}`}>
|
<span className={`text-sm flex items-center gap-1 ${running ? "text-emerald-700" : "text-zinc-600"}`}>
|
||||||
|
{running ? <Activity size={14} className="animate-pulse" /> : <Server size={14} />}
|
||||||
{running ? tx("运行中", "Running") : tx("空闲", "Idle")}
|
{running ? tx("运行中", "Running") : tx("空闲", "Idle")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,16 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
|||||||
import { apiFetch } from "@/lib/api";
|
import { apiFetch } from "@/lib/api";
|
||||||
import { readToken } from "@/lib/auth";
|
import { readToken } from "@/lib/auth";
|
||||||
import { useI18nText } from "@/lib/i18n";
|
import { useI18nText } from "@/lib/i18n";
|
||||||
|
import {
|
||||||
|
Book,
|
||||||
|
Code2,
|
||||||
|
FileQuestion,
|
||||||
|
Library,
|
||||||
|
Map as MapIcon,
|
||||||
|
RefreshCw,
|
||||||
|
Shield,
|
||||||
|
Sword,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
type Article = {
|
type Article = {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -198,13 +208,17 @@ export default function KbListPage() {
|
|||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-5xl px-3 py-6 max-[390px]:px-2 sm:px-4 md:px-6 md:py-8">
|
<main className="mx-auto max-w-5xl px-3 py-6 max-[390px]:px-2 sm:px-4 md:px-6 md:py-8">
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||||
<h1 className="text-xl font-semibold max-[390px]:text-lg sm:text-2xl">{tx("学习知识库", "Knowledge Base")}</h1>
|
<h1 className="text-xl font-semibold max-[390px]:text-lg sm:text-2xl flex items-center gap-2">
|
||||||
|
<Library size={28} />
|
||||||
|
{tx("学习知识库", "Knowledge Base")}
|
||||||
|
</h1>
|
||||||
{canManageRefresh ? (
|
{canManageRefresh ? (
|
||||||
<button
|
<button
|
||||||
className="rounded border px-3 py-1 text-sm disabled:opacity-50"
|
className="rounded border px-3 py-1 text-sm disabled:opacity-50 flex items-center gap-2"
|
||||||
disabled={triggerLoading || refreshStatus?.running}
|
disabled={triggerLoading || refreshStatus?.running}
|
||||||
onClick={() => void triggerRefresh()}
|
onClick={() => void triggerRefresh()}
|
||||||
>
|
>
|
||||||
|
<RefreshCw size={14} className={triggerLoading || refreshStatus?.running ? "animate-spin" : ""} />
|
||||||
{triggerLoading
|
{triggerLoading
|
||||||
? tx("更新触发中...", "Triggering...")
|
? tx("更新触发中...", "Triggering...")
|
||||||
: refreshStatus?.running
|
: refreshStatus?.running
|
||||||
@@ -232,10 +246,8 @@ export default function KbListPage() {
|
|||||||
? tx("读取中...", "Loading...")
|
? tx("读取中...", "Loading...")
|
||||||
: refreshStatus?.running
|
: refreshStatus?.running
|
||||||
? tx(`运行中(开始于 ${fmtTs(refreshStatus.last_started_at)})`, `Running (started at ${fmtTs(refreshStatus.last_started_at)})`)
|
? tx(`运行中(开始于 ${fmtTs(refreshStatus.last_started_at)})`, `Running (started at ${fmtTs(refreshStatus.last_started_at)})`)
|
||||||
: tx(`空闲(最近结束 ${fmtTs(refreshStatus?.last_finished_at ?? null)},退出码 ${
|
: tx(`空闲(最近结束 ${fmtTs(refreshStatus?.last_finished_at ?? null)},退出码 ${refreshStatus?.last_exit_code ?? "-"
|
||||||
refreshStatus?.last_exit_code ?? "-"
|
})`, `Idle (last finished ${fmtTs(refreshStatus?.last_finished_at ?? null)}, exit code ${refreshStatus?.last_exit_code ?? "-"
|
||||||
})`, `Idle (last finished ${fmtTs(refreshStatus?.last_finished_at ?? null)}, exit code ${
|
|
||||||
refreshStatus?.last_exit_code ?? "-"
|
|
||||||
})`))}
|
})`))}
|
||||||
</p>
|
</p>
|
||||||
{loading && <p className="mt-3 text-sm text-zinc-500">{tx("加载中...", "Loading...")}</p>}
|
{loading && <p className="mt-3 text-sm text-zinc-500">{tx("加载中...", "Loading...")}</p>}
|
||||||
@@ -244,17 +256,20 @@ export default function KbListPage() {
|
|||||||
|
|
||||||
<section className="mt-4 space-y-5">
|
<section className="mt-4 space-y-5">
|
||||||
{[
|
{[
|
||||||
["roadmap", tx("学习总路线", "Learning Roadmap")],
|
["roadmap", tx("学习总路线", "Learning Roadmap"), MapIcon],
|
||||||
["cpp", tx("C++ 基础", "C++ Fundamentals")],
|
["cpp", tx("C++ 基础", "C++ Fundamentals"), Code2],
|
||||||
["cspj", "CSP-J"],
|
["cspj", "CSP-J", Sword],
|
||||||
["csps", "CSP-S"],
|
["csps", "CSP-S", Shield],
|
||||||
["other", tx("其他资料", "Other Resources")],
|
["other", tx("其他资料", "Other Resources"), FileQuestion],
|
||||||
].map(([key, label]) => {
|
].map(([key, label, Icon]) => {
|
||||||
const group = grouped[key] ?? [];
|
const group = grouped[key as string] ?? [];
|
||||||
if (!group.length) return null;
|
if (!group.length) return null;
|
||||||
return (
|
return (
|
||||||
<div key={key} className="space-y-3">
|
<div key={key as string} className="space-y-3">
|
||||||
<h2 className="text-sm font-semibold text-zinc-700">{label}</h2>
|
<h2 className="text-sm font-semibold text-zinc-700 flex items-center gap-2">
|
||||||
|
{Icon && <Icon size={18} />}
|
||||||
|
{label as string}
|
||||||
|
</h2>
|
||||||
{group.map((a) => (
|
{group.map((a) => (
|
||||||
<Link
|
<Link
|
||||||
key={a.slug}
|
key={a.slug}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import "@/themes/minecraft/theme.css";
|
|||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "CSP Online Learning & Contest Platform",
|
title: "CSP Quest World - 8-bit Adventure",
|
||||||
description: "Quests, Cursed Tome review, Raids, Knowledge Base, and C++ runner.",
|
description: "Join the 8-bit adventure! Solve algorithms, raid contests, and build your legend in the CSP Quest World.",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { apiFetch } from "@/lib/api";
|
import { apiFetch } from "@/lib/api";
|
||||||
import { useI18nText } from "@/lib/i18n";
|
import { useI18nText } from "@/lib/i18n";
|
||||||
import { useUiPreferences } from "@/components/ui-preference-provider";
|
import { useUiPreferences } from "@/components/ui-preference-provider";
|
||||||
|
import { Crown, Medal, Trophy, User, Calendar } from "lucide-react";
|
||||||
|
|
||||||
type Row = {
|
type Row = {
|
||||||
user_id: number;
|
user_id: number;
|
||||||
@@ -50,10 +51,10 @@ export default function LeaderboardPage() {
|
|||||||
const getRankIcon = (index: number) => {
|
const getRankIcon = (index: number) => {
|
||||||
if (!isMc) return `#${index + 1}`;
|
if (!isMc) return `#${index + 1}`;
|
||||||
switch (index) {
|
switch (index) {
|
||||||
case 0: return "🏆";
|
case 0: return <Trophy size={20} className="text-[color:var(--mc-gold)]" />;
|
||||||
case 1: return "🥈";
|
case 1: return <Medal size={20} className="text-zinc-300" />;
|
||||||
case 2: return "🥉";
|
case 2: return <Medal size={20} className="text-orange-700" />;
|
||||||
default: return `#${index + 1}`;
|
default: return <span className="font-mono">#{index + 1}</span>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,7 +63,7 @@ export default function LeaderboardPage() {
|
|||||||
<h1 className={`text-xl font-bold max-[390px]:text-lg sm:text-2xl ${isMc ? "text-[color:var(--mc-diamond)] mc-text-shadow" : ""}`}>
|
<h1 className={`text-xl font-bold max-[390px]:text-lg sm:text-2xl ${isMc ? "text-[color:var(--mc-diamond)] mc-text-shadow" : ""}`}>
|
||||||
{isMc ? (
|
{isMc ? (
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<span>🏰</span>
|
<Crown size={24} />
|
||||||
{tx("名人堂", "Hall of Fame")}
|
{tx("名人堂", "Hall of Fame")}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
@@ -101,15 +102,25 @@ export default function LeaderboardPage() {
|
|||||||
<thead className={`${isMc ? "bg-black/30 text-zinc-300" : "bg-zinc-100 text-left"}`}>
|
<thead className={`${isMc ? "bg-black/30 text-zinc-300" : "bg-zinc-100 text-left"}`}>
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-3 py-2 text-left">{tx("排名", "Rank")}</th>
|
<th className="px-3 py-2 text-left">{tx("排名", "Rank")}</th>
|
||||||
<th className="px-3 py-2 text-left">{tx("用户", "User")}</th>
|
<th className="px-3 py-2 text-left">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{isMc && <User size={16} />}
|
||||||
|
{tx("用户", "User")}
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
<th className="px-3 py-2 text-left">Rating</th>
|
<th className="px-3 py-2 text-left">Rating</th>
|
||||||
<th className="px-3 py-2 text-left">{tx("注册时间", "Registered At")}</th>
|
<th className="px-3 py-2 text-left">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{isMc && <Calendar size={16} />}
|
||||||
|
{tx("注册时间", "Registered At")}
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className={isMc ? "divide-y divide-zinc-700" : ""}>
|
<tbody className={isMc ? "divide-y divide-zinc-700" : ""}>
|
||||||
{items.map((row, i) => (
|
{items.map((row, i) => (
|
||||||
<tr key={row.user_id} className={isMc ? "hover:bg-white/5 transition-colors" : "border-t"}>
|
<tr key={row.user_id} className={isMc ? "hover:bg-white/5 transition-colors" : "border-t"}>
|
||||||
<td className={`px-3 py-2 font-bold ${getRankColor(i)}`}>{getRankIcon(i)}</td>
|
<td className={`px-3 py-2 font-bold flex items-center justify-center ${getRankColor(i)}`}>{getRankIcon(i)}</td>
|
||||||
<td className={`px-3 py-2 font-medium ${getRankColor(i)}`}>{row.username}</td>
|
<td className={`px-3 py-2 font-medium ${getRankColor(i)}`}>{row.username}</td>
|
||||||
<td className="px-3 py-2 text-[color:var(--mc-emerald)]">{row.rating}</td>
|
<td className="px-3 py-2 text-[color:var(--mc-emerald)]">{row.rating}</td>
|
||||||
<td className="px-3 py-2 text-zinc-500">
|
<td className="px-3 py-2 text-zinc-500">
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import {
|
||||||
|
ArrowRightLeft,
|
||||||
|
Calendar,
|
||||||
|
CheckCircle2,
|
||||||
|
History,
|
||||||
|
IdCard,
|
||||||
|
RefreshCw,
|
||||||
|
ShoppingBag,
|
||||||
|
TrendingUp,
|
||||||
|
TrendingDown,
|
||||||
|
Zap,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
import { PixelAvatar } from "@/components/pixel-avatar";
|
import { PixelAvatar } from "@/components/pixel-avatar";
|
||||||
import { apiFetch, listRatingHistory, type RatingHistoryItem } from "@/lib/api";
|
import { apiFetch, listRatingHistory, type RatingHistoryItem } from "@/lib/api";
|
||||||
@@ -243,17 +255,19 @@ export default function MePage() {
|
|||||||
Level {Math.floor(profile.rating / 100)}
|
Level {Math.floor(profile.rating / 100)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-[color:var(--mc-stone-dark)]">UID: {profile.id}</p>
|
<div className="flex justify-between text-sm items-center">
|
||||||
|
<span className="text-zinc-800 flex items-center gap-1">
|
||||||
|
<IdCard size={14} className="text-zinc-500" />
|
||||||
|
UID
|
||||||
|
</span>
|
||||||
|
<span className="text-zinc-600 font-mono">{profile.id}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex justify-between text-sm items-center">
|
||||||
<div className="mt-4 space-y-2 border-t border-black/20 pt-4">
|
<span className="text-zinc-800 flex items-center gap-1">
|
||||||
<div className="flex justify-between text-sm">
|
<Calendar size={14} className="text-zinc-500" />
|
||||||
<span className="text-zinc-800">{tx("绿宝石 (Rating)", "Emeralds (Rating)")}</span>
|
{tx("加入时间", "Joined")}
|
||||||
<span className="font-bold text-[color:var(--mc-green)] text-shadow-sm">{profile.rating}</span>
|
</span>
|
||||||
</div>
|
<span className="text-zinc-600 font-mono">{new Date(profile.created_at * 1000).toLocaleDateString()}</span>
|
||||||
<div className="flex justify-between text-sm">
|
|
||||||
<span className="text-zinc-800">{tx("加入时间", "Joined")}</span>
|
|
||||||
<span className="text-zinc-600">{new Date(profile.created_at * 1000).toLocaleDateString()}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -271,13 +285,16 @@ export default function MePage() {
|
|||||||
<div key={idx} className="bg-[color:var(--mc-surface-soft)] p-3 border-2 border-[color:var(--mc-stone-dark)] relative group hover:border-[color:var(--mc-stone)] transition-colors">
|
<div key={idx} className="bg-[color:var(--mc-surface-soft)] p-3 border-2 border-[color:var(--mc-stone-dark)] relative group hover:border-[color:var(--mc-stone)] transition-colors">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className={`w-5 h-5 border-2 border-[color:var(--mc-stone-dark)] flex items-center justify-center bg-black/30 mt-0.5 ${task.completed ? 'bg-[color:var(--mc-green)]/20' : ''}`}>
|
<div className={`w-5 h-5 border-2 border-[color:var(--mc-stone-dark)] flex items-center justify-center bg-black/30 mt-0.5 ${task.completed ? 'bg-[color:var(--mc-green)]/20' : ''}`}>
|
||||||
{task.completed && <span className="text-[color:var(--mc-green)] text-sm">✓</span>}
|
{task.completed && <CheckCircle2 size={16} className="text-[color:var(--mc-green)]" />}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="flex justify-between items-start mb-1">
|
<div className="flex justify-between items-start mb-1">
|
||||||
<h3 className="text-[color:var(--mc-plank-light)] text-lg font-bold leading-tight">
|
<h3 className="text-[color:var(--mc-plank-light)] text-lg font-bold leading-tight flex items-center gap-2">
|
||||||
{task.title}
|
{task.title}
|
||||||
<span className="ml-2 text-[color:var(--mc-gold)] text-base font-minecraft">+{task.reward} XP</span>
|
<span className="ml-2 text-[color:var(--mc-gold)] text-base font-minecraft flex items-center gap-1">
|
||||||
|
<Zap size={14} />
|
||||||
|
+{task.reward} XP
|
||||||
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[color:var(--mc-stone)] text-base leading-snug">
|
<p className="text-[color:var(--mc-stone)] text-base leading-snug">
|
||||||
@@ -299,8 +316,10 @@ export default function MePage() {
|
|||||||
|
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<div className="flex gap-2 text-black">
|
<div className="flex gap-2 text-black">
|
||||||
|
<div className="relative flex-1">
|
||||||
|
<ShoppingBag className="absolute left-2 top-2 text-black/50 pointer-events-none" size={16} />
|
||||||
<select
|
<select
|
||||||
className="flex-1 rounded-none border-2 border-black bg-[color:var(--surface)] px-2 py-1 text-base font-bold"
|
className="w-full rounded-none border-2 border-black bg-[color:var(--surface)] px-2 py-1 pl-8 text-base font-bold appearance-none"
|
||||||
value={selectedItemId}
|
value={selectedItemId}
|
||||||
onChange={(e) => setSelectedItemId(Number(e.target.value))}
|
onChange={(e) => setSelectedItemId(Number(e.target.value))}
|
||||||
>
|
>
|
||||||
@@ -311,6 +330,7 @@ export default function MePage() {
|
|||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
className="w-20 rounded-none border-2 border-black bg-[color:var(--surface)] px-2 py-1 text-base font-bold text-center"
|
className="w-20 rounded-none border-2 border-black bg-[color:var(--surface)] px-2 py-1 text-base font-bold text-center"
|
||||||
type="number"
|
type="number"
|
||||||
@@ -340,10 +360,11 @@ export default function MePage() {
|
|||||||
<option value="studyday">{tx("工作日价格", "Workday Price")}</option>
|
<option value="studyday">{tx("工作日价格", "Workday Price")}</option>
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
className="mc-btn mc-btn-success text-xs px-4"
|
className="mc-btn mc-btn-success text-xs px-4 flex items-center gap-2"
|
||||||
onClick={() => void redeem()}
|
onClick={() => void redeem()}
|
||||||
disabled={redeemLoading || !selectedItemId}
|
disabled={redeemLoading || !selectedItemId}
|
||||||
>
|
>
|
||||||
|
<ArrowRightLeft size={14} />
|
||||||
{tx("交易", "Trade")}
|
{tx("交易", "Trade")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -355,12 +376,16 @@ export default function MePage() {
|
|||||||
|
|
||||||
{/* Rating History Section */}
|
{/* Rating History Section */}
|
||||||
<section className="mt-4 rounded-none border-[3px] border-black bg-[color:var(--mc-surface)] p-4 shadow-[4px_4px_0_rgba(0,0,0,0.5)]">
|
<section className="mt-4 rounded-none border-[3px] border-black bg-[color:var(--mc-surface)] p-4 shadow-[4px_4px_0_rgba(0,0,0,0.5)]">
|
||||||
<h2 className="text-base font-bold text-black mb-2">{tx("积分变动记录", "Rating History")}</h2>
|
<h2 className="text-base font-bold text-black mb-2 flex items-center gap-2">
|
||||||
|
<History size={18} />
|
||||||
|
{tx("积分变动记录", "Rating History")}
|
||||||
|
</h2>
|
||||||
<div className="max-h-60 overflow-y-auto space-y-1">
|
<div className="max-h-60 overflow-y-auto space-y-1">
|
||||||
{historyItems.map((item, idx) => (
|
{historyItems.map((item, idx) => (
|
||||||
<div key={idx} className="flex justify-between text-xs text-zinc-800 border-b border-zinc-200 pb-1">
|
<div key={idx} className="flex justify-between text-xs text-zinc-800 border-b border-zinc-200 pb-1">
|
||||||
<span>
|
<span>
|
||||||
<span className={`font-bold ${item.change > 0 ? 'text-[color:var(--mc-green)]' : 'text-[color:var(--mc-red)]'}`}>
|
<span className={`font-bold flex items-center gap-1 ${item.change > 0 ? 'text-[color:var(--mc-green)]' : 'text-[color:var(--mc-red)]'}`}>
|
||||||
|
{item.change > 0 ? <TrendingUp size={14} /> : <TrendingDown size={14} />}
|
||||||
{item.change > 0 ? `+${item.change}` : item.change}
|
{item.change > 0 ? `+${item.change}` : item.change}
|
||||||
</span>
|
</span>
|
||||||
<span className="ml-2">{item.note}</span>
|
<span className="ml-2">{item.note}</span>
|
||||||
@@ -381,10 +406,11 @@ export default function MePage() {
|
|||||||
<div className="flex items-center justify-between gap-2 mb-2">
|
<div className="flex items-center justify-between gap-2 mb-2">
|
||||||
<h2 className="text-base font-bold text-black">{tx("交易记录", "Trade History")}</h2>
|
<h2 className="text-base font-bold text-black">{tx("交易记录", "Trade History")}</h2>
|
||||||
<button
|
<button
|
||||||
className="text-xs text-[color:var(--mc-stone-dark)] underline"
|
className="text-xs text-[color:var(--mc-stone-dark)] underline flex items-center gap-1 hover:text-black"
|
||||||
onClick={() => void loadAll()}
|
onClick={() => void loadAll()}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
|
<RefreshCw size={12} className={loading ? "animate-spin" : ""} />
|
||||||
{tx("刷新", "Refresh")}
|
{tx("刷新", "Refresh")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
43
frontend/src/app/not-found.tsx
普通文件
43
frontend/src/app/not-found.tsx
普通文件
@@ -0,0 +1,43 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useUiPreferences } from "@/components/ui-preference-provider";
|
||||||
|
import { useI18nText } from "@/lib/i18n";
|
||||||
|
import { AlertTriangle } from "lucide-react";
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
const { theme } = useUiPreferences();
|
||||||
|
const { tx } = useI18nText();
|
||||||
|
const isMc = theme === "minecraft";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`flex min-h-[80vh] flex-col items-center justify-center p-4 text-center ${isMc ? "text-white" : ""}`}>
|
||||||
|
{isMc ? (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="text-6xl animate-bounce">👾</div>
|
||||||
|
<h1 className="text-4xl font-bold text-[#AA0000] mc-text-shadow">
|
||||||
|
{tx("这里是虚空...", "THE VOID")}
|
||||||
|
</h1>
|
||||||
|
<p className="text-xl text-zinc-400">
|
||||||
|
{tx("你来到了世界的尽头。", "You have reached the end of the world.")}
|
||||||
|
</p>
|
||||||
|
<div className="pt-4">
|
||||||
|
<Link href="/" className="mc-btn bg-[color:var(--mc-wood)] text-white px-8 py-3 text-lg inline-flex items-center gap-2">
|
||||||
|
<span>🏠</span>
|
||||||
|
{tx("重生 (返回首页)", "Respawn (Home)")}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<AlertTriangle size={64} className="mx-auto text-zinc-300" />
|
||||||
|
<h1 className="text-2xl font-bold text-zinc-800">404 - Page Not Found</h1>
|
||||||
|
<p className="text-zinc-600">The page you are looking for does not exist.</p>
|
||||||
|
<Link href="/" className="inline-block rounded bg-zinc-900 px-6 py-2 text-white hover:bg-zinc-700">
|
||||||
|
Back to Home
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,6 +3,20 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Book,
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
Globe,
|
||||||
|
Search,
|
||||||
|
Shield,
|
||||||
|
Sword,
|
||||||
|
Tag,
|
||||||
|
Trophy,
|
||||||
|
Filter,
|
||||||
|
ArrowUpDown
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
import { apiFetch } from "@/lib/api";
|
import { apiFetch } from "@/lib/api";
|
||||||
import { useI18nText } from "@/lib/i18n";
|
import { useI18nText } from "@/lib/i18n";
|
||||||
|
|
||||||
@@ -87,6 +101,7 @@ const QUICK_CARDS = [
|
|||||||
titleEn: "CSP-J Trials",
|
titleEn: "CSP-J Trials",
|
||||||
descZh: "普及组入门任务",
|
descZh: "普及组入门任务",
|
||||||
descEn: "Junior Tier Quests",
|
descEn: "Junior Tier Quests",
|
||||||
|
icon: Sword,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
presetKey: "csp-s",
|
presetKey: "csp-s",
|
||||||
@@ -94,6 +109,7 @@ const QUICK_CARDS = [
|
|||||||
titleEn: "CSP-S Challenges",
|
titleEn: "CSP-S Challenges",
|
||||||
descZh: "提高组进阶任务",
|
descZh: "提高组进阶任务",
|
||||||
descEn: "Senior Tier Quests",
|
descEn: "Senior Tier Quests",
|
||||||
|
icon: Shield,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
presetKey: "noip-junior",
|
presetKey: "noip-junior",
|
||||||
@@ -101,6 +117,7 @@ const QUICK_CARDS = [
|
|||||||
titleEn: "NOIP Basics",
|
titleEn: "NOIP Basics",
|
||||||
descZh: "算法与思维",
|
descZh: "算法与思维",
|
||||||
descEn: "Algorithm & Logic",
|
descEn: "Algorithm & Logic",
|
||||||
|
icon: Book,
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
@@ -269,24 +286,31 @@ export default function ProblemsPage() {
|
|||||||
<button
|
<button
|
||||||
key={card.presetKey}
|
key={card.presetKey}
|
||||||
type="button"
|
type="button"
|
||||||
className={`rounded-xl border px-4 py-3 text-left transition ${active
|
className={`rounded-xl border px-4 py-3 text-left transition flex items-center gap-4 ${active
|
||||||
? "bg-[color:var(--mc-grass-dark)] text-white"
|
? "bg-[color:var(--mc-grass-dark)] text-white"
|
||||||
: "bg-[color:var(--mc-plank)] text-black hover:bg-[color:var(--mc-plank-light)]"
|
: "bg-[color:var(--mc-plank)] text-black hover:bg-[color:var(--mc-plank-light)]"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => selectPreset(card.presetKey)}
|
onClick={() => selectPreset(card.presetKey)}
|
||||||
>
|
>
|
||||||
|
<div className={`p-2 border-2 border-black bg-black/10 rounded-sm ${active ? "bg-white/20" : ""}`}>
|
||||||
|
<card.icon size={24} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
<p className="text-base font-bold mc-text-shadow-sm">{isZh ? card.titleZh : card.titleEn}</p>
|
<p className="text-base font-bold mc-text-shadow-sm">{isZh ? card.titleZh : card.titleEn}</p>
|
||||||
<p className={`mt-1 text-xs ${active ? "text-zinc-100" : "text-zinc-800"}`}>
|
<p className={`mt-1 text-xs ${active ? "text-zinc-100" : "text-zinc-800"}`}>
|
||||||
{isZh ? card.descZh : card.descEn}
|
{isZh ? card.descZh : card.descEn}
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="mt-4 grid gap-3 rounded-xl border bg-[color:var(--mc-stone-dark)] p-4 md:grid-cols-2 lg:grid-cols-6 shadow-[4px_4px_0_rgba(0,0,0,0.5)]">
|
<section className="mt-4 grid gap-3 rounded-xl border bg-[color:var(--mc-stone-dark)] p-4 md:grid-cols-2 lg:grid-cols-6 shadow-[4px_4px_0_rgba(0,0,0,0.5)]">
|
||||||
|
<div className="relative">
|
||||||
|
<Filter className="absolute left-2 top-2.5 h-4 w-4 text-zinc-400" />
|
||||||
<select
|
<select
|
||||||
className="rounded-none border-2 border-black bg-[color:var(--surface)] text-white px-3 py-2 text-sm"
|
className="w-full rounded-none border-2 border-black bg-[color:var(--surface)] text-white pl-8 pr-3 py-2 text-sm appearance-none"
|
||||||
value={presetKey}
|
value={presetKey}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
selectPreset(e.target.value);
|
selectPreset(e.target.value);
|
||||||
@@ -298,6 +322,7 @@ export default function ProblemsPage() {
|
|||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
className="rounded-none border-2 border-black bg-[color:var(--surface)] text-white px-3 py-2 text-sm lg:col-span-2"
|
className="rounded-none border-2 border-black bg-[color:var(--surface)] text-white px-3 py-2 text-sm lg:col-span-2"
|
||||||
@@ -343,10 +368,11 @@ export default function ProblemsPage() {
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="mc-btn mc-btn-primary"
|
className="mc-btn mc-btn-primary flex items-center justify-center gap-2"
|
||||||
onClick={applySearch}
|
onClick={applySearch}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
|
<Search size={16} />
|
||||||
{loading ? tx("加载中...", "Loading...") : tx("搜索", "Search")}
|
{loading ? tx("加载中...", "Loading...") : tx("搜索", "Search")}
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
@@ -422,13 +448,17 @@ export default function ProblemsPage() {
|
|||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex flex-wrap gap-1">
|
||||||
{tags.length === 0 && <span className="text-[color:var(--mc-stone-dark)]">-</span>}
|
{tags.length === 0 && <span className="text-[color:var(--mc-stone-dark)]">-</span>}
|
||||||
{tags.map((tag) => (
|
{tags.map((tag) => (
|
||||||
<span key={tag} className="border border-black bg-[color:var(--mc-stone-dark)] px-2 py-0.5 text-xs text-white">
|
<span key={tag} className="border border-black bg-[color:var(--mc-stone-dark)] px-2 py-0.5 text-xs text-white flex items-center gap-1">
|
||||||
|
<Tag size={10} />
|
||||||
{tag}
|
{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-[color:var(--mc-stone)]">{problem.source || "-"}</td>
|
<td className="px-3 py-2 text-[color:var(--mc-stone)] flex items-center gap-1">
|
||||||
|
<Globe size={12} />
|
||||||
|
{problem.source || "-"}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -450,21 +480,21 @@ export default function ProblemsPage() {
|
|||||||
<div className="mt-4 flex flex-col gap-3 text-sm sm:flex-row sm:items-center sm:justify-between">
|
<div className="mt-4 flex flex-col gap-3 text-sm sm:flex-row sm:items-center sm:justify-between">
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<button
|
<button
|
||||||
className="mc-btn"
|
className="mc-btn px-2"
|
||||||
onClick={() => setPage((p) => Math.max(1, p - 1))}
|
onClick={() => setPage((p) => Math.max(1, p - 1))}
|
||||||
disabled={loading || page <= 1}
|
disabled={loading || page <= 1}
|
||||||
>
|
>
|
||||||
{tx("上一页", "Prev")}
|
<ChevronLeft size={16} />
|
||||||
</button>
|
</button>
|
||||||
<span className="text-[color:var(--mc-diamond)] font-bold">
|
<span className="text-[color:var(--mc-diamond)] font-bold">
|
||||||
{isZh ? `第 ${page} / ${totalPages} 页` : `Page ${page} / ${totalPages}`}
|
{isZh ? `第 ${page} / ${totalPages} 页` : `Page ${page} / ${totalPages}`}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
className="mc-btn"
|
className="mc-btn px-2"
|
||||||
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
|
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
|
||||||
disabled={loading || page >= totalPages}
|
disabled={loading || page >= totalPages}
|
||||||
>
|
>
|
||||||
{tx("下一页", "Next")}
|
<ChevronRight size={16} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useState } from "react";
|
|||||||
|
|
||||||
import { apiFetch } from "@/lib/api";
|
import { apiFetch } from "@/lib/api";
|
||||||
import { useI18nText } from "@/lib/i18n";
|
import { useI18nText } from "@/lib/i18n";
|
||||||
|
import { AlertTriangle, Code2, Monitor, Play, Terminal, Timer } from "lucide-react";
|
||||||
|
|
||||||
type RunResult = {
|
type RunResult = {
|
||||||
status: string;
|
status: string;
|
||||||
@@ -50,13 +51,17 @@ export default function RunPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="mx-auto max-w-6xl px-3 py-6 max-[390px]:px-2 sm:px-4 md:px-6 md:py-8">
|
<main className="mx-auto max-w-6xl px-3 py-6 max-[390px]:px-2 sm:px-4 md:px-6 md:py-8">
|
||||||
<h1 className="text-xl font-semibold max-[390px]:text-lg sm:text-2xl">
|
<h1 className="text-xl font-semibold max-[390px]:text-lg sm:text-2xl flex items-center gap-2">
|
||||||
|
<Terminal size={24} />
|
||||||
{tx("在线 C++ 编写 / 编译 / 运行", "Online C++ Editor / Compile / Run")}
|
{tx("在线 C++ 编写 / 编译 / 运行", "Online C++ Editor / Compile / Run")}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div className="mt-4 grid gap-4 lg:grid-cols-2">
|
<div className="mt-4 grid gap-4 lg:grid-cols-2">
|
||||||
<section className="rounded-xl border bg-white p-4">
|
<section className="rounded-xl border bg-white p-4">
|
||||||
<h2 className="text-sm font-medium">{tx("代码", "Code")}</h2>
|
<h2 className="text-sm font-medium flex items-center gap-2">
|
||||||
|
<Code2 size={16} />
|
||||||
|
{tx("代码", "Code")}
|
||||||
|
</h2>
|
||||||
<textarea
|
<textarea
|
||||||
className="mt-2 h-72 w-full rounded border p-3 font-mono text-sm sm:h-[420px]"
|
className="mt-2 h-72 w-full rounded border p-3 font-mono text-sm sm:h-[420px]"
|
||||||
value={code}
|
value={code}
|
||||||
@@ -77,24 +82,42 @@ export default function RunPage() {
|
|||||||
onClick={() => void run()}
|
onClick={() => void run()}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{loading ? tx("运行中...", "Running...") : tx("运行", "Run")}
|
{loading ? (
|
||||||
|
<span className="flex items-center justify-center gap-2">
|
||||||
|
<Play size={16} className="animate-spin" />
|
||||||
|
{tx("运行中...", "Running...")}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="flex items-center justify-center gap-2">
|
||||||
|
<Play size={16} />
|
||||||
|
{tx("运行", "Run")}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{error && <p className="mt-3 text-sm text-red-600">{error}</p>}
|
{error && <p className="mt-3 text-sm text-red-600">{error}</p>}
|
||||||
|
|
||||||
{result && (
|
{result && (
|
||||||
<div className="mt-4 space-y-3 text-sm">
|
<div className="mt-4 space-y-3 text-sm">
|
||||||
<p>
|
<p className="flex items-center gap-2">
|
||||||
{tx("状态", "Status")}: <b>{result.status}</b> · {tx("耗时", "Time")}: {result.time_ms}ms
|
{tx("状态", "Status")}: <b>{result.status}</b> ·
|
||||||
|
<Timer size={14} />
|
||||||
|
{tx("耗时", "Time")}: {result.time_ms}ms
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-medium">stdout</h3>
|
<h3 className="font-medium flex items-center gap-1">
|
||||||
|
<Monitor size={14} />
|
||||||
|
stdout
|
||||||
|
</h3>
|
||||||
<pre className="mt-1 overflow-auto rounded bg-zinc-900 p-3 text-xs text-zinc-100">
|
<pre className="mt-1 overflow-auto rounded bg-zinc-900 p-3 text-xs text-zinc-100">
|
||||||
{result.stdout || "(empty)"}
|
{result.stdout || "(empty)"}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-medium">stderr</h3>
|
<h3 className="font-medium flex items-center gap-1 text-red-500">
|
||||||
|
<AlertTriangle size={14} />
|
||||||
|
stderr
|
||||||
|
</h3>
|
||||||
<pre className="mt-1 overflow-auto rounded bg-zinc-900 p-3 text-xs text-zinc-100">
|
<pre className="mt-1 overflow-auto rounded bg-zinc-900 p-3 text-xs text-zinc-100">
|
||||||
{result.stderr || "(empty)"}
|
{result.stderr || "(empty)"}
|
||||||
</pre>
|
</pre>
|
||||||
|
|||||||
@@ -6,6 +6,21 @@ import { useEffect, useState } from "react";
|
|||||||
import { apiFetch } from "@/lib/api";
|
import { apiFetch } from "@/lib/api";
|
||||||
import { useI18nText } from "@/lib/i18n";
|
import { useI18nText } from "@/lib/i18n";
|
||||||
import { useUiPreferences } from "@/components/ui-preference-provider";
|
import { useUiPreferences } from "@/components/ui-preference-provider";
|
||||||
|
import {
|
||||||
|
AlertTriangle,
|
||||||
|
Check,
|
||||||
|
Clock,
|
||||||
|
FileText,
|
||||||
|
Filter,
|
||||||
|
Search,
|
||||||
|
Timer,
|
||||||
|
Trophy,
|
||||||
|
User,
|
||||||
|
Wrench,
|
||||||
|
X,
|
||||||
|
Zap,
|
||||||
|
Scroll
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
type Submission = {
|
type Submission = {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -46,20 +61,27 @@ export default function SubmissionsPage() {
|
|||||||
/** Map raw status codes to themed display text */
|
/** Map raw status codes to themed display text */
|
||||||
const statusLabel = (raw: string) => {
|
const statusLabel = (raw: string) => {
|
||||||
if (!isMc) return raw;
|
if (!isMc) return raw;
|
||||||
const map: Record<string, string> = {
|
switch (raw) {
|
||||||
Accepted: "✅ " + tx("通过", "Accepted"),
|
case "Accepted":
|
||||||
AC: "✅ AC",
|
case "AC":
|
||||||
WA: "❌ WA",
|
return <span className="flex items-center gap-1 text-[color:var(--mc-green)]"><Check size={14} /> AC</span>;
|
||||||
"Wrong Answer": "❌ " + tx("答案错误", "Wrong Answer"),
|
case "WA":
|
||||||
TLE: "⏰ TLE",
|
case "Wrong Answer":
|
||||||
"Time Limit Exceeded": "⏰ " + tx("超时", "TLE"),
|
return <span className="flex items-center gap-1 text-[color:var(--mc-red)]"><X size={14} /> WA</span>;
|
||||||
MLE: "💾 MLE",
|
case "TLE":
|
||||||
RE: "💥 RE",
|
case "Time Limit Exceeded":
|
||||||
"Runtime Error": "💥 " + tx("运行错误", "RE"),
|
return <span className="flex items-center gap-1 text-[color:var(--mc-gold)]"><Clock size={14} /> TLE</span>;
|
||||||
CE: "🔧 CE",
|
case "MLE":
|
||||||
"Compile Error": "🔧 " + tx("编译错误", "CE"),
|
return <span className="flex items-center gap-1 text-[color:var(--mc-red)]"><Zap size={14} /> MLE</span>;
|
||||||
};
|
case "RE":
|
||||||
return map[raw] ?? raw;
|
case "Runtime Error":
|
||||||
|
return <span className="flex items-center gap-1 text-orange-500"><AlertTriangle size={14} /> RE</span>;
|
||||||
|
case "CE":
|
||||||
|
case "Compile Error":
|
||||||
|
return <span className="flex items-center gap-1 text-zinc-500"><Wrench size={14} /> CE</span>;
|
||||||
|
default:
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
@@ -89,8 +111,8 @@ export default function SubmissionsPage() {
|
|||||||
<h1 className={`text-xl font-bold max-[390px]:text-lg sm:text-2xl ${isMc ? "text-[color:var(--mc-diamond)] mc-text-shadow" : ""}`}>
|
<h1 className={`text-xl font-bold max-[390px]:text-lg sm:text-2xl ${isMc ? "text-[color:var(--mc-diamond)] mc-text-shadow" : ""}`}>
|
||||||
{isMc ? (
|
{isMc ? (
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<span>📜</span>
|
<Scroll size={24} />
|
||||||
{tx("施法记录", "Spell Cast Log")}
|
<span>{tx("施法记录", "Spell Cast Log")}</span>
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
tx("提交记录", "Submissions")
|
tx("提交记录", "Submissions")
|
||||||
@@ -126,17 +148,25 @@ export default function SubmissionsPage() {
|
|||||||
onChange={(e) => setContestId(e.target.value)}
|
onChange={(e) => setContestId(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
className={`px-4 py-2 disabled:opacity-50 ${isMc
|
className={`px-4 py-2 disabled:opacity-50 flex items-center justify-center gap-2 ${isMc
|
||||||
? "mc-btn"
|
? "mc-btn"
|
||||||
: "rounded bg-zinc-900 text-white"}`}
|
: "rounded bg-zinc-900 text-white"}`}
|
||||||
onClick={() => void load()}
|
onClick={() => void load()}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{loading
|
{loading ? (
|
||||||
? tx("搜索中...", "Searching...")
|
<span className="flex items-center gap-2">
|
||||||
: isMc
|
<Filter size={16} className="animate-spin" />
|
||||||
? tx("🔍 搜索记录", "🔍 Search Logs")
|
{tx("搜索中...", "Searching...")}
|
||||||
: tx("筛选", "Filter")}
|
</span>
|
||||||
|
) : isMc ? (
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<Search size={16} />
|
||||||
|
{tx("搜索记录", "Search Logs")}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
tx("筛选", "Filter")
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -180,12 +210,37 @@ export default function SubmissionsPage() {
|
|||||||
<thead className={isMc ? "bg-black/30 text-zinc-300 text-left" : "bg-zinc-100 text-left"}>
|
<thead className={isMc ? "bg-black/30 text-zinc-300 text-left" : "bg-zinc-100 text-left"}>
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-3 py-2">ID</th>
|
<th className="px-3 py-2">ID</th>
|
||||||
<th className="px-3 py-2">{isMc ? tx("冒险者", "Player") : tx("用户", "User")}</th>
|
<th className="px-3 py-2">
|
||||||
<th className="px-3 py-2">{tx("任务", "Quest")}</th>
|
<div className="flex items-center gap-1">
|
||||||
<th className="px-3 py-2">{tx("状态", "Status")}</th>
|
{isMc && <User size={14} />}
|
||||||
<th className="px-3 py-2">{tx("分数", "Score")}</th>
|
{isMc ? tx("冒险者", "Player") : tx("用户", "User")}
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th className="px-3 py-2">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{isMc && <FileText size={14} />}
|
||||||
|
{tx("任务", "Quest")}
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th className="px-3 py-2">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{isMc && <Check size={14} />}
|
||||||
|
{tx("状态", "Status")}
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th className="px-3 py-2">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{isMc && <Trophy size={14} />}
|
||||||
|
{tx("分数", "Score")}
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
<th className="px-3 py-2">{isMc ? tx("绿宝石 Δ", "Emerald Δ") : tx("Rating 变化", "Rating Delta")}</th>
|
<th className="px-3 py-2">{isMc ? tx("绿宝石 Δ", "Emerald Δ") : tx("Rating 变化", "Rating Delta")}</th>
|
||||||
<th className="px-3 py-2">{tx("耗时(ms)", "Time(ms)")}</th>
|
<th className="px-3 py-2">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{isMc && <Timer size={14} />}
|
||||||
|
{tx("耗时(ms)", "Time(ms)")}
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
<th className="px-3 py-2">{tx("详情", "Detail")}</th>
|
<th className="px-3 py-2">{tx("详情", "Detail")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { apiFetch } from "@/lib/api";
|
|||||||
import { readToken } from "@/lib/auth";
|
import { readToken } from "@/lib/auth";
|
||||||
import { useI18nText } from "@/lib/i18n";
|
import { useI18nText } from "@/lib/i18n";
|
||||||
import { useUiPreferences } from "@/components/ui-preference-provider";
|
import { useUiPreferences } from "@/components/ui-preference-provider";
|
||||||
|
import { BookX, Trash2, RefreshCw, RotateCcw, Save, Search, Skull } from "lucide-react";
|
||||||
|
|
||||||
type WrongBookItem = {
|
type WrongBookItem = {
|
||||||
user_id: number;
|
user_id: number;
|
||||||
@@ -80,11 +81,14 @@ export default function WrongBookPage() {
|
|||||||
<h1 className={`text-xl font-bold max-[390px]:text-lg sm:text-2xl ${isMc ? "text-[color:var(--mc-diamond)] mc-text-shadow" : ""}`}>
|
<h1 className={`text-xl font-bold max-[390px]:text-lg sm:text-2xl ${isMc ? "text-[color:var(--mc-diamond)] mc-text-shadow" : ""}`}>
|
||||||
{isMc ? (
|
{isMc ? (
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<span>📜</span>
|
<Skull size={24} />
|
||||||
{tx("诅咒卷轴", "Cursed Scrolls")}
|
{tx("诅咒卷轴", "Cursed Scrolls")}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
tx("错题本", "Wrong Book")
|
<span className="flex items-center gap-2">
|
||||||
|
<BookX size={24} />
|
||||||
|
{tx("错题本", "Wrong Book")}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</h1>
|
</h1>
|
||||||
<p className={`mt-2 text-sm ${isMc ? "text-zinc-400" : "text-zinc-600"}`}>
|
<p className={`mt-2 text-sm ${isMc ? "text-zinc-400" : "text-zinc-600"}`}>
|
||||||
@@ -102,9 +106,19 @@ export default function WrongBookPage() {
|
|||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{loading
|
{loading
|
||||||
? tx("搜索中...", "Searching...")
|
? isMc ? (
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<Search size={16} className="animate-spin" />
|
||||||
|
{tx("搜索中...", "Searching...")}
|
||||||
|
</span>
|
||||||
|
) : tx("搜索中...", "Searching...")
|
||||||
: isMc
|
: isMc
|
||||||
? tx("🔍 重新搜索", "🔍 Search Again")
|
? (
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<RefreshCw size={16} />
|
||||||
|
{tx("重新搜索", "Search Again")}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
: tx("刷新", "Refresh")}
|
: tx("刷新", "Refresh")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -133,7 +147,12 @@ export default function WrongBookPage() {
|
|||||||
: "hover:bg-zinc-100"}`}
|
: "hover:bg-zinc-100"}`}
|
||||||
href={`/problems/${item.problem_id}`}
|
href={`/problems/${item.problem_id}`}
|
||||||
>
|
>
|
||||||
{isMc ? tx("⚔️ 重新挑战", "⚔️ Retry Quest") : tx("查看任务", "View Quest")}
|
{isMc ? (
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<RotateCcw size={12} />
|
||||||
|
{tx("重新挑战", "Retry Quest")}
|
||||||
|
</span>
|
||||||
|
) : tx("查看任务", "View Quest")}
|
||||||
</Link>
|
</Link>
|
||||||
{item.last_submission_id && (
|
{item.last_submission_id && (
|
||||||
<Link
|
<Link
|
||||||
@@ -160,7 +179,12 @@ export default function WrongBookPage() {
|
|||||||
: "hover:bg-zinc-100"}`}
|
: "hover:bg-zinc-100"}`}
|
||||||
onClick={() => void removeItem(item.problem_id)}
|
onClick={() => void removeItem(item.problem_id)}
|
||||||
>
|
>
|
||||||
{isMc ? tx("🗑️ 移除诅咒", "🗑️ Remove Curse") : tx("移除", "Remove")}
|
{isMc ? (
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<Trash2 size={14} />
|
||||||
|
{tx("移除诅咒", "Remove Curse")}
|
||||||
|
</span>
|
||||||
|
) : tx("移除", "Remove")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -185,7 +209,12 @@ export default function WrongBookPage() {
|
|||||||
: "hover:bg-zinc-100"}`}
|
: "hover:bg-zinc-100"}`}
|
||||||
onClick={() => void updateNote(item.problem_id, item.note)}
|
onClick={() => void updateNote(item.problem_id, item.note)}
|
||||||
>
|
>
|
||||||
{isMc ? tx("💾 保存笔记", "💾 Save Notes") : tx("保存备注", "Save Note")}
|
{isMc ? (
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<Save size={12} />
|
||||||
|
{tx("保存笔记", "Save Notes")}
|
||||||
|
</span>
|
||||||
|
) : tx("保存备注", "Save Note")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -9,10 +9,34 @@ import { XpBar } from "@/components/xp-bar";
|
|||||||
import { apiFetch } from "@/lib/api";
|
import { apiFetch } from "@/lib/api";
|
||||||
import { clearToken, readToken } from "@/lib/auth";
|
import { clearToken, readToken } from "@/lib/auth";
|
||||||
import type { ThemeId } from "@/themes/types";
|
import type { ThemeId } from "@/themes/types";
|
||||||
|
import {
|
||||||
|
BookOpen,
|
||||||
|
BookX,
|
||||||
|
Crown,
|
||||||
|
FileText,
|
||||||
|
Home,
|
||||||
|
Key,
|
||||||
|
Library,
|
||||||
|
LogOut,
|
||||||
|
Play,
|
||||||
|
Settings,
|
||||||
|
Sword,
|
||||||
|
Trophy,
|
||||||
|
User,
|
||||||
|
Users,
|
||||||
|
Menu,
|
||||||
|
X,
|
||||||
|
Database,
|
||||||
|
FileJson,
|
||||||
|
Shield,
|
||||||
|
Gift,
|
||||||
|
FileCode
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
type NavLink = {
|
type NavLink = {
|
||||||
label: string;
|
label: string;
|
||||||
href: string;
|
href: string;
|
||||||
|
icon: React.ElementType;
|
||||||
};
|
};
|
||||||
|
|
||||||
type NavGroup = {
|
type NavGroup = {
|
||||||
@@ -32,28 +56,28 @@ function buildNavGroups(t: (key: string) => string, isAdmin: boolean): NavGroup[
|
|||||||
key: "learn",
|
key: "learn",
|
||||||
label: t("nav.group.learn"),
|
label: t("nav.group.learn"),
|
||||||
links: [
|
links: [
|
||||||
{ label: t("nav.link.home"), href: "/" },
|
{ label: t("nav.link.home"), href: "/", icon: Home },
|
||||||
{ label: t("nav.link.problems"), href: "/problems" },
|
{ label: t("nav.link.problems"), href: "/problems", icon: BookOpen },
|
||||||
{ label: t("nav.link.submissions"), href: "/submissions" },
|
{ label: t("nav.link.submissions"), href: "/submissions", icon: FileText },
|
||||||
{ label: t("nav.link.wrong_book"), href: "/wrong-book" },
|
{ label: t("nav.link.wrong_book"), href: "/wrong-book", icon: BookX },
|
||||||
{ label: t("nav.link.kb"), href: "/kb" },
|
{ label: t("nav.link.kb"), href: "/kb", icon: Library },
|
||||||
{ label: t("nav.link.run"), href: "/run" },
|
{ label: t("nav.link.run"), href: "/run", icon: Play },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "contest",
|
key: "contest",
|
||||||
label: t("nav.group.contest"),
|
label: t("nav.group.contest"),
|
||||||
links: [
|
links: [
|
||||||
{ label: t("nav.link.contests"), href: "/contests" },
|
{ label: t("nav.link.contests"), href: "/contests", icon: Trophy },
|
||||||
{ label: t("nav.link.leaderboard"), href: "/leaderboard" },
|
{ label: t("nav.link.leaderboard"), href: "/leaderboard", icon: Crown },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "account",
|
key: "account",
|
||||||
label: t("nav.group.account"),
|
label: t("nav.group.account"),
|
||||||
links: [
|
links: [
|
||||||
{ label: t("nav.link.auth"), href: "/auth" },
|
{ label: t("nav.link.auth"), href: "/auth", icon: Key },
|
||||||
{ label: t("nav.link.me"), href: "/me" },
|
{ label: t("nav.link.me"), href: "/me", icon: User },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -63,11 +87,11 @@ function buildNavGroups(t: (key: string) => string, isAdmin: boolean): NavGroup[
|
|||||||
key: "system",
|
key: "system",
|
||||||
label: t("nav.group.system"),
|
label: t("nav.group.system"),
|
||||||
links: [
|
links: [
|
||||||
{ label: t("nav.link.imports"), href: "/imports" },
|
{ label: t("nav.link.imports"), href: "/imports", icon: Database },
|
||||||
{ label: t("nav.link.backend_logs"), href: "/backend-logs" },
|
{ label: t("nav.link.backend_logs"), href: "/backend-logs", icon: FileCode },
|
||||||
{ label: t("nav.link.admin_users"), href: "/admin-users" },
|
{ label: t("nav.link.admin_users"), href: "/admin-users", icon: Users },
|
||||||
{ label: t("nav.link.admin_redeem"), href: "/admin-redeem" },
|
{ label: t("nav.link.admin_redeem"), href: "/admin-redeem", icon: Gift },
|
||||||
{ label: t("nav.link.api_docs"), href: "/api-docs" },
|
{ label: t("nav.link.api_docs"), href: "/api-docs", icon: FileJson },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -222,14 +246,17 @@ export function AppNav() {
|
|||||||
<button
|
<button
|
||||||
key={item.href}
|
key={item.href}
|
||||||
type="button"
|
type="button"
|
||||||
className={`block w-full rounded px-3 py-1.5 text-left text-sm ${linkActive ? "bg-zinc-900 text-white" : "hover:bg-zinc-100"
|
className={`flex w-full items-center gap-2 px-3 py-2 text-left text-xs ${linkActive
|
||||||
|
? "mc-btn mc-btn-primary"
|
||||||
|
: "mc-btn hover:brightness-110"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDesktopOpenGroup(null);
|
setDesktopOpenGroup(null);
|
||||||
router.push(item.href);
|
router.push(item.href);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.label}
|
<item.icon size={14} className="shrink-0" />
|
||||||
|
<span>{item.label}</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -336,21 +363,31 @@ export function AppNav() {
|
|||||||
<div className="space-y-3 md:hidden">
|
<div className="space-y-3 md:hidden">
|
||||||
{navGroups.map((group) => (
|
{navGroups.map((group) => (
|
||||||
<section key={group.key} className="rounded-lg border p-2">
|
<section key={group.key} className="rounded-lg border p-2">
|
||||||
<h3 className="text-xs font-semibold text-zinc-600">{group.label}</h3>
|
<h3 className="mb-2 flex items-center gap-2 text-xs font-semibold text-zinc-600">
|
||||||
<select
|
{group.label}
|
||||||
className="mt-2 w-full rounded-md border px-3 py-1 text-xs"
|
</h3>
|
||||||
value={resolveActiveLink(pathname, group)}
|
<div className="grid grid-cols-3 gap-2">
|
||||||
onChange={(e) => {
|
{group.links.map((item) => {
|
||||||
|
const active = isActivePath(pathname, item.href);
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={item.href}
|
||||||
|
type="button"
|
||||||
|
className={`flex flex-col items-center justify-center gap-1 p-2 text-center text-[10px] ${active
|
||||||
|
? "mc-btn mc-btn-primary"
|
||||||
|
: "mc-btn"
|
||||||
|
}`}
|
||||||
|
onClick={() => {
|
||||||
setMenuOpen(false);
|
setMenuOpen(false);
|
||||||
router.push(e.target.value);
|
router.push(item.href);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{group.links.map((item) => (
|
<item.icon size={18} />
|
||||||
<option key={item.href} value={item.href}>
|
<span className="w-full truncate">{item.label}</span>
|
||||||
{item.label}
|
</button>
|
||||||
</option>
|
);
|
||||||
))}
|
})}
|
||||||
</select>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Link from "next/link";
|
|||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
|
|
||||||
import { useUiPreferences } from "@/components/ui-preference-provider";
|
import { useUiPreferences } from "@/components/ui-preference-provider";
|
||||||
|
import { BookOpen, Calendar, ScrollText, Swords, User } from "lucide-react";
|
||||||
|
|
||||||
function isActivePath(pathname: string, href: string): boolean {
|
function isActivePath(pathname: string, href: string): boolean {
|
||||||
if (pathname === href) return true;
|
if (pathname === href) return true;
|
||||||
@@ -16,11 +17,11 @@ export function MobileTabBar() {
|
|||||||
const isMc = theme === "minecraft";
|
const isMc = theme === "minecraft";
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ label: t("mobile.tab.problems"), href: "/problems", icon: "📜" },
|
{ label: t("mobile.tab.problems"), href: "/problems", icon: BookOpen },
|
||||||
{ label: t("mobile.tab.submissions"), href: "/submissions", icon: "⏱️" },
|
{ label: t("mobile.tab.submissions"), href: "/submissions", icon: ScrollText },
|
||||||
{ label: t("mobile.tab.contests"), href: "/contests", icon: "⚔️" },
|
{ label: t("mobile.tab.contests"), href: "/contests", icon: Swords },
|
||||||
{ label: t("mobile.tab.kb"), href: "/kb", icon: "📘" },
|
{ label: t("mobile.tab.kb"), href: "/kb", icon: Calendar }, // KB maps to "Library" usually but keeping order
|
||||||
{ label: t("mobile.tab.me"), href: "/me", icon: "👤" },
|
{ label: t("mobile.tab.me"), href: "/me", icon: User },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -45,7 +46,7 @@ export function MobileTabBar() {
|
|||||||
: "text-zinc-600 hover:bg-zinc-100 rounded-md"
|
: "text-zinc-600 hover:bg-zinc-100 rounded-md"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{isMc && <span className="text-sm mb-0.5">{tab.icon}</span>}
|
{isMc && <tab.icon size={18} className="mb-0.5" />}
|
||||||
<span className="truncate w-full">{tab.label}</span>
|
<span className="truncate w-full">{tab.label}</span>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户