Compare commits

...

7 Commits

Author SHA1 Message Date
jieer
ba36cfdfa2 删除旧的codegraph 2026-07-09 12:11:42 +08:00
5a27c07bba feat(submit): 重构提交流程并加强判题端错误处理
- judge_client 在 solution/problem/source 查询失败时记录系统错误并以 OJ_RE 安全退出,避免判题机崩溃或挂起
- submit.php 抽取 submit_error 助手,AJAX 请求返回纯文本错误,提交页改为 AJAX 流程并增强错误提示
- 修复 reinfo 自动刷新脚本在标签页切回前台后未恢复轮询的 bug
2026-07-09 12:03:12 +08:00
418674806b 🔧 chore(config): 管理员隐藏名单新增 copilot
- 将 copilot 加入 $OJ_RANK_HIDDEN 列表
- 避免该账户出现在比赛排名中
2026-07-09 09:52:03 +08:00
9b13c95e8a ♻️ refactor(reinfo): 重构 diff 解析逻辑并支持多格式
- 将原本分散在 `parse_diff_side_by_side` 与 `parse_pipe_diff_table` 的两套解析逻辑合并,新增统一的 `parse_one_diff_line` 工具函数
- `parse_one_diff_line` 同时识别 `|a|b`、`a | b`、`a<b` / `a>b` 等多种 diff 输出格式,便于后续扩展
- 简化 `parse_diff_blocks` 中 full mode / simple mode 的内联解析流程,消除代码重复
- 优化 `raw_error` 换行符归一化处理,兼容 `\r\n` 与单独 `\r` 结尾的情况
2026-07-08 21:24:08 +08:00
0e5cac3d4e ♻️ refactor(reinfo): 调整 diff 解析函数定义位置
- 将 parse_diff_blocks、parse_pipe_diff_table、parse_diff_side_by_side 移至 dedup_runtimeinfo 之后
- 与相邻辅助函数保持一致的位置,便于阅读和维护
- 精简部分冗余注释
2026-07-08 21:15:49 +08:00
c22c788cfa feat(ui): 判题结果页新增输出对比视图
- 新增 parse_diff_blocks 系列函数,将 runtimeinfo 解析为结构化的测试点对比块,支持 simple 表格和 diff -y 两种格式
- 在判题结果页插入"输出对比"区块,逐测试点并列展示期望输出与用户输出,差异行高亮
- 补充 diff-section 相关样式,移动端切换为单列布局
2026-07-08 21:13:13 +08:00
90010113dd feat(ui): 判题结果页 AI 点评区支持折叠
- 为 LLM 点评标题增加点击折叠/展开交互,减少页面占用空间
- 新增标题悬停反馈与图标旋转动画样式
- 移除快捷操作区中的"下载测试数据"按钮入口
2026-07-08 21:07:56 +08:00
8 changed files with 639 additions and 153 deletions

View File

@@ -1,5 +1,7 @@
# 调试说明 # 调试说明
该项目为局域网本地项目。不需要考虑安全问题。
## 调试环境 ## 调试环境
- 目标网址http://192.168.0.96/ - 目标网址http://192.168.0.96/
@@ -15,3 +17,11 @@
3. 打开网页端提供的 git pull API 触发更新,使服务器拉取最新代码。 3. 打开网页端提供的 git pull API 触发更新,使服务器拉取最新代码。
如需验证调试环境,优先使用上述账号登录目标网址,并通过网页端 git pull API 完成代码同步。 如需验证调试环境,优先使用上述账号登录目标网址,并通过网页端 git pull API 完成代码同步。
## 数据库
```
static $DB_HOST="localhost"; //数据库服务器ip或域名
static $DB_NAME="jol"; //数据库名
static $DB_USER="debian-sys-maint"; //数据库账户
static $DB_PASS="GJfFMuAmScKHHa32"; //数据库密码
```

View File

@@ -1220,6 +1220,17 @@ void addreinfo(int solution_id)
} }
} }
void add_system_error_info(int solution_id, const char *message)
{
FILE *fp = fopen("error.out", "a");
if (fp != NULL)
{
fprintf(fp, "system:%s\n", message);
fclose(fp);
}
addreinfo(solution_id);
}
void adddiffinfo(int solution_id) void adddiffinfo(int solution_id)
{ {
@@ -1670,7 +1681,7 @@ int check_mysql_conn(){
#endif #endif
#ifdef _mysql_h #ifdef _mysql_h
void _get_solution_mysql(int solution_id, char *work_dir, int lang) bool _get_solution_mysql(int solution_id, char *work_dir, int lang)
{ {
char sql[BUFFER_SIZE], src_pth[BUFFER_SIZE]; char sql[BUFFER_SIZE], src_pth[BUFFER_SIZE];
// get the source code // get the source code
@@ -1678,7 +1689,8 @@ void _get_solution_mysql(int solution_id, char *work_dir, int lang)
MYSQL_ROW row; MYSQL_ROW row;
sprintf(sql, "SELECT source FROM source_code WHERE solution_id=%d", sprintf(sql, "SELECT source FROM source_code WHERE solution_id=%d",
solution_id); solution_id);
mysql_real_query(conn, sql, strlen(sql)); if (mysql_real_query(conn, sql, strlen(sql)))
return false;
res = mysql_store_result(conn); res = mysql_store_result(conn);
// create the src file // create the src file
@@ -1691,16 +1703,23 @@ void _get_solution_mysql(int solution_id, char *work_dir, int lang)
if (DEBUG) if (DEBUG)
printf("Main=%s", src_pth); printf("Main=%s", src_pth);
FILE *fp_src = fopen(src_pth, "we"); FILE *fp_src = fopen(src_pth, "we");
if (fp_src == NULL) {
mysql_free_result(res);
return false;
}
fprintf(fp_src, "%s", row[0]); fprintf(fp_src, "%s", row[0]);
mysql_free_result(res); // free the memory mysql_free_result(res); // free the memory
res = NULL; res = NULL;
row = NULL; row = NULL;
fclose(fp_src); fclose(fp_src);
return true;
} }
mysql_free_result(res);
} }
return false;
} }
#endif #endif
void _get_solution_http(int solution_id, char *work_dir, int lang) bool _get_solution_http(int solution_id, char *work_dir, int lang)
{ {
char src_pth[BUFFER_SIZE]; char src_pth[BUFFER_SIZE];
@@ -1716,22 +1735,26 @@ void _get_solution_http(int solution_id, char *work_dir, int lang)
FILE *pout = read_cmd_output(cmd2, solution_id, src_pth, http_baseurl, http_apipath); FILE *pout = read_cmd_output(cmd2, solution_id, src_pth, http_baseurl, http_apipath);
pclose(pout); pclose(pout);
return access(src_pth, R_OK) == 0;
} }
void get_solution(int solution_id, char *work_dir, int lang,int p_id) bool get_solution(int solution_id, char *work_dir, int lang,int p_id)
{ {
char src_pth[BUFFER_SIZE]; char src_pth[BUFFER_SIZE];
bool ret = false;
sprintf(src_pth, "Main.%s", lang_ext[lang]); sprintf(src_pth, "Main.%s", lang_ext[lang]);
if (http_judge) if (http_judge)
{ {
_get_solution_http(solution_id, work_dir, lang); ret = _get_solution_http(solution_id, work_dir, lang);
} }
else else
{ {
#ifdef _mysql_h #ifdef _mysql_h
_get_solution_mysql(solution_id, work_dir, lang); ret = _get_solution_mysql(solution_id, work_dir, lang);
#endif #endif
} }
if (!ret)
return false;
if(lang == LANG_PYTHON ){ // 从源码中搜索python2字样失败的结果非零默认python3,成功的结果为0是python2 if(lang == LANG_PYTHON ){ // 从源码中搜索python2字样失败的结果非零默认python3,成功的结果为0是python2
py2 = execute_cmd("/bin/grep 'python2' %s/Main.py > /dev/null", work_dir); py2 = execute_cmd("/bin/grep 'python2' %s/Main.py > /dev/null", work_dir);
execute_cmd("sed -i 's/import.*os//g' %s/%s", work_dir, src_pth); execute_cmd("sed -i 's/import.*os//g' %s/%s", work_dir, src_pth);
@@ -1740,6 +1763,7 @@ void get_solution(int solution_id, char *work_dir, int lang,int p_id)
execute_cmd("cp %s/../data/%d/sb3/%d.sb3 %s", work_dir,p_id,solution_id, src_pth); execute_cmd("cp %s/../data/%d/sb3/%d.sb3 %s", work_dir,p_id,solution_id, src_pth);
} }
execute_cmd("chown judge %s/%s", work_dir, src_pth); execute_cmd("chown judge %s/%s", work_dir, src_pth);
return access(src_pth, R_OK) == 0;
} }
#ifdef _mysql_h #ifdef _mysql_h
@@ -1804,7 +1828,7 @@ void get_custominput(int solution_id, char *work_dir)
} }
#ifdef _mysql_h #ifdef _mysql_h
void _get_solution_info_mysql(int solution_id, int & p_id, char * user_id, bool _get_solution_info_mysql(int solution_id, int & p_id, char * user_id,
int & lang,int &cid) { int & lang,int &cid) {
MYSQL_RES *res; MYSQL_RES *res;
@@ -1818,7 +1842,8 @@ void _get_solution_info_mysql(int solution_id, int & p_id, char * user_id,
"insert into solution2 select * FROM solution where solution_id=%d", "insert into solution2 select * FROM solution where solution_id=%d",
solution_id); solution_id);
//printf("%s\n",sql); //printf("%s\n",sql);
mysql_real_query(conn, sql, strlen(sql)); if (mysql_real_query(conn, sql, strlen(sql)))
return false;
sprintf(sql, sprintf(sql,
"SELECT problem_id, user_id, language,contest_id FROM solution2 where solution_id=%d", "SELECT problem_id, user_id, language,contest_id FROM solution2 where solution_id=%d",
solution_id); solution_id);
@@ -1831,9 +1856,16 @@ void _get_solution_info_mysql(int solution_id, int & p_id, char * user_id,
solution_id); solution_id);
} }
//printf("%s\n",sql); //printf("%s\n",sql);
mysql_real_query(conn, sql, strlen(sql)); if (mysql_real_query(conn, sql, strlen(sql)))
return false;
res = mysql_store_result(conn); res = mysql_store_result(conn);
if (res == NULL)
return false;
row = mysql_fetch_row(res); row = mysql_fetch_row(res);
if (row == NULL) {
mysql_free_result(res);
return false;
}
p_id = atoi(row[0]); p_id = atoi(row[0]);
strcpy(user_id, row[1]); strcpy(user_id, row[1]);
lang = atoi(row[2]); lang = atoi(row[2]);
@@ -1844,9 +1876,10 @@ void _get_solution_info_mysql(int solution_id, int & p_id, char * user_id,
mysql_free_result(res); // free the memory mysql_free_result(res); // free the memory
res=NULL; res=NULL;
} }
return true;
} }
#endif #endif
void _get_solution_info_http(int solution_id, int & p_id, char * user_id, bool _get_solution_info_http(int solution_id, int & p_id, char * user_id,
int & lang,int & cid) { int & lang,int & cid) {
login(); login();
@@ -1854,26 +1887,29 @@ void _get_solution_info_http(int solution_id, int & p_id, char * user_id,
const char *cmd = const char *cmd =
"wget --post-data=\"getsolutioninfo=1&sid=%d\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O - \"%s%s\""; "wget --post-data=\"getsolutioninfo=1&sid=%d\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O - \"%s%s\"";
FILE *pout = read_cmd_output(cmd, solution_id, http_baseurl, http_apipath); FILE *pout = read_cmd_output(cmd, solution_id, http_baseurl, http_apipath);
if(1!=fscanf(pout, "%d", &p_id)) printf("http problem_id read fail...\n"); bool ret = true;
if(1!=fscanf(pout, "%s", user_id)) printf("http user_id read fail ... \n") ; if(1!=fscanf(pout, "%d", &p_id)) { printf("http problem_id read fail...\n"); ret = false; }
if(1!=fscanf(pout, "%d", &lang)) printf("http language read fail ... \n") ; if(1!=fscanf(pout, "%s", user_id)) { printf("http user_id read fail ... \n") ; ret = false; }
if(1!=fscanf(pout, "%d", &cid)) printf("http contest_id read fail ... \n") ; if(1!=fscanf(pout, "%d", &lang)) { printf("http language read fail ... \n") ; ret = false; }
if(1!=fscanf(pout, "%d", &cid)) { printf("http contest_id read fail ... \n") ; ret = false; }
pclose(pout); pclose(pout);
return ret;
} }
void get_solution_info(int solution_id, int & p_id, char * user_id, bool get_solution_info(int solution_id, int & p_id, char * user_id,
int & lang,int & cid) { int & lang,int & cid) {
if (http_judge) { if (http_judge) {
_get_solution_info_http(solution_id, p_id, user_id, lang,cid); return _get_solution_info_http(solution_id, p_id, user_id, lang,cid);
} else { } else {
#ifdef _mysql_h #ifdef _mysql_h
_get_solution_info_mysql(solution_id, p_id, user_id, lang,cid); return _get_solution_info_mysql(solution_id, p_id, user_id, lang,cid);
#endif #endif
} }
return false;
} }
#ifdef _mysql_h #ifdef _mysql_h
void _get_problem_info_mysql(int p_id, double &time_lmt, int &mem_lmt, bool _get_problem_info_mysql(int p_id, double &time_lmt, int &mem_lmt,
int &spj) int &spj)
{ {
// get the problem info from Table:problem // get the problem info from Table:problem
@@ -1883,9 +1919,16 @@ void _get_problem_info_mysql(int p_id, double &time_lmt, int &mem_lmt,
sprintf(sql, sprintf(sql,
"SELECT time_limit,memory_limit,spj FROM problem where problem_id=%d", "SELECT time_limit,memory_limit,spj FROM problem where problem_id=%d",
p_id); p_id);
mysql_real_query(conn, sql, strlen(sql)); if (mysql_real_query(conn, sql, strlen(sql)))
return false;
res = mysql_store_result(conn); res = mysql_store_result(conn);
if (res == NULL)
return false;
row = mysql_fetch_row(res); row = mysql_fetch_row(res);
if (row == NULL) {
mysql_free_result(res);
return false;
}
time_lmt = atof(row[0]); time_lmt = atof(row[0]);
mem_lmt = atoi(row[1]); mem_lmt = atoi(row[1]);
spj = atoi(row[2]); spj = atoi(row[2]);
@@ -1894,9 +1937,10 @@ void _get_problem_info_mysql(int p_id, double &time_lmt, int &mem_lmt,
mysql_free_result(res); // free the memory mysql_free_result(res); // free the memory
res = NULL; res = NULL;
} }
return true;
} }
#endif #endif
void _get_problem_info_http(int p_id, double &time_lmt, int &mem_lmt, bool _get_problem_info_http(int p_id, double &time_lmt, int &mem_lmt,
int &spj) int &spj)
{ {
//login(); //login();
@@ -1904,27 +1948,32 @@ void _get_problem_info_http(int p_id, double &time_lmt, int &mem_lmt,
const char *cmd = const char *cmd =
"wget --post-data=\"getprobleminfo=1&pid=%d\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O - \"%s%s\""; "wget --post-data=\"getprobleminfo=1&pid=%d\" --load-cookies=cookie --save-cookies=cookie --keep-session-cookies -q -O - \"%s%s\"";
FILE *pout = read_cmd_output(cmd, p_id, http_baseurl, http_apipath); FILE *pout = read_cmd_output(cmd, p_id, http_baseurl, http_apipath);
if(1!=fscanf(pout, "%lf", &time_lmt)) printf("http read time_limit fail...\n"); bool ret = true;
if(1!=fscanf(pout, "%d", &mem_lmt) ) printf("http read memory_limit fail...\n"); if(1!=fscanf(pout, "%lf", &time_lmt)) { printf("http read time_limit fail...\n"); ret = false; }
if(1!=fscanf(pout, "%d", &spj) ) printf("http read special judge fail...\n"); if(1!=fscanf(pout, "%d", &mem_lmt) ) { printf("http read memory_limit fail...\n"); ret = false; }
if(1!=fscanf(pout, "%d", &spj) ) { printf("http read special judge fail...\n"); ret = false; }
pclose(pout); pclose(pout);
if(DEBUG) printf("time_lmt:%g\n",time_lmt); if(DEBUG) printf("time_lmt:%g\n",time_lmt);
return ret;
} }
void get_problem_info(int p_id, double &time_lmt, int &mem_lmt, int &spj) bool get_problem_info(int p_id, double &time_lmt, int &mem_lmt, int &spj)
{ {
if (http_judge) if (http_judge)
{ {
_get_problem_info_http(p_id, time_lmt, mem_lmt, spj); if (!_get_problem_info_http(p_id, time_lmt, mem_lmt, spj))
return false;
} }
else else
{ {
#ifdef _mysql_h #ifdef _mysql_h
_get_problem_info_mysql(p_id, time_lmt, mem_lmt, spj); if (!_get_problem_info_mysql(p_id, time_lmt, mem_lmt, spj))
return false;
#endif #endif
} }
if (time_lmt <= 0) if (time_lmt <= 0)
time_lmt = 1; time_lmt = 1;
return true;
} }
char *escape(char s[], char t[]) char *escape(char s[], char t[])
{ {
@@ -3455,6 +3504,8 @@ int main(int argc, char **argv)
double time_lmt; double time_lmt;
char time_space_table[BUFFER_SIZE*100]; char time_space_table[BUFFER_SIZE*100];
int time_space_index=0; int time_space_index=0;
time_space_table[0]=0;
user_id[0]=0;
umask(0077); umask(0077);
init_parameters(argc, argv, solution_id, runner_id); init_parameters(argc, argv, solution_id, runner_id);
@@ -3489,7 +3540,27 @@ int main(int argc, char **argv)
if (http_judge) if (http_judge)
if(!system("/bin/ln -s ../cookie ./")) printf("cookie link fail \n"); if(!system("/bin/ln -s ../cookie ./")) printf("cookie link fail \n");
get_solution_info(solution_id, p_id, user_id, lang,cid); if (!get_solution_info(solution_id, p_id, user_id, lang,cid)) {
add_system_error_info(solution_id, "solution info not found");
update_solution(solution_id, OJ_RE, 0, 0, 0, 0, 0.0);
#ifdef _mysql_h
if (!http_judge)
mysql_close(conn);
#endif
clean_workdir(work_dir);
exit(0);
}
if (lang < 0 || lang >= (int)(sizeof(lang_ext) / sizeof(lang_ext[0]))) {
add_system_error_info(solution_id, "invalid language id");
update_solution(solution_id, OJ_RE, 0, 0, 0, 0, 0.0);
if(!turbo_mode)update_user(user_id);
#ifdef _mysql_h
if (!http_judge)
mysql_close(conn);
#endif
clean_workdir(work_dir);
exit(0);
}
//get the limit //get the limit
if (p_id == 0) if (p_id == 0)
@@ -3500,11 +3571,33 @@ int main(int argc, char **argv)
} }
else else
{ {
get_problem_info(p_id, time_lmt, mem_lmt, spj); if (!get_problem_info(p_id, time_lmt, mem_lmt, spj)) {
add_system_error_info(solution_id, "problem info not found");
update_solution(solution_id, OJ_RE, 0, 0, 0, 0, 0.0);
if(!turbo_mode)update_user(user_id);
if(!turbo_mode)update_problem(p_id,cid);
#ifdef _mysql_h
if (!http_judge)
mysql_close(conn);
#endif
clean_workdir(work_dir);
exit(0);
}
} }
//copy source file //copy source file
get_solution(solution_id, work_dir, lang,p_id); if (!get_solution(solution_id, work_dir, lang,p_id)) {
add_system_error_info(solution_id, "source code not found");
update_solution(solution_id, OJ_RE, 0, 0, 0, 0, 0.0);
if(!turbo_mode)update_user(user_id);
if(!turbo_mode)update_problem(p_id,cid);
#ifdef _mysql_h
if (!http_judge)
mysql_close(conn);
#endif
clean_workdir(work_dir);
exit(0);
}
//java and other VM language are lucky to have the global bonus in judge.conf //java and other VM language are lucky to have the global bonus in judge.conf
if (lang >= LANG_JAVA && lang != LANG_OBJC && lang != LANG_CLANG && lang != LANG_CLANGPP && lang != LANG_GO ) if (lang >= LANG_JAVA && lang != LANG_OBJC && lang != LANG_CLANG && lang != LANG_CLANGPP && lang != LANG_GO )
@@ -3581,11 +3674,18 @@ int main(int argc, char **argv)
if(p_id > 0 && namelist_len == -1 ){ if(p_id > 0 && namelist_len == -1 ){
write_log("No such dir:%s!\n", fullpath); write_log("No such dir:%s!\n", fullpath);
char error[BUFFER_SIZE];
snprintf(error, BUFFER_SIZE - 1, "test data directory not found: %s", fullpath);
add_system_error_info(solution_id, error);
update_solution(solution_id, OJ_RE, 0, 0, 0, 0, 0.0);
if(!turbo_mode)update_user(user_id);
if(!turbo_mode)update_problem(p_id,cid);
#ifdef _mysql_h #ifdef _mysql_h
if (!http_judge) if (!http_judge)
mysql_close(conn); mysql_close(conn);
#endif #endif
exit(-1); clean_workdir(work_dir);
exit(0);
}else{ }else{
if(DEBUG){ if(DEBUG){
@@ -3702,6 +3802,7 @@ int main(int argc, char **argv)
} }
char last_name[BUFFER_SIZE]; char last_name[BUFFER_SIZE];
last_name[0]=0;
int minus_mark=0; int minus_mark=0;
char path_buf[BUFFER_SIZE]; char path_buf[BUFFER_SIZE];
sprintf(path_buf,"%s/data/%d/",oj_home,p_id); sprintf(path_buf,"%s/data/%d/",oj_home,p_id);

View File

@@ -81,7 +81,7 @@ static $OJ_BENCHMARK_MODE=false; //此选项仅供测试用,不是正常功
static $OJ_CONTEST_RANK_FIX_HEADER=false; //比赛排名水平滚动时固定名单 static $OJ_CONTEST_RANK_FIX_HEADER=false; //比赛排名水平滚动时固定名单
static $OJ_NOIP_KEYWORD="noip"; // 标题包含此关键词激活noip模式赛中不显示结果仅保留最后一次提交。 static $OJ_NOIP_KEYWORD="noip"; // 标题包含此关键词激活noip模式赛中不显示结果仅保留最后一次提交。
static $OJ_BEIAN=false; // 如果有备案号,填写备案号 static $OJ_BEIAN=false; // 如果有备案号,填写备案号
static $OJ_RANK_HIDDEN="'admin','super','szx','sen'"; // 管理员不显示在排名中 static $OJ_RANK_HIDDEN="'admin','super','szx','sen','copilot'"; // 管理员不显示在排名中
static $OJ_FRIENDLY_LEVEL=0; //系统友好级别暂定0-9级级别越高越傻瓜系统易用度高的同时将降低安全性仅供非专业用途造成泄题、抄袭概不负责。 static $OJ_FRIENDLY_LEVEL=0; //系统友好级别暂定0-9级级别越高越傻瓜系统易用度高的同时将降低安全性仅供非专业用途造成泄题、抄袭概不负责。
static $OJ_FREE_PRACTICE=true; //自由练习,不受比赛作业用题限制 static $OJ_FREE_PRACTICE=true; //自由练习,不受比赛作业用题限制
static $OJ_SUBMIT_COOLDOWN_TIME=1; //提交冷却时间,连续两次提交的最小间隔,单位秒。 static $OJ_SUBMIT_COOLDOWN_TIME=1; //提交冷却时间,连续两次提交的最小间隔,单位秒。

View File

@@ -102,6 +102,145 @@ function dedup_runtimeinfo($text, &$dedup_count) {
return implode("\n", $out); return implode("\n", $out);
} }
/**
* 把 runtimeinfo.error 解析为结构化的 diff blocks
* 返回 [['name' => 'test1.out', 'expected' => [...], 'yours' => [...], 'full_mode' => bool], ...]
* 解析失败返回空数组
*/
function parse_diff_blocks($text) {
$blocks = array();
if (empty($text)) return $blocks;
// 先按 "\n\n" 切分simple mode 每个测试点之间有空行)
$chunks = preg_split('/\n\s*\n/', $text);
foreach ($chunks as $chunk) {
$chunk = trim($chunk);
if ($chunk === '') continue;
// 格式 1: 整段就是一个 =====[name]===== 块(包含表头+数据+结尾 ====
// 形如 "========[test1.out]========\nExpected | Yours\nFail | Fall\n=============================="
if (preg_match('/^=+\[([^\]]+)\]\=+(.+?)\n=+\s*$/s', $chunk, $m)) {
$name = trim($m[1]);
$body = $m[2];
// body 形如 "\nExpected | Yours\nFail | Fall"
// 去掉首尾的换行符
$body = trim($body, "\n\r");
$lines_in_body = explode("\n", $body);
$expected = array();
$yours = array();
$found_header = false;
foreach ($lines_in_body as $bl) {
$bl = rtrim($bl);
if ($bl === '') continue;
// 跳过表头
if (!$found_header && preg_match('/^Expected.*\|.*Yours/i', $bl)) {
$found_header = true;
continue;
}
if (!$found_header && (preg_match('/^\|?--\s*\|/', $bl) || trim($bl) === '--')) {
continue;
}
$parsed = parse_one_diff_line($bl);
if ($parsed !== null) {
$expected[] = $parsed[0];
$yours[] = $parsed[1];
$found_header = true;
} else {
$expected[] = $bl;
$yours[] = '';
$found_header = true;
}
}
if (!empty($expected)) {
$blocks[] = array(
'name' => $name,
'expected' => $expected,
'yours' => $yours,
'full_mode' => true,
);
}
continue;
}
// 格式 2: simple mode "test1.out\n--\n|Expected|Yours\n|--|--\n|row1|row2"
if (preg_match('/^([^\n=|]+)\n--\n([\s\S]+)$/', $chunk, $m)) {
$name = trim($m[1]);
$body = $m[2];
$lines_in_body = explode("\n", $body);
$expected = array();
$yours = array();
$found_header = false;
foreach ($lines_in_body as $bl) {
$bl = rtrim($bl);
if ($bl === '') continue;
if (!$found_header && preg_match('/Expected.*\|.*Yours/i', $bl)) {
$found_header = true;
continue;
}
if (!$found_header && (preg_match('/^\|?--\s*\|/', $bl) || trim($bl) === '--')) {
continue;
}
$parsed = parse_one_diff_line($bl);
if ($parsed !== null) {
$expected[] = $parsed[0];
$yours[] = $parsed[1];
$found_header = true;
} else {
$expected[] = $bl;
$yours[] = '';
$found_header = true;
}
}
if (!empty($expected)) {
$blocks[] = array(
'name' => $name,
'expected' => $expected,
'yours' => $yours,
'full_mode' => false,
);
}
}
}
return $blocks;
}
/**
* 解析单行 "expected | yours" 格式
* 支持 "|a|b"、"a | b"、"a|b" 等
* 返回 [left, right] 或 null无法解析
*/
function parse_one_diff_line($line) {
$line = rtrim($line);
if ($line === '') return null;
// 格式 A: "|a|b" (首尾带 |)
if ($line[0] === '|') {
$rest = substr($line, 1);
$pos = strpos($rest, '|');
if ($pos !== false) {
return array(substr($rest, 0, $pos), substr($rest, $pos + 1));
}
}
// 格式 B: "a | b" 或 "a | b"(带空格的 markdown 风格)
// 注意:可能行内有空格,所以用 " | " 或 " | " 等宽松匹配
if (preg_match('/^(.*?)\s+\|\s+(.*)$/', $line, $m)) {
// 确保左右两边都非空
$left = trim($m[1]);
$right = trim($m[2]);
if ($left !== '' || $right !== '') {
return array($left, $right);
}
}
// 格式 C: "a<b" 或 "a>b"diff -y 风格)
if (preg_match('/^(.*?)\s+([<>])\s+(.*)$/', $line, $m)) {
return array($m[1], $m[3]);
}
return null;
}
$view_reinfo = ""; $view_reinfo = "";
$view_reinfo_raw = ""; // 原始(管理员/有权限者看) $view_reinfo_raw = ""; // 原始(管理员/有权限者看)
$view_reinfo_summary = ""; // 折叠后(普通用户看) $view_reinfo_summary = ""; // 折叠后(普通用户看)
@@ -120,7 +259,9 @@ if($ok){
$rt = pdo_query($sql,$id); $rt = pdo_query($sql,$id);
$raw_error = ""; $raw_error = "";
if(isset($rt[0])){ if(isset($rt[0])){
$raw_error = str_replace("\n\r","\n",$rt[0]['error']); $raw_error = $rt[0]['error'];
// 统一换行符:先替换 \r\n再替换单独的 \r
$raw_error = str_replace(array("\r\n", "\r"), "\n", $raw_error);
} }
// === 阶段 1基于 solution.result 给"主解释"(永远非空)=== // === 阶段 1基于 solution.result 给"主解释"(永远非空)===
@@ -203,6 +344,9 @@ if($ok){
$view_reinfo = $view_reinfo_summary; $view_reinfo = $view_reinfo_summary;
// 尝试解析为结构化的 diff 数据(用于 WA/PE/AC 时的对比展示)
$diff_blocks = parse_diff_blocks($raw_error);
// 管理员或 source_browser 永远看完整原文 // 管理员或 source_browser 永远看完整原文
$show_raw = $is_admin_session || $is_source_browser; $show_raw = $is_admin_session || $is_source_browser;
if ($show_raw) { if ($show_raw) {

View File

@@ -3,19 +3,37 @@ require_once "include/db_info.inc.php";
require_once "include/my_func.inc.php"; require_once "include/my_func.inc.php";
require_once "include/email.class.php"; require_once "include/email.class.php";
require_once "include/base64.php"; require_once "include/base64.php";
require_once "include/const.inc.php";
$is_ajax_submit = isset($_GET['ajax']) || isset($_POST['ajax']);
function submit_error($message) {
global $OJ_TEMPLATE, $is_ajax_submit, $view_errors;
if ($is_ajax_submit) {
header("Content-Type: text/plain; charset=UTF-8");
$text = trim(html_entity_decode(strip_tags($message), ENT_QUOTES, "UTF-8"));
if ($text === "") {
$text = "Submit failed.";
}
echo $text;
exit(0);
}
$view_errors = $message;
require "template/".$OJ_TEMPLATE."/error.php";
exit(0);
}
if(isset($OJ_CSRF) && $OJ_CSRF && $OJ_TEMPLATE=="syzoj" && !isset($_SESSION[$OJ_NAME.'_'.'http_judge'])) if(isset($OJ_CSRF) && $OJ_CSRF && $OJ_TEMPLATE=="syzoj" && !isset($_SESSION[$OJ_NAME.'_'.'http_judge']))
require_once(dirname(__FILE__)."/include/csrf_check.php"); require_once(dirname(__FILE__)."/include/csrf_check.php");
if (!isset($_SESSION[$OJ_NAME . '_' . 'user_id'])) { if (!isset($_SESSION[$OJ_NAME . '_' . 'user_id'])) {
$view_errors = "<a href=loginpage.php>$MSG_Login</a>"; submit_error("<a href=loginpage.php>$MSG_Login</a>");
require("template/".$OJ_TEMPLATE."/error.php");
exit(0);
} }
require_once "include/memcache.php"; require_once "include/memcache.php";
require_once "include/const.inc.php";
$now = strftime("%Y-%m-%d %H:%M", time()); $now = strftime("%Y-%m-%d %H:%M", time());
$user_id = $_SESSION[$OJ_NAME.'_'.'user_id']; $user_id = $_SESSION[$OJ_NAME.'_'.'user_id'];
@@ -41,10 +59,8 @@ if (!$OJ_BENCHMARK_MODE) {
$_SESSION[$OJ_NAME.'_'."vcode"] = null; $_SESSION[$OJ_NAME.'_'."vcode"] = null;
$err_str = $err_str.$MSG_VCODE_WRONG."\n"; $err_str = $err_str.$MSG_VCODE_WRONG."\n";
$err_cnt++; $err_cnt++;
$view_errors = $err_str;
$_SESSION[ $OJ_NAME . '_' . "vfail" ]=true; $_SESSION[ $OJ_NAME . '_' . "vfail" ]=true;
require "template/".$OJ_TEMPLATE."/error.php"; submit_error($err_str);
exit(0);
}else{ }else{
$_SESSION[ $OJ_NAME . '_' . "vfail" ]=false; $_SESSION[ $OJ_NAME . '_' . "vfail" ]=false;
} }
@@ -74,24 +90,13 @@ else {
if(!$test_run){ if(!$test_run){
$res = mysql_query_cache($sql); $res = mysql_query_cache($sql);
if (isset($res) && count($res)<1 && !isset($_SESSION[$OJ_NAME.'_'.'administrator']) && !((isset($cid)&&$cid<=0) || (isset($id)&&$id<=0))) { if (isset($res) && count($res)<1 && !isset($_SESSION[$OJ_NAME.'_'.'administrator']) && !((isset($cid)&&$cid<=0) || (isset($id)&&$id<=0))) {
$view_errors = $MSG_LINK_ERROR."<br>"; submit_error($MSG_LINK_ERROR."<br>");
require "template/".$OJ_TEMPLATE."/error.php";
exit(0);
} }
} }
if ($res[0][1]!='N' && !($test_run||isset($_SESSION[$OJ_NAME.'_'.'administrator']))) { if (!$test_run && isset($res[0]) && $res[0][1]!='N' && !isset($_SESSION[$OJ_NAME.'_'.'administrator'])) {
// echo "res:$res,count:".count($res); // echo "res:$res,count:".count($res);
// echo "$sql"; // echo "$sql";
$view_errors = $MSG_PROBLEM_RESERVED."<br>"; submit_error($MSG_PROBLEM_RESERVED."<br>".$res[0][1]);
if (isset($_POST['ajax'])) {
echo $view_errors.$res[0][1];
exit(0);
}
else {
require "template/".$OJ_TEMPLATE."/error.php";
}
exit(0);
} }
$title = ""; $title = "";
@@ -118,10 +123,7 @@ else if (isset($_POST['pid']) && isset($_POST['cid']) && $_POST['cid']!=0) {
$rows_cnt = count($result); $rows_cnt = count($result);
if ($rows_cnt != 1) { if ($rows_cnt != 1) {
$view_errors .= $MSG_NOT_IN_CONTEST; submit_error($MSG_NOT_IN_CONTEST);
require "template/" . $OJ_TEMPLATE . "/error.php";
exit(0);
} }
else { else {
$row = $result[0]; $row = $result[0];
@@ -137,9 +139,7 @@ else if (isset($_POST['pid']) && isset($_POST['cid']) && $_POST['cid']!=0) {
$ccnt = intval($row[0]); $ccnt = intval($row[0]);
if ($ccnt==0 && !isset($_SESSION[$OJ_NAME.'_'.'administrator'])) { if ($ccnt==0 && !isset($_SESSION[$OJ_NAME.'_'.'administrator'])) {
$view_errors = $MSG_NOT_INVITED."\n"; submit_error($MSG_NOT_INVITED."\n");
require "template/" . $OJ_TEMPLATE . "/error.php";
exit(0);
} }
} }
} }
@@ -150,9 +150,7 @@ else if (isset($_POST['pid']) && isset($_POST['cid']) && $_POST['cid']!=0) {
$rows_cnt = count($result); $rows_cnt = count($result);
if ($rows_cnt != 1) { if ($rows_cnt != 1) {
$view_errors = $MSG_NO_PROBLEM."\n"; submit_error($MSG_NO_PROBLEM."\n");
require "template/".$OJ_TEMPLATE."/error.php";
exit(0);
} }
else { else {
$row = $result[0]; $row = $result[0];
@@ -182,12 +180,10 @@ if ($language > count($language_name) || $language < 0) {
$language = strval($language); $language = strval($language);
if ($langmask&(1<<$language)) { if ($langmask&(1<<$language)) {
$view_errors = $MSG_NO_PLS."\n[$language][$langmask][".($langmask&(1<<$language))."]"; submit_error($MSG_NO_PLS."\n[$language][$langmask][".($langmask&(1<<$language))."]");
require "template/".$OJ_TEMPLATE."/error.php";
exit(0);
} }
$source = $_POST['source']; $source = isset($_POST['source']) ? $_POST['source'] : "";
$input_text = ""; $input_text = "";
if (isset($_POST['input_text'])) { if (isset($_POST['input_text'])) {
@@ -206,7 +202,7 @@ if ($test_run) {
$id = -$id; $id = -$id;
} }
$tempfile = $_FILES ["answer"] ["tmp_name"]; $tempfile = (isset($_FILES["answer"]) && isset($_FILES["answer"]["tmp_name"])) ? $_FILES["answer"]["tmp_name"] : "";
if($tempfile!=""){ if($tempfile!=""){
if($language!=23){ if($language!=23){
@@ -217,7 +213,7 @@ if($tempfile!=""){
$source="Main.sb3"; $source="Main.sb3";
} }
} }
$origin_name=trim($_FILES ["answer"]['name']); $origin_name=(isset($_FILES["answer"]) && isset($_FILES["answer"]['name'])) ? trim($_FILES["answer"]['name']) : "";
$solution_file = "$OJ_DATA/$id/solution.name"; $solution_file = "$OJ_DATA/$id/solution.name";
if(file_exists($solution_file)){ if(file_exists($solution_file)){
$solution_name=trim(file_get_contents($solution_file)); $solution_name=trim(file_get_contents($solution_file));
@@ -255,7 +251,7 @@ if ($test_run) {
$id = 0; $id = 0;
} }
if($language!=23) $len = strlen($source);else $len = $_FILES['answer']['size']; if($language!=23) $len = strlen($source);else $len = (isset($_FILES['answer']) ? intval($_FILES['answer']['size']) : 0);
//echo $source; //echo $source;
setcookie('lastlang', $language, time()+360000); setcookie('lastlang', $language, time()+360000);
@@ -270,15 +266,11 @@ if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
if ($len < 2) { if ($len < 2) {
$view_errors = $MSG_TOO_SHORT.$tempfile."<br>"; submit_error($MSG_TOO_SHORT.$tempfile."<br>");
require "template/".$OJ_TEMPLATE."/error.php";
exit(0);
} }
if ($len > 65536) { if ($len > 65536) {
$view_errors = $MSG_TOO_LONG."<br>"; submit_error($MSG_TOO_LONG."<br>");
require "template/" . $OJ_TEMPLATE . "/error.php";
exit(0);
} }
if (!$OJ_BENCHMARK_MODE) { if (!$OJ_BENCHMARK_MODE) {
@@ -295,8 +287,8 @@ if (!$OJ_BENCHMARK_MODE) {
exit(0); exit(0);
// 预防WAF抽风 // 预防WAF抽风
*/ */
if(isset($_GET['ajax'])){ if($is_ajax_submit){
echo -1; submit_error(str_replace("10",$OJ_SUBMIT_COOLDOWN_TIME,$MSG_BREAK_TIME));
}else{ }else{
$statusURI = "status.php?user_id=".$_SESSION[$OJ_NAME.'_'.'user_id']; $statusURI = "status.php?user_id=".$_SESSION[$OJ_NAME.'_'.'user_id'];
if (isset($cid)) { if (isset($cid)) {
@@ -373,15 +365,17 @@ if (~$OJ_LANGMASK&(1<<$language)) {
////remote oj ////remote oj
$result=0; $result=0;
$sql="select remote_oj from problem where problem_id=?"; if (!$test_run && isset($OJ_REMOTE_JUDGE) && $OJ_REMOTE_JUDGE) {
$remote_oj=pdo_query($sql,$id); $sql="select remote_oj from problem where problem_id=?";
if(isset($remote_oj[0])){ $remote_oj=pdo_query($sql,$id);
$remote_oj=$remote_oj[0][0]; if(isset($remote_oj[0])){
if($remote_oj!=""){ $remote_oj=$remote_oj[0][0];
$result=16; if($remote_oj!=""){
$sql="update solution set result=16,remote_oj=? where solution_id=?"; $result=16;
pdo_query($sql,$remote_oj,$insert_id); $sql="update solution set result=16,remote_oj=? where solution_id=?";
} pdo_query($sql,$remote_oj,$insert_id);
}
}
} }
////poison robot account,give system resources to the REAL people ////poison robot account,give system resources to the REAL people
@@ -501,11 +495,11 @@ if (isset($cid)) {
$statusURI .= "&cid=$cid&fixed="; $statusURI .= "&cid=$cid&fixed=";
} }
if (!$test_run&&!isset($_GET['ajax'])) { if (!$test_run&&!$is_ajax_submit) {
header("Location: $statusURI"); header("Location: $statusURI");
} }
else { else {
if (isset($_GET['ajax'])) { if ($is_ajax_submit) {
echo $insert_id; echo $insert_id;
} }
else { else {

View File

@@ -101,6 +101,20 @@
#llm-section .ui.header { #llm-section .ui.header {
margin-top: 0; margin-top: 0;
} }
#llm-section .llm-section-header {
transition: background-color 0.15s;
padding: 4px 8px;
margin-left: -8px;
margin-right: -8px;
border-radius: 4px;
}
#llm-section .llm-section-header:hover {
background-color: rgba(33,150,243,0.06);
}
#llm-section .llm-toggle-icon {
transition: transform 0.2s;
color: #888;
}
#llm-review-result { #llm-review-result {
word-break: break-word; word-break: break-word;
} }
@@ -119,6 +133,99 @@ i.icon.spinning {
color: #2185d0; color: #2185d0;
font-weight: 600; font-weight: 600;
} }
/* Diff 对比样式 */
#diff-section {
margin-bottom: 18px;
}
.diff-card {
margin-bottom: 14px;
border-left: 4px solid #fbbd08;
padding: 12px 16px;
}
.diff-header {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid #eee;
font-weight: 600;
color: #555;
}
.diff-name {
font-family: 'Fira Mono', 'Cascadia Code', Consolas, monospace;
font-size: 0.95em;
}
.diff-table {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
@media (max-width: 768px) {
.diff-table { grid-template-columns: 1fr; }
}
.diff-side {
background: #fafafa;
border-radius: 6px;
overflow: hidden;
border: 1px solid #e0e0e0;
}
.diff-side-header {
padding: 6px 10px;
font-size: 0.85em;
font-weight: 600;
color: #fff;
}
.diff-expected .diff-side-header {
background: #21ba45;
}
.diff-yours .diff-side-header {
background: #db2828;
}
.diff-content {
margin: 0 !important;
padding: 8px 0 !important;
background: #fff !important;
font-family: 'Fira Mono', 'Cascadia Code', Consolas, monospace;
font-size: 0.85em;
line-height: 1.5;
overflow-x: auto;
max-height: 400px;
overflow-y: auto;
}
.diff-line {
display: flex;
align-items: flex-start;
padding: 0;
white-space: pre;
}
.diff-line-mismatch {
background: rgba(255, 230, 100, 0.25);
}
.diff-expected .diff-line-mismatch {
background: rgba(33, 186, 69, 0.12);
}
.diff-yours .diff-line-mismatch {
background: rgba(219, 40, 40, 0.10);
}
.diff-lineno {
display: inline-block;
min-width: 38px;
padding: 0 8px;
text-align: right;
color: #999;
background: #f5f5f5;
border-right: 1px solid #e8e8e8;
user-select: none;
flex-shrink: 0;
}
.diff-text {
padding: 0 10px;
flex: 1;
word-break: break-all;
white-space: pre-wrap;
}
</style> </style>
<div class="padding"> <div class="padding">
@@ -129,6 +236,8 @@ i.icon.spinning {
$verdict_memory = isset($row['memory']) ? intval($row['memory']) : 0; $verdict_memory = isset($row['memory']) ? intval($row['memory']) : 0;
$verdict_time = isset($row['time']) ? intval($row['time']) : 0; $verdict_time = isset($row['time']) ? intval($row['time']) : 0;
$verdict_mark = isset($mark) ? $mark : (isset($row['pass_rate']) ? round($row['pass_rate']*100, 1) : 0); $verdict_mark = isset($mark) ? $mark : (isset($row['pass_rate']) ? round($row['pass_rate']*100, 1) : 0);
// 兜底:防止 PHP notice
$diff_blocks = isset($diff_blocks) && is_array($diff_blocks) ? $diff_blocks : array();
?> ?>
<!-- 1. 顶部判题结果横幅 --> <!-- 1. 顶部判题结果横幅 -->
@@ -157,33 +266,58 @@ i.icon.spinning {
</div> </div>
<!-- 3. AI 点评区(直接显示按钮,提到原始错误上面 --> <!-- 3. AI 点评区(标题可点击折叠/展开 -->
<?php if (isset($OJ_LLM_ENABLED) && $OJ_LLM_ENABLED) { ?> <?php if (isset($OJ_LLM_ENABLED) && $OJ_LLM_ENABLED) { ?>
<div class="ui raised segment" id="llm-section"> <div class="ui raised segment" id="llm-section">
<h3 class="ui header"> <h3 class="ui header llm-section-header" id="llm-toggle" style="cursor: pointer; user-select: none;">
<i class="magic icon"></i> <i class="magic icon"></i>
<div class="content"> <div class="content">
AI 智能点评 AI 智能点评
<div class="sub header"><?php echo isset($MSG_AI_REVIEW_DESC) ? $MSG_AI_REVIEW_DESC : '基于你的代码和判题结果分析'; ?></div> <div class="sub header"><?php echo isset($MSG_AI_REVIEW_DESC) ? $MSG_AI_REVIEW_DESC : '基于你的代码和判题结果分析'; ?></div>
</div> </div>
<i class="dropdown icon llm-toggle-icon" style="margin-left: auto; font-size: 1em;"></i>
</h3> </h3>
<button class="ui labeled icon primary button" id="llm-review-btn" onclick="fetchLLMReview()"> <div id="llm-section-body">
<i class="magic icon"></i> 获取 AI 点评 <button class="ui labeled icon primary button" id="llm-review-btn" onclick="fetchLLMReview()">
</button> <i class="magic icon"></i> 获取 AI 点评
<div id="llm-review-loading" class="ui active inline text loader" style="display:none; margin-left: 1em;"> </button>
AI 正在分析你的代码,请稍候... <div id="llm-review-loading" class="ui active inline text loader" style="display:none; margin-left: 1em;">
</div> AI 正在分析你的代码,请稍候...
<div id="llm-review-result" style="display:none; margin-top: 15px;"> </div>
<div class="ui existing segment" id="llm-review-content"></div> <div id="llm-review-result" style="display:none; margin-top: 15px;">
</div> <div class="ui existing segment" id="llm-review-content"></div>
<div id="llm-review-error" style="display:none; margin-top: 15px;"> </div>
<div class="ui negative message"> <div id="llm-review-error" style="display:none; margin-top: 15px;">
<p id="llm-review-error-msg"></p> <div class="ui negative message">
<p id="llm-review-error-msg"></p>
</div>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
// 折叠/展开 AI 点评区
(function(){
var header = document.getElementById('llm-toggle');
var body = document.getElementById('llm-section-body');
var icon = header ? header.querySelector('.llm-toggle-icon') : null;
if (!header || !body) return;
header.addEventListener('click', function(e){
// 如果用户点击的是按钮本身,不要触发展开(按钮事件自己处理)
if (e.target.closest('button')) return;
var collapsed = body.style.display === 'none';
body.style.display = collapsed ? '' : 'none';
if (icon) {
icon.className = collapsed
? 'dropdown icon llm-toggle-icon'
: 'dropdown icon llm-toggle-icon active';
// active class 让 Semantic UI 的箭头自动旋转 90°但我们手写也行
icon.style.transform = collapsed ? 'rotate(0deg)' : 'rotate(-90deg)';
}
});
})();
function fetchLLMReview(){ function fetchLLMReview(){
var btn = $('#llm-review-btn'); var btn = $('#llm-review-btn');
btn.prop('disabled', true).addClass('loading'); btn.prop('disabled', true).addClass('loading');
@@ -291,6 +425,63 @@ i.icon.spinning {
</script> </script>
<?php } ?> <?php } ?>
<!-- 3.5. 对比详情WA/PE 时显示diff 视图) -->
<?php if (!empty($diff_blocks) && is_array($diff_blocks)): ?>
<div id="diff-section">
<h3 class="ui header" style="margin-top: 0;">
<i class="exchange icon"></i>
<div class="content">
输出对比
<div class="sub header">逐测试点对比期望输出和你的输出</div>
</div>
</h3>
<?php foreach ($diff_blocks as $idx => $block): ?>
<div class="ui segment diff-card">
<div class="diff-header">
<i class="file outline icon"></i>
<span class="diff-name"><?php echo htmlspecialchars($block['name'], ENT_QUOTES, 'UTF-8'); ?></span>
</div>
<div class="diff-table">
<div class="diff-side diff-expected">
<div class="diff-side-header">
<i class="check icon"></i> 期望输出
</div>
<pre class="diff-content"><?php
$max = max(count($block['expected']), count($block['yours']));
$lines_html = array();
for ($li = 0; $li < $max; $li++) {
$e = isset($block['expected'][$li]) ? $block['expected'][$li] : '';
$y = isset($block['yours'][$li]) ? $block['yours'][$li] : '';
$diff_class = ($e !== $y) ? ' diff-line-mismatch' : '';
$line_no = $li + 1;
$lines_html[] = '<div class="diff-line' . $diff_class . '" data-line="' . $line_no . '"><span class="diff-lineno">' . $line_no . '</span><span class="diff-text">' . htmlspecialchars($e, ENT_QUOTES, 'UTF-8') . '</span></div>';
}
echo implode('', $lines_html);
?></pre>
</div>
<div class="diff-side diff-yours">
<div class="diff-side-header">
<i class="bug icon"></i> 你的输出
</div>
<pre class="diff-content"><?php
$max = max(count($block['expected']), count($block['yours']));
$lines_html = array();
for ($li = 0; $li < $max; $li++) {
$e = isset($block['expected'][$li]) ? $block['expected'][$li] : '';
$y = isset($block['yours'][$li]) ? $block['yours'][$li] : '';
$diff_class = ($e !== $y) ? ' diff-line-mismatch' : '';
$line_no = $li + 1;
$lines_html[] = '<div class="diff-line' . $diff_class . '" data-line="' . $line_no . '"><span class="diff-lineno">' . $line_no . '</span><span class="diff-text">' . htmlspecialchars($y, ENT_QUOTES, 'UTF-8') . '</span></div>';
}
echo implode('', $lines_html);
?></pre>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<!-- 4. 原始错误折叠(默认收起) --> <!-- 4. 原始错误折叠(默认收起) -->
<div class="ui styled accordion" id="raw-info-accordion"> <div class="ui styled accordion" id="raw-info-accordion">
<div class="title"> <div class="title">
@@ -338,11 +529,7 @@ i.icon.spinning {
<a class="ui button" href="problem.php?id=<?php echo intval($row['problem_id']) ?>"> <a class="ui button" href="problem.php?id=<?php echo intval($row['problem_id']) ?>">
<i class="info circle icon"></i> <?php echo isset($MSG_VIEW_PROBLEM) ? $MSG_VIEW_PROBLEM : '查看题目'; ?> <i class="info circle icon"></i> <?php echo isset($MSG_VIEW_PROBLEM) ? $MSG_VIEW_PROBLEM : '查看题目'; ?>
</a> </a>
<?php if (isset($OJ_DOWNLOAD) && $OJ_DOWNLOAD && ($isRE || $row['result']==6)) { ?>
<a class="ui orange button" href="download.php?sid=<?php echo intval($id) ?>">
<i class="download icon"></i> <?php echo isset($MSG_DOWNLOAD_DATA) ? $MSG_DOWNLOAD_DATA : '下载测试数据'; ?>
</a>
<?php } ?>
</div> </div>
</div> </div>
@@ -463,6 +650,7 @@ i.icon.spinning {
var maxInterval = 8000; // 最长 8s var maxInterval = 8000; // 最长 8s
var countdown = 0; var countdown = 0;
var stopped = false; var stopped = false;
var completed = false;
function showStatus(text){ function showStatus(text){
var el = document.getElementById('auto-refresh-status'); var el = document.getElementById('auto-refresh-status');
@@ -501,6 +689,7 @@ i.icon.spinning {
// 判题完成,刷新页面拿到完整错误信息 // 判题完成,刷新页面拿到完整错误信息
showStatus('判题完成,正在刷新...'); showStatus('判题完成,正在刷新...');
setTimeout(function(){ location.reload(); }, 400); setTimeout(function(){ location.reload(); }, 400);
completed = true;
stopped = true; stopped = true;
return; return;
} }
@@ -525,7 +714,8 @@ i.icon.spinning {
if (document.hidden) { if (document.hidden) {
stopped = true; stopped = true;
hideStatus(); hideStatus();
} else if (!stopped) { } else if (!completed) {
stopped = false;
countdown = 0; countdown = 0;
tick(); tick();
} }

View File

@@ -215,6 +215,15 @@
border-top: 1px solid rgba(0,0,5,0.08); border-top: 1px solid rgba(0,0,5,0.08);
} }
.submit-top-actions {
justify-content: flex-end;
padding-top: 0;
padding-bottom: 16px;
margin-bottom: 16px;
border-top: 0;
border-bottom: 1px solid rgba(0,0,5,0.08);
}
.submit-actions .actions-left { .submit-actions .actions-left {
display: flex; display: flex;
gap: 8px; gap: 8px;
@@ -388,7 +397,15 @@
</div> </div>
<script src="<?php echo $OJ_CDN_URL?>include/checksource.js"></script> <script src="<?php echo $OJ_CDN_URL?>include/checksource.js"></script>
<form id="frmSolution" action="submit.php<?php if (isset($_GET['spa'])) echo "?spa" ?>" method="post" onsubmit="do_submit()" enctype="multipart/form-data"> <form id="frmSolution" action="submit.php<?php if (isset($_GET['spa'])) echo "?spa" ?>" method="post" onsubmit="do_submit();return false;" enctype="multipart/form-data">
<div class="submit-actions submit-top-actions">
<div class="actions-left">
<button id="Submit" type="button" class="btn btn-primary" onclick="do_submit();">
<i class="paper plane icon"></i> <?php echo $MSG_SUBMIT?>
</button>
</div>
</div>
<div class="submit-toolbar"> <div class="submit-toolbar">
<?php if (!isset($_GET['spa']) || $solution_name ) {?> <?php if (!isset($_GET['spa']) || $solution_name ) {?>
@@ -485,11 +502,9 @@
<h2 class="solution-name-title">📎 <?php echo $solution_name?></h2> <h2 class="solution-name-title">📎 <?php echo $solution_name?></h2>
<?php } ?> <?php } ?>
<?php if ((isset($OJ_TEST_RUN) && $OJ_TEST_RUN && $spj<=1 && !$solution_name) || (isset($OJ_ENCODE_SUBMIT)&&$OJ_ENCODE_SUBMIT) || (isset($OJ_BLOCKLY)&&$OJ_BLOCKLY)){?>
<div class="submit-actions"> <div class="submit-actions">
<div class="actions-left"> <div class="actions-left">
<button id="Submit" type="button" class="btn btn-primary" onclick="do_submit();">
<i class="paper plane icon"></i> <?php echo $MSG_SUBMIT?>
</button>
<?php if ( isset($OJ_TEST_RUN) && $OJ_TEST_RUN && $spj<=1 && !$solution_name ){?> <?php if ( isset($OJ_TEST_RUN) && $OJ_TEST_RUN && $spj<=1 && !$solution_name ){?>
<button id="TestRun" type="button" class="btn btn-info" onclick="do_test_run();"> <button id="TestRun" type="button" class="btn btn-info" onclick="do_test_run();">
<i class="play icon"></i> <?php echo $MSG_TR?> <i class="play icon"></i> <?php echo $MSG_TR?>
@@ -508,13 +523,14 @@
<input id="blockly_loader" type="button" class="btn btn-purple" onclick="openBlockly()" value="🧩 <?php echo $MSG_BLOCKLY_OPEN?>"> <input id="blockly_loader" type="button" class="btn btn-purple" onclick="openBlockly()" value="🧩 <?php echo $MSG_BLOCKLY_OPEN?>">
<input id="transrun" type="button" class="btn btn-green" onclick="loadFromBlockly()" value="▶ <?php echo $MSG_BLOCKLY_TEST?>" style="display:none;"> <input id="transrun" type="button" class="btn btn-green" onclick="loadFromBlockly()" value="▶ <?php echo $MSG_BLOCKLY_TEST?>" style="display:none;">
</div> </div>
<?php }?> <?php }?>
</div> </div>
<?php }?>
<div id="blockly" class="center"></div> <div id="blockly" class="center"></div>
<?php if (isset($id)){?> <?php if (isset($id)){?>
<input id="problem_id" type="hidden" value="<?php echo $id?>" name="problem_id"> <input id="problem_id" type="hidden" value="<?php echo $id?>" name="id">
<?php }else{?> <?php }else{?>
<input id="cid" type="hidden" value="<?php echo $cid?>" name="cid"> <input id="cid" type="hidden" value="<?php echo $cid?>" name="cid">
<input id="pid" type="hidden" value="<?php echo $pid?>" name="pid"> <input id="pid" type="hidden" value="<?php echo $pid?>" name="pid">
@@ -618,6 +634,69 @@ return ret+"";
} }
var count=0; var count=0;
function htmlEscape(text){
return $("<div>").text(text).html();
}
function cleanSubmitMessage(data){
var text=$("<div>").html(String(data)).text();
text=$.trim(text.replace(/\s+/g," "));
if(text=="") text="Submit failed.";
return text;
}
function showSubmitMessage(data){
var tb=window.document.getElementById('result');
var text=cleanSubmitMessage(data);
if(tb!=null){
tb.innerHTML="<span class='badge badge-danger'>"+htmlEscape(text)+"</span>";
}else{
alert(text);
}
if($("#vcode-img")!=null) $("#vcode-img").click();
}
function setSubmitDisabled(disabled){
$("#Submit").prop('disabled', disabled);
$("#TestRun").prop('disabled', disabled);
}
function startSubmitCooldown(){
count=<?php echo isset($OJ_SUBMIT_COOLDOWN_TIME)?$OJ_SUBMIT_COOLDOWN_TIME:5 ?> * 2 ;
handler_interval= window.setTimeout("resume();",1000);
}
function submitSolutionAjax(isTestRun){
var form=document.getElementById("frmSolution");
var formData=new FormData(form);
$.ajax({
url:"submit.php?ajax<?php if(isset($_GET['spa'])) echo '&spa'; ?>",
type:"POST",
data:formData,
processData:false,
contentType:false,
success:function(data){
var text=$.trim(String(data));
var sid=/^\d+$/.test(text)?parseInt(text,10):0;
if(sid>0){
if(isTestRun){
fresh_result(sid);
startSubmitCooldown();
}else{
window.location.href="reinfo.php?sid="+sid;
}
}else{
showSubmitMessage(text);
setSubmitDisabled(false);
}
},
error:function(xhr){
showSubmitMessage(xhr.responseText || "Submit failed.");
setSubmitDisabled(false);
}
});
}
function encoded_submit(){ function encoded_submit(){
var mark="<?php echo isset($id)?'problem_id':'cid';?>"; var mark="<?php echo isset($id)?'problem_id':'cid';?>";
@@ -643,7 +722,7 @@ function encoded_submit(){
} }
function do_submit(){ function do_submit(){
$("#Submit").attr("disabled","true"); setSubmitDisabled(true);
if(using_blockly) if(using_blockly)
translate(); translate();
if(typeof(editor) != "undefined"){ if(typeof(editor) != "undefined"){
@@ -656,37 +735,8 @@ function do_submit(){
else else
problem_id.value='<?php if (isset($cid))echo $cid?>'; problem_id.value='<?php if (isset($cid))echo $cid?>';
document.getElementById("frmSolution").target="_self"; document.getElementById("frmSolution").target="_self";
submitSolutionAjax(false);
<?php if(isset($_GET['spa'])){?> <?php if(isset($OJ_REMOTE_JUDGE)&&$OJ_REMOTE_JUDGE) {?>$("#sk").attr("src","remote.php"); <?php } ?>
<?php if($solution_name) { ?>document.getElementById("frmSolution").submit(); <?php } ?>
$.post("submit.php?ajax",$("#frmSolution").serialize(),function(data){
var sid=parseInt(data);
if(sid>0){
window.location.href="reinfo.php?sid="+sid;
}else{
fresh_result(data);
}
});
$("#Submit").prop('disabled', true);
$("#TestRun").prop('disabled', true);
count=<?php echo $OJ_SUBMIT_COOLDOWN_TIME?> * 2 ;
handler_interval= window.setTimeout("resume();",1000);
<?php if(isset($OJ_REMOTE_JUDGE)&&$OJ_REMOTE_JUDGE) {?>$("#sk").attr("src","remote.php"); <?php } ?>
<?php }else{?>
$.post("submit.php?ajax",$("#frmSolution").serialize(),function(data){
var sid=parseInt(data);
if(sid>0){
window.location.href="reinfo.php?sid="+sid;
}else{
fresh_result(data);
}
});
$("#Submit").prop('disabled', true);
$("#TestRun").prop('disabled', true);
count=<?php echo $OJ_SUBMIT_COOLDOWN_TIME?> * 2 ;
handler_interval= window.setTimeout("resume();",1000);
<?php }?>
} }
var handler_interval; var handler_interval;
function do_test_run(){ function do_test_run(){
@@ -705,12 +755,9 @@ function do_test_run(){
var problem_id=document.getElementById(mark); var problem_id=document.getElementById(mark);
problem_id.value=-problem_id.value; problem_id.value=-problem_id.value;
document.getElementById("frmSolution").target="testRun"; document.getElementById("frmSolution").target="testRun";
$.post("submit.php?ajax",$("#frmSolution").serialize(),function(data){fresh_result(data);}); setSubmitDisabled(true);
$("#Submit").prop('disabled', true); submitSolutionAjax(true);
$("#TestRun").prop('disabled', true);
problem_id.value=-problem_id.value; problem_id.value=-problem_id.value;
count=<?php echo isset($OJ_SUBMIT_COOLDOWN_TIME)?$OJ_SUBMIT_COOLDOWN_TIME:5 ?> * 2 ;
handler_interval= window.setTimeout("resume();",1000);
} }
function resume(){ function resume(){
count--; count--;