简化结果上传查询的方法

现在,在后端使用csv/xlsx上传结果文件即可
优化前端信息显示
This commit is contained in:
2026-03-07 00:37:13 +08:00
parent 11ac498de3
commit 965c62fb20
4 changed files with 946 additions and 68 deletions
+36 -29
View File
@@ -223,7 +223,7 @@ MIT License - 详见 LICENSE 文件
### 1. 需要安装的插件 ### 1. 需要安装的插件
1. **Formidable Forms** 1. **Formidable Forms**
用于报名表单、进度查询表单、公示视图(可选使用 Formidable Views)。 用于报名表单渲染(前台报名)。
2. **WP Mail SMTP** 2. **WP Mail SMTP**
用于 Formidable 提交邮件发送(站点邮件走 SMTP)。 用于 Formidable 提交邮件发送(站点邮件走 SMTP)。
@@ -234,30 +234,44 @@ MIT License - 详见 LICENSE 文件
可配置字段: 可配置字段:
- 报名开始时间(datetime - 报名开始时间(datetime
- 报名结束时间(datetime - 报名结束时间(datetime
- 第一次面试日期(date - 第一次面试开始/结束时间(datetime
-次面试日期(date -次面试地点(中/英
- 录取公示开始日期(date,系统自动延续 7 天 - 第二次面试开始/结束时间(datetime
- 第二次面试地点(中/英)
- 国庆能力摸底开始/结束日期(调试)
- 录取结果公布开始日期(date,系统自动延续 7 天)
- 报名表单 Shortcode - 报名表单 Shortcode
- 结果查询 Shortcode
- 公示视图 Shortcode
说明: 说明:
- 「国庆能力摸底阶段」固定为每年 **10/01 - 10/07** - 「国庆能力摸底阶段」固定为每年 **10/01 - 10/07**
- 年份优先取报名开始时间年份;若未设置,则取公示开始年份;仍未设置则取当前年份 - 若填写“国庆能力摸底开始/结束日期(调试)”,将优先使用调试时间
### 3. 前台显示逻辑 ### 3. 阶段结果文件配置(重点)
在同一页面中,为以下阶段上传结果文件(CSV 或 XLSX):
- 第一次面试结果文件
- 国庆能力摸底结果文件
- 第二次面试结果文件
- 录取结果文件
文件列顺序必须为:
`姓名,QQ,邮箱,学号,手机,是否通过`
其中:
- `是否通过 = 1` 表示通过
- 其他任意值(含 `0`、空)都按未通过处理
注意:
- 不需要配置任何“字段映射”或“阶段识别字段”。
- 系统会自动按上述固定列进行识别与查询。
### 4. 前台显示逻辑
- 报名表单:仅在报名阶段显示。 - 报名表单:仅在报名阶段显示。
- 结果查询表单:从报名开始到公示结束前可用 - 录取进度查询:报名阶段不显示;报名结束后按当前阶段显示对应结果(使用姓名/QQ/邮箱/学号查询)
- 公示视图:仅在公示 7 天窗口内显示。
- Canvas 进度条:根据当前时间自动高亮阶段(已完成/进行中/未开始)。 - Canvas 进度条:根据当前时间自动高亮阶段(已完成/进行中/未开始)。
### 4. Formidable 推荐配置方式
1. 新建报名表单,复制 shortcode,填入「报名表单 Shortcode」。
2. 新建结果查询表单(例如:学号/邮箱/手机号 + 状态查询逻辑),填入「结果查询 Shortcode」。
3. 如需公示名单展示,创建 Formidable View,复制 shortcode,填入「公示视图 Shortcode」。
### 5. WP Mail SMTP 配置建议 ### 5. WP Mail SMTP 配置建议
1. 安装并启用 **WP Mail SMTP** 1. 安装并启用 **WP Mail SMTP**
@@ -265,17 +279,10 @@ MIT License - 详见 LICENSE 文件
3. 设置发件人邮箱与发件人名称。 3. 设置发件人邮箱与发件人名称。
4. 使用插件自带发送测试邮件,确认可达后再开放报名。 4. 使用插件自带发送测试邮件,确认可达后再开放报名。
## Join Page Quick Guide (ASCII) ## 加入我们页面快速说明
- Admin path: Settings -> Recruitment Settings. - 后台路径:`设置 -> 招新设置`
- Configure timeline nodes: registration start/end, interview I date, interview II date, notice start date. - 先配置时间节点:报名开始/结束、一面开始/结束、二面开始/结束、录取结果公布开始日期。
- Stage "National Day Assessment" is fixed to Oct 1 - Oct 7 (auto year). - “国庆能力摸底”默认是每年 `10/01 - 10/07`(可用调试日期覆盖)。
- Set Formidable shortcodes: - 报名表单短代码使用 Formidable 表单,邮件发送建议配合 WP Mail SMTP。
- registration form shortcode - 阶段结果统一使用 CSV/XLSX 上传,不再使用 Formidable Entries 做结果查询。
- progress lookup shortcode
- public notice view shortcode
- Frontend visibility rules:
- registration form: only in registration window
- lookup form: from registration start until notice window ends
- public notice view: only during 7-day notice window
- Mail delivery: configure WP Mail SMTP plugin for Formidable emails.
+30
View File
@@ -501,6 +501,31 @@
color: var(--color-primary); color: var(--color-primary);
} }
.join-stage-item .join-stage-status.is-waiting-notice {
color: #e5484d;
}
.join-stage-item.is-waiting-notice {
border-color: color-mix(in srgb, #e5484d 62%, var(--border-default) 38%);
background: linear-gradient(
180deg,
color-mix(in srgb, #e5484d 7%, var(--bg-card) 93%) 0%,
color-mix(in srgb, var(--bg-card) 97%, transparent) 100%
);
box-shadow:
0 0 0 1px color-mix(in srgb, #e5484d 24%, transparent),
0 10px 20px -18px color-mix(in srgb, #e5484d 48%, transparent);
}
.join-stage-item.is-waiting-notice::before {
background: color-mix(in srgb, #e5484d 70%, transparent);
opacity: 1;
}
.join-stage-item.is-waiting-notice .join-stage-status {
color: #e5484d;
}
.join-stage-item.is-completed { .join-stage-item.is-completed {
border-color: color-mix(in srgb, #0f8a4f 48%, var(--border-default) 52%); border-color: color-mix(in srgb, #0f8a4f 48%, var(--border-default) 52%);
background: linear-gradient( background: linear-gradient(
@@ -518,6 +543,11 @@
color: var(--color-primary); color: var(--color-primary);
} }
.join-stage-item.is-waiting-notice.is-active .join-stage-status,
.join-stage-item.is-waiting-notice.is-current .join-stage-status {
color: #e5484d;
}
.join-stage-item.is-upcoming .join-stage-status, .join-stage-item.is-upcoming .join-stage-status,
.join-stage-item.is-pending .join-stage-status { .join-stage-item.is-pending .join-stage-status {
color: var(--text-secondary); color: var(--text-secondary);
+856 -28
View File
@@ -1401,6 +1401,21 @@ function itstudio_join_get_default_settings() {
'assessment_start_date' => '', 'assessment_start_date' => '',
'assessment_end_date' => '', 'assessment_end_date' => '',
'notice_start_date' => '', 'notice_start_date' => '',
'result_data_source' => 'file',
'result_formidable_form_id' => '',
'result_formidable_name_field' => '',
'result_formidable_qq_field' => '',
'result_formidable_email_field' => '',
'result_formidable_student_id_field' => '',
'result_formidable_registration_field' => '',
'result_formidable_first_interview_field' => '',
'result_formidable_assessment_field' => '',
'result_formidable_second_interview_field' => '',
'result_formidable_admission_field' => '',
'result_first_interview_file' => 0,
'result_assessment_file' => 0,
'result_second_interview_file' => 0,
'result_admission_file' => 0,
'result_registration_records' => '', 'result_registration_records' => '',
'result_first_interview_records' => '', 'result_first_interview_records' => '',
'result_assessment_records' => '', 'result_assessment_records' => '',
@@ -1507,6 +1522,22 @@ function itstudio_join_sanitize_settings($input) {
'assessment_start_date' => itstudio_join_sanitize_date_value($input['assessment_start_date'] ?? ''), 'assessment_start_date' => itstudio_join_sanitize_date_value($input['assessment_start_date'] ?? ''),
'assessment_end_date' => itstudio_join_sanitize_date_value($input['assessment_end_date'] ?? ''), 'assessment_end_date' => itstudio_join_sanitize_date_value($input['assessment_end_date'] ?? ''),
'notice_start_date' => itstudio_join_sanitize_date_value($input['notice_start_date'] ?? ''), 'notice_start_date' => itstudio_join_sanitize_date_value($input['notice_start_date'] ?? ''),
// 固定使用文件结果模式,避免后台再配置字段映射。
'result_data_source' => 'file',
'result_formidable_form_id' => itstudio_join_sanitize_shortcode_value($input['result_formidable_form_id'] ?? ''),
'result_formidable_name_field' => itstudio_join_sanitize_shortcode_value($input['result_formidable_name_field'] ?? ''),
'result_formidable_qq_field' => itstudio_join_sanitize_shortcode_value($input['result_formidable_qq_field'] ?? ''),
'result_formidable_email_field' => itstudio_join_sanitize_shortcode_value($input['result_formidable_email_field'] ?? ''),
'result_formidable_student_id_field' => itstudio_join_sanitize_shortcode_value($input['result_formidable_student_id_field'] ?? ''),
'result_formidable_registration_field' => itstudio_join_sanitize_shortcode_value($input['result_formidable_registration_field'] ?? ''),
'result_formidable_first_interview_field' => itstudio_join_sanitize_shortcode_value($input['result_formidable_first_interview_field'] ?? ''),
'result_formidable_assessment_field' => itstudio_join_sanitize_shortcode_value($input['result_formidable_assessment_field'] ?? ''),
'result_formidable_second_interview_field' => itstudio_join_sanitize_shortcode_value($input['result_formidable_second_interview_field'] ?? ''),
'result_formidable_admission_field' => itstudio_join_sanitize_shortcode_value($input['result_formidable_admission_field'] ?? ''),
'result_first_interview_file' => isset($input['result_first_interview_file']) ? absint($input['result_first_interview_file']) : 0,
'result_assessment_file' => isset($input['result_assessment_file']) ? absint($input['result_assessment_file']) : 0,
'result_second_interview_file' => isset($input['result_second_interview_file']) ? absint($input['result_second_interview_file']) : 0,
'result_admission_file' => isset($input['result_admission_file']) ? absint($input['result_admission_file']) : 0,
'result_registration_records' => itstudio_join_sanitize_records_value($input['result_registration_records'] ?? ''), 'result_registration_records' => itstudio_join_sanitize_records_value($input['result_registration_records'] ?? ''),
'result_first_interview_records' => itstudio_join_sanitize_records_value($input['result_first_interview_records'] ?? ''), 'result_first_interview_records' => itstudio_join_sanitize_records_value($input['result_first_interview_records'] ?? ''),
'result_assessment_records' => itstudio_join_sanitize_records_value($input['result_assessment_records'] ?? ''), 'result_assessment_records' => itstudio_join_sanitize_records_value($input['result_assessment_records'] ?? ''),
@@ -1606,8 +1637,626 @@ function itstudio_join_get_result_field_map() {
); );
} }
function itstudio_join_get_result_file_field_map() {
return array(
'first_interview' => 'result_first_interview_file',
'assessment' => 'result_assessment_file',
'second_interview' => 'result_second_interview_file',
'public_notice' => 'result_admission_file',
);
}
function itstudio_join_is_file_result_mode($settings) {
$settings = is_array($settings) ? $settings : array();
return (($settings['result_data_source'] ?? 'manual') === 'file');
}
function itstudio_join_get_result_file_attachment_id($settings, $stage_key) {
$settings = is_array($settings) ? $settings : array();
$map = itstudio_join_get_result_file_field_map();
$setting_key = isset($map[$stage_key]) ? $map[$stage_key] : '';
if ($setting_key === '') {
return 0;
}
return absint((string) ($settings[$setting_key] ?? ''));
}
function itstudio_join_csv_delimiter_for_line($line) {
$line = (string) $line;
$candidates = array(',', ';', "\t", '|');
$best = ',';
$best_count = -1;
foreach ($candidates as $delimiter) {
$count = substr_count($line, $delimiter);
if ($count > $best_count) {
$best_count = $count;
$best = $delimiter;
}
}
return $best;
}
function itstudio_join_read_csv_rows($path) {
if (!is_string($path) || $path === '' || !is_readable($path)) {
return array();
}
$rows = array();
$handle = fopen($path, 'rb');
if ($handle === false) {
return array();
}
$delimiter = ',';
$first_line = fgets($handle);
if ($first_line !== false) {
$first_line = preg_replace('/^\xEF\xBB\xBF/', '', (string) $first_line);
$delimiter = itstudio_join_csv_delimiter_for_line($first_line);
rewind($handle);
}
while (($data = fgetcsv($handle, 0, $delimiter)) !== false) {
if (!is_array($data)) {
continue;
}
$row = array();
foreach ($data as $cell) {
$row[] = trim((string) $cell);
}
if (!empty(array_filter($row, static function ($value) {
return $value !== '';
}))) {
$rows[] = $row;
}
}
fclose($handle);
return $rows;
}
function itstudio_join_xlsx_column_to_index($column_ref) {
$column_ref = strtoupper((string) $column_ref);
$length = strlen($column_ref);
$index = 0;
for ($i = 0; $i < $length; $i++) {
$ch = ord($column_ref[$i]);
if ($ch < 65 || $ch > 90) {
continue;
}
$index = ($index * 26) + ($ch - 64);
}
return max(0, $index - 1);
}
function itstudio_join_xlsx_shared_strings($zip) {
$shared = array();
$xml = $zip->getFromName('xl/sharedStrings.xml');
if (!is_string($xml) || trim($xml) === '') {
return $shared;
}
$sx = simplexml_load_string($xml);
if (!($sx instanceof SimpleXMLElement)) {
return $shared;
}
foreach ($sx->si as $si) {
if (isset($si->t)) {
$shared[] = (string) $si->t;
continue;
}
$chunks = array();
if (isset($si->r)) {
foreach ($si->r as $run) {
$chunks[] = (string) $run->t;
}
}
$shared[] = implode('', $chunks);
}
return $shared;
}
function itstudio_join_read_xlsx_rows($path) {
if (!class_exists('ZipArchive') || !class_exists('SimpleXMLElement')) {
return array();
}
if (!is_string($path) || $path === '' || !is_readable($path)) {
return array();
}
$zip = new ZipArchive();
if ($zip->open($path) !== true) {
return array();
}
$sheetXml = $zip->getFromName('xl/worksheets/sheet1.xml');
if (!is_string($sheetXml) || trim($sheetXml) === '') {
// fallback: first worksheet found
for ($i = 0; $i < $zip->numFiles; $i++) {
$name = $zip->getNameIndex($i);
if (is_string($name) && preg_match('#^xl/worksheets/sheet\d+\.xml$#', $name)) {
$sheetXml = $zip->getFromName($name);
break;
}
}
}
if (!is_string($sheetXml) || trim($sheetXml) === '') {
$zip->close();
return array();
}
$sharedStrings = itstudio_join_xlsx_shared_strings($zip);
$zip->close();
$sheet = simplexml_load_string($sheetXml);
if (!($sheet instanceof SimpleXMLElement) || !isset($sheet->sheetData)) {
return array();
}
$rows = array();
foreach ($sheet->sheetData->row as $rowNode) {
$row = array();
foreach ($rowNode->c as $cell) {
$ref = (string) ($cell['r'] ?? '');
$type = (string) ($cell['t'] ?? '');
preg_match('/^[A-Z]+/i', $ref, $matches);
$colRef = isset($matches[0]) ? $matches[0] : '';
$colIndex = itstudio_join_xlsx_column_to_index($colRef);
$value = '';
if ($type === 's') {
$sharedIndex = isset($cell->v) ? (int) $cell->v : -1;
$value = ($sharedIndex >= 0 && isset($sharedStrings[$sharedIndex])) ? (string) $sharedStrings[$sharedIndex] : '';
} elseif ($type === 'inlineStr' && isset($cell->is->t)) {
$value = (string) $cell->is->t;
} else {
$value = isset($cell->v) ? (string) $cell->v : '';
}
$row[$colIndex] = trim($value);
}
if (!empty($row)) {
ksort($row);
$normalized = array_values($row);
if (!empty(array_filter($normalized, static function ($value) {
return $value !== '';
}))) {
$rows[] = $normalized;
}
}
}
return $rows;
}
function itstudio_join_read_result_rows_from_attachment($attachment_id) {
static $cache = array();
$attachment_id = absint($attachment_id);
if ($attachment_id <= 0) {
return array();
}
if (isset($cache[$attachment_id])) {
return $cache[$attachment_id];
}
$path = get_attached_file($attachment_id);
if (!is_string($path) || $path === '' || !is_readable($path)) {
$cache[$attachment_id] = array();
return array();
}
$ext = strtolower((string) pathinfo($path, PATHINFO_EXTENSION));
$rows = array();
if ($ext === 'csv') {
$rows = itstudio_join_read_csv_rows($path);
} elseif ($ext === 'xlsx') {
$rows = itstudio_join_read_xlsx_rows($path);
} else {
// try csv as fallback
$rows = itstudio_join_read_csv_rows($path);
}
$cache[$attachment_id] = is_array($rows) ? $rows : array();
return $cache[$attachment_id];
}
function itstudio_join_parse_result_rows_to_records($rows) {
$rows = is_array($rows) ? $rows : array();
$records = array();
$is_header_row = static function ($name, $qq, $email, $student_id, $phone, $passed_raw) {
$joined = implode(',', array($name, $qq, $email, $student_id, $phone, $passed_raw));
$joined = preg_replace('/^\xEF\xBB\xBF/', '', (string) $joined);
$joined = preg_replace('/\s+/u', '', (string) $joined);
$joined_lower = function_exists('mb_strtolower') ? mb_strtolower($joined, 'UTF-8') : strtolower($joined);
$is_cn_header = (
strpos($joined_lower, '姓名') !== false
|| strpos($joined_lower, 'qq') !== false
|| strpos($joined_lower, '邮箱') !== false
|| strpos($joined_lower, '学号') !== false
|| strpos($joined_lower, '手机') !== false
|| strpos($joined_lower, '是否通过') !== false
);
$is_en_header = (
strpos($joined_lower, 'name') !== false
|| strpos($joined_lower, 'qq') !== false
|| strpos($joined_lower, 'email') !== false
|| strpos($joined_lower, 'student') !== false
|| strpos($joined_lower, 'phone') !== false
|| strpos($joined_lower, 'pass') !== false
);
return $is_cn_header || $is_en_header;
};
foreach ($rows as $idx => $row) {
if (!is_array($row)) {
continue;
}
$name = trim((string) ($row[0] ?? ''));
$qq = trim((string) ($row[1] ?? ''));
$email = trim((string) ($row[2] ?? ''));
$student_id = trim((string) ($row[3] ?? ''));
$phone = trim((string) ($row[4] ?? ''));
$passed_raw = trim((string) ($row[5] ?? ''));
// 过滤标题行(支持中英文标题)
if ($idx === 0 && $is_header_row($name, $qq, $email, $student_id, $phone, $passed_raw)) {
continue;
}
// 容错:有些表格会在中间重复表头
if ($is_header_row($name, $qq, $email, $student_id, $phone, $passed_raw)) {
continue;
}
$record = array(
'name' => itstudio_join_normalize_lookup_value('name', $name),
'qq' => itstudio_join_normalize_lookup_value('qq', $qq),
'email' => itstudio_join_normalize_lookup_value('email', $email),
'student_id' => itstudio_join_normalize_lookup_value('student_id', $student_id),
'phone' => preg_replace('/\D+/', '', $phone),
'passed' => ($passed_raw === '1'),
);
if ($record['name'] === '' && $record['qq'] === '' && $record['email'] === '' && $record['student_id'] === '' && $record['phone'] === '') {
continue;
}
$records[] = $record;
}
return $records;
}
function itstudio_join_is_formidable_result_mode($settings) {
$settings = is_array($settings) ? $settings : array();
return (($settings['result_data_source'] ?? 'manual') === 'formidable');
}
function itstudio_join_get_formidable_form_id($settings) {
$settings = is_array($settings) ? $settings : array();
return absint((string) ($settings['result_formidable_form_id'] ?? ''));
}
function itstudio_join_get_formidable_identity_field_refs($settings) {
$settings = is_array($settings) ? $settings : array();
return array(
'name' => trim((string) ($settings['result_formidable_name_field'] ?? '')),
'qq' => trim((string) ($settings['result_formidable_qq_field'] ?? '')),
'email' => trim((string) ($settings['result_formidable_email_field'] ?? '')),
'student_id' => trim((string) ($settings['result_formidable_student_id_field'] ?? '')),
);
}
function itstudio_join_get_formidable_stage_field_ref($settings, $stage_key) {
$settings = is_array($settings) ? $settings : array();
$map = array(
'registration' => 'result_formidable_registration_field',
'first_interview' => 'result_formidable_first_interview_field',
'assessment' => 'result_formidable_assessment_field',
'second_interview' => 'result_formidable_second_interview_field',
'public_notice' => 'result_formidable_admission_field',
);
$setting_key = isset($map[$stage_key]) ? $map[$stage_key] : '';
if ($setting_key === '') {
return '';
}
return trim((string) ($settings[$setting_key] ?? ''));
}
function itstudio_join_resolve_formidable_field_id($form_id, $field_ref) {
static $cache = array();
$form_id = absint($form_id);
$field_ref = trim((string) $field_ref);
if ($field_ref === '') {
return 0;
}
$cache_key = $form_id . '|' . $field_ref;
if (isset($cache[$cache_key])) {
return (int) $cache[$cache_key];
}
if (ctype_digit($field_ref)) {
$cache[$cache_key] = (int) $field_ref;
return (int) $cache[$cache_key];
}
$field_id = 0;
if (class_exists('FrmField') && method_exists('FrmField', 'get_id_by_key')) {
$field_id = (int) FrmField::get_id_by_key($field_ref);
}
if ($field_id <= 0) {
global $wpdb;
$table = $wpdb->prefix . 'frm_fields';
if ($form_id > 0) {
$field_id = (int) $wpdb->get_var($wpdb->prepare("SELECT id FROM {$table} WHERE field_key = %s AND form_id = %d LIMIT 1", $field_ref, $form_id));
} else {
$field_id = (int) $wpdb->get_var($wpdb->prepare("SELECT id FROM {$table} WHERE field_key = %s LIMIT 1", $field_ref));
}
}
$cache[$cache_key] = $field_id > 0 ? $field_id : 0;
return (int) $cache[$cache_key];
}
function itstudio_join_get_formidable_entry_meta_value($entry_id, $field_id) {
static $cache = array();
$entry_id = absint($entry_id);
$field_id = absint($field_id);
if ($entry_id <= 0 || $field_id <= 0) {
return '';
}
$cache_key = $entry_id . '|' . $field_id;
if (array_key_exists($cache_key, $cache)) {
return $cache[$cache_key];
}
$value = '';
if (class_exists('FrmEntryMeta') && method_exists('FrmEntryMeta', 'get_entry_meta_by_field')) {
$value = FrmEntryMeta::get_entry_meta_by_field($entry_id, $field_id, true);
} else {
global $wpdb;
$table = $wpdb->prefix . 'frm_item_metas';
$value = $wpdb->get_var($wpdb->prepare("SELECT meta_value FROM {$table} WHERE item_id = %d AND field_id = %d LIMIT 1", $entry_id, $field_id));
}
$cache[$cache_key] = $value;
return $value;
}
function itstudio_join_is_truthy_result_value($value) {
if (is_array($value)) {
foreach ($value as $item) {
if (itstudio_join_is_truthy_result_value($item)) {
return true;
}
}
return false;
}
if (is_bool($value)) {
return $value;
}
if (is_numeric($value)) {
return ((float) $value) > 0;
}
$value = trim((string) $value);
if ($value === '') {
return false;
}
if (is_serialized($value)) {
$decoded = maybe_unserialize($value);
if ($decoded !== $value) {
return itstudio_join_is_truthy_result_value($decoded);
}
}
$normalized = function_exists('mb_strtolower') ? mb_strtolower($value, 'UTF-8') : strtolower($value);
return in_array($normalized, array(
'1', 'true', 'yes', 'on', 'y',
'pass', 'passed', 'admit', 'admitted',
'是', '通过', '已通过', '录取', '已录取', '完成', '成功',
), true);
}
function itstudio_join_find_formidable_entry_id_by_query($settings, $query) {
$settings = is_array($settings) ? $settings : array();
$query = is_array($query) ? $query : array();
if (!class_exists('FrmEntry') || !class_exists('FrmEntryMeta')) {
return 0;
}
$form_id = itstudio_join_get_formidable_form_id($settings);
if ($form_id <= 0) {
return 0;
}
$identity_refs = itstudio_join_get_formidable_identity_field_refs($settings);
$active_filters = array();
foreach (array('name', 'qq', 'email', 'student_id') as $field) {
$query_value = trim((string) ($query[$field] ?? ''));
if ($query_value === '') {
continue;
}
$field_ref = trim((string) ($identity_refs[$field] ?? ''));
if ($field_ref === '') {
continue;
}
$field_id = itstudio_join_resolve_formidable_field_id($form_id, $field_ref);
if ($field_id <= 0) {
continue;
}
$active_filters[$field] = $field_id;
}
if (empty($active_filters)) {
return 0;
}
$entries = FrmEntry::getAll(
array(
'it.form_id' => $form_id,
'is_draft' => 0,
),
' ORDER BY it.id DESC'
);
if (empty($entries)) {
return 0;
}
foreach ($entries as $entry) {
$entry_id = absint(is_object($entry) ? ($entry->id ?? 0) : (is_array($entry) ? ($entry['id'] ?? 0) : 0));
if ($entry_id <= 0) {
continue;
}
$matched = true;
foreach ($active_filters as $field => $field_id) {
$raw_value = itstudio_join_get_formidable_entry_meta_value($entry_id, $field_id);
$normalized_entry_value = itstudio_join_normalize_lookup_value($field, (string) $raw_value);
$normalized_query_value = itstudio_join_normalize_lookup_value($field, (string) ($query[$field] ?? ''));
if ($normalized_query_value === '' || $normalized_entry_value === '' || $normalized_entry_value !== $normalized_query_value) {
$matched = false;
break;
}
}
if ($matched) {
return $entry_id;
}
}
return 0;
}
function itstudio_join_formidable_entry_matches_stage($settings, $entry_id, $stage_key) {
$settings = is_array($settings) ? $settings : array();
$entry_id = absint($entry_id);
$stage_key = (string) $stage_key;
if ($entry_id <= 0) {
return false;
}
$form_id = itstudio_join_get_formidable_form_id($settings);
if ($form_id <= 0) {
return false;
}
$field_ref = itstudio_join_get_formidable_stage_field_ref($settings, $stage_key);
if ($field_ref === '') {
return $stage_key === 'registration';
}
$field_id = itstudio_join_resolve_formidable_field_id($form_id, $field_ref);
if ($field_id <= 0) {
return false;
}
$raw_value = itstudio_join_get_formidable_entry_meta_value($entry_id, $field_id);
return itstudio_join_is_truthy_result_value($raw_value);
}
function itstudio_join_formidable_stage_has_data($settings, $stage_key) {
$settings = is_array($settings) ? $settings : array();
if (!class_exists('FrmEntry') || !class_exists('FrmEntryMeta')) {
return false;
}
$form_id = itstudio_join_get_formidable_form_id($settings);
if ($form_id <= 0) {
return false;
}
global $wpdb;
$items_table = $wpdb->prefix . 'frm_items';
$metas_table = $wpdb->prefix . 'frm_item_metas';
$entry_count = (int) $wpdb->get_var($wpdb->prepare("SELECT COUNT(1) FROM {$items_table} WHERE form_id = %d AND is_draft = 0", $form_id));
if ($entry_count <= 0) {
return false;
}
if ($stage_key === 'registration') {
return true;
}
$field_ref = itstudio_join_get_formidable_stage_field_ref($settings, $stage_key);
$field_id = itstudio_join_resolve_formidable_field_id($form_id, $field_ref);
if ($field_id <= 0) {
return false;
}
$hit = (int) $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(1)
FROM {$metas_table} AS m
INNER JOIN {$items_table} AS i ON i.id = m.item_id
WHERE i.form_id = %d
AND i.is_draft = 0
AND m.field_id = %d
AND COALESCE(m.meta_value, '') <> ''",
$form_id,
$field_id
));
return $hit > 0 || $entry_count > 0;
}
function itstudio_join_formidable_has_queryable_identity_field($settings, $query) {
$settings = is_array($settings) ? $settings : array();
$query = is_array($query) ? $query : array();
$form_id = itstudio_join_get_formidable_form_id($settings);
if ($form_id <= 0) {
return false;
}
$identity_refs = itstudio_join_get_formidable_identity_field_refs($settings);
foreach (array('name', 'qq', 'email', 'student_id') as $field) {
$query_value = trim((string) ($query[$field] ?? ''));
if ($query_value === '') {
continue;
}
$field_ref = trim((string) ($identity_refs[$field] ?? ''));
if ($field_ref === '') {
continue;
}
$field_id = itstudio_join_resolve_formidable_field_id($form_id, $field_ref);
if ($field_id > 0) {
return true;
}
}
return false;
}
function itstudio_join_has_uploaded_result_for_stage($settings, $stage_key) { function itstudio_join_has_uploaded_result_for_stage($settings, $stage_key) {
$settings = is_array($settings) ? $settings : array(); $settings = is_array($settings) ? $settings : array();
if (itstudio_join_is_file_result_mode($settings)) {
$attachment_id = itstudio_join_get_result_file_attachment_id($settings, $stage_key);
if ($attachment_id <= 0) {
return false;
}
$rows = itstudio_join_read_result_rows_from_attachment($attachment_id);
$records = itstudio_join_parse_result_rows_to_records($rows);
return !empty($records);
}
if (itstudio_join_is_formidable_result_mode($settings)) {
return itstudio_join_formidable_stage_has_data($settings, $stage_key);
}
$field_map = itstudio_join_get_result_field_map(); $field_map = itstudio_join_get_result_field_map();
$field_key = isset($field_map[$stage_key]) ? $field_map[$stage_key] : ''; $field_key = isset($field_map[$stage_key]) ? $field_map[$stage_key] : '';
if ($field_key === '') { if ($field_key === '') {
@@ -1734,6 +2383,40 @@ function itstudio_join_record_matches_query($record, $query) {
function itstudio_join_find_record_in_stage_results($settings, $stage_key, $query) { function itstudio_join_find_record_in_stage_results($settings, $stage_key, $query) {
$settings = is_array($settings) ? $settings : array(); $settings = is_array($settings) ? $settings : array();
if (itstudio_join_is_file_result_mode($settings)) {
$attachment_id = itstudio_join_get_result_file_attachment_id($settings, $stage_key);
if ($attachment_id <= 0) {
return false;
}
$rows = itstudio_join_read_result_rows_from_attachment($attachment_id);
$records = itstudio_join_parse_result_rows_to_records($rows);
if (empty($records)) {
return false;
}
foreach ($records as $record) {
if (itstudio_join_record_matches_query($record, $query) && !empty($record['passed'])) {
return true;
}
}
return false;
}
if (itstudio_join_is_formidable_result_mode($settings)) {
static $entry_cache = array();
$cache_key = md5(wp_json_encode(array(
'form_id' => itstudio_join_get_formidable_form_id($settings),
'query' => $query,
)));
if (!isset($entry_cache[$cache_key])) {
$entry_cache[$cache_key] = itstudio_join_find_formidable_entry_id_by_query($settings, $query);
}
$entry_id = (int) $entry_cache[$cache_key];
if ($entry_id <= 0) {
return false;
}
return itstudio_join_formidable_entry_matches_stage($settings, $entry_id, $stage_key);
}
$field_map = itstudio_join_get_result_field_map(); $field_map = itstudio_join_get_result_field_map();
$field_key = isset($field_map[$stage_key]) ? $field_map[$stage_key] : ''; $field_key = isset($field_map[$stage_key]) ? $field_map[$stage_key] : '';
if ($field_key === '') { if ($field_key === '') {
@@ -1829,33 +2512,23 @@ function itstudio_join_resolve_progress_lookup($runtime = array(), $request_sour
$status_second = itstudio_join_get_stage_status_by_key($runtime, 'second_interview'); $status_second = itstudio_join_get_stage_status_by_key($runtime, 'second_interview');
$status_notice = itstudio_join_get_stage_status_by_key($runtime, 'public_notice'); $status_notice = itstudio_join_get_stage_status_by_key($runtime, 'public_notice');
$uploaded_registration = itstudio_join_has_uploaded_result_for_stage($settings, 'registration');
$uploaded_first = itstudio_join_has_uploaded_result_for_stage($settings, 'first_interview'); $uploaded_first = itstudio_join_has_uploaded_result_for_stage($settings, 'first_interview');
$uploaded_assessment = itstudio_join_has_uploaded_result_for_stage($settings, 'assessment'); $uploaded_assessment = itstudio_join_has_uploaded_result_for_stage($settings, 'assessment');
$uploaded_second = itstudio_join_has_uploaded_result_for_stage($settings, 'second_interview'); $uploaded_second = itstudio_join_has_uploaded_result_for_stage($settings, 'second_interview');
$uploaded_notice = itstudio_join_has_uploaded_result_for_stage($settings, 'public_notice'); $uploaded_notice = itstudio_join_has_uploaded_result_for_stage($settings, 'public_notice');
if (!$uploaded_registration) { if ($status_registration === 'active' || $status_registration === 'upcoming') {
$response['message_cn'] = '报名数据尚未上传,暂无法查询。'; $response['message_cn'] = '报名阶段暂不开放进度查询。';
$response['message_en'] = 'Registration data has not been uploaded yet.'; $response['message_en'] = 'Progress lookup is not available during registration stage.';
$response['tone'] = 'warning';
return $response;
}
$is_registered = itstudio_join_find_record_in_stage_results($settings, 'registration', $query);
if (!$is_registered) {
$response['message_cn'] = '未报名。';
$response['message_en'] = 'Not registered.';
$response['tone'] = 'error';
return $response;
}
if ($status_registration === 'active' || $status_registration === 'upcoming' || $status_first === 'upcoming' || $status_first === 'pending') {
$response['message_cn'] = '已报名。';
$response['message_en'] = 'Registered.';
$response['tone'] = 'success'; $response['tone'] = 'success';
return $response; return $response;
} }
if ($status_first === 'upcoming' || $status_first === 'pending' || $status_first === 'active') {
$response['message_cn'] = '当前暂不可查询,请等待第一次面试结束后查看结果。';
$response['message_en'] = 'Lookup is not available yet. Please wait until Interview I results are published.';
$response['tone'] = 'warning';
return $response;
}
if ($status_first === 'completed') { if ($status_first === 'completed') {
if (!$uploaded_first) { if (!$uploaded_first) {
@@ -2208,7 +2881,7 @@ function itstudio_join_get_runtime_data() {
'all_day' => true, 'all_day' => true,
'location_cn' => '', 'location_cn' => '',
'location_en' => '', 'location_en' => '',
'result_uploaded' => itstudio_join_has_uploaded_result_for_stage($settings, 'registration'), 'result_uploaded' => false,
), ),
array( array(
'key' => 'first_interview', 'key' => 'first_interview',
@@ -2334,15 +3007,22 @@ function itstudio_join_get_runtime_data() {
$is_registration_open = itstudio_join_is_in_window($now, $registration_start, $registration_end); $is_registration_open = itstudio_join_is_in_window($now, $registration_start, $registration_end);
$is_query_open = false; $is_query_open = false;
if ($registration_start instanceof DateTimeImmutable) { $query_start = null;
if ($registration_end instanceof DateTimeImmutable) {
$query_start = $registration_end->modify('+1 second');
} elseif ($first_interview_start instanceof DateTimeImmutable) {
$query_start = $first_interview_start;
}
if ($query_start instanceof DateTimeImmutable) {
if ($notice_end instanceof DateTimeImmutable) { if ($notice_end instanceof DateTimeImmutable) {
$is_query_open = itstudio_join_is_in_window($now, $registration_start, $notice_end); $is_query_open = itstudio_join_is_in_window($now, $query_start, $notice_end);
} else { } else {
$is_query_open = ($now >= $registration_start); $is_query_open = ($now >= $query_start);
} }
} }
$is_notice_open = itstudio_join_is_in_window($now, $notice_start, $notice_end); $is_notice_open = itstudio_join_is_in_window($now, $notice_start, $notice_end);
$is_notice_finished = ($notice_end instanceof DateTimeImmutable) && ($now > $notice_end);
$current_stage_photo_url = itstudio_join_get_stage_photo_url($settings, (string) ($current_stage['key'] ?? '')); $current_stage_photo_url = itstudio_join_get_stage_photo_url($settings, (string) ($current_stage['key'] ?? ''));
if ($current_stage_photo_url === '') { if ($current_stage_photo_url === '') {
$current_stage_photo_url = get_template_directory_uri() . '/resources/it_logo_2024.svg'; $current_stage_photo_url = get_template_directory_uri() . '/resources/it_logo_2024.svg';
@@ -2390,6 +3070,7 @@ function itstudio_join_get_runtime_data() {
'is_registration_open' => $is_registration_open, 'is_registration_open' => $is_registration_open,
'is_query_open' => $is_query_open, 'is_query_open' => $is_query_open,
'is_notice_open' => $is_notice_open, 'is_notice_open' => $is_notice_open,
'is_notice_finished' => $is_notice_finished,
'show_progress_visual' => $show_progress_visual, 'show_progress_visual' => $show_progress_visual,
'current_stage_photo_url' => $current_stage_photo_url, 'current_stage_photo_url' => $current_stage_photo_url,
'query_deadline_cn' => $notice_end instanceof DateTimeImmutable ? $notice_end->format('Y-m-d H:i') : '', 'query_deadline_cn' => $notice_end instanceof DateTimeImmutable ? $notice_end->format('Y-m-d H:i') : '',
@@ -2469,6 +3150,15 @@ function itstudio_join_register_settings() {
} }
add_action('admin_init', 'itstudio_join_register_settings'); add_action('admin_init', 'itstudio_join_register_settings');
function itstudio_join_allow_result_file_mimes($mimes) {
$mimes = is_array($mimes) ? $mimes : array();
// 允许在媒体库上传招新结果文件。
$mimes['csv'] = 'text/csv';
$mimes['xlsx'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
return $mimes;
}
add_filter('upload_mimes', 'itstudio_join_allow_result_file_mimes');
function itstudio_join_register_settings_page() { function itstudio_join_register_settings_page() {
add_options_page( add_options_page(
'招新设置', '招新设置',
@@ -2521,6 +3211,52 @@ function itstudio_join_render_result_records_row($field_key, $label, $settings,
<?php <?php
} }
function itstudio_join_render_text_setting_row($field_key, $label, $settings, $description = '', $placeholder = '') {
$value = isset($settings[$field_key]) ? (string) $settings[$field_key] : '';
?>
<tr>
<th scope="row"><label for="<?php echo esc_attr('itstudio_join_' . $field_key); ?>"><?php echo esc_html($label); ?></label></th>
<td>
<input
type="text"
id="<?php echo esc_attr('itstudio_join_' . $field_key); ?>"
name="itstudio_join_settings[<?php echo esc_attr($field_key); ?>]"
value="<?php echo esc_attr($value); ?>"
class="regular-text code"
<?php if ($placeholder !== '') : ?>placeholder="<?php echo esc_attr($placeholder); ?>"<?php endif; ?>
>
<?php if ($description !== '') : ?>
<p class="description"><?php echo esc_html($description); ?></p>
<?php endif; ?>
</td>
</tr>
<?php
}
function itstudio_join_render_result_file_row($field_key, $label, $settings, $description = '') {
$attachment_id = isset($settings[$field_key]) ? absint($settings[$field_key]) : 0;
$file_url = $attachment_id > 0 ? wp_get_attachment_url($attachment_id) : '';
$filename = $attachment_id > 0 ? basename((string) get_attached_file($attachment_id)) : '';
?>
<tr>
<th scope="row"><label><?php echo esc_html($label); ?></label></th>
<td>
<input type="hidden" class="itstudio-join-result-file-id" name="itstudio_join_settings[<?php echo esc_attr($field_key); ?>]" value="<?php echo esc_attr($attachment_id); ?>">
<div class="itstudio-join-result-file-preview" style="margin-bottom:10px;<?php echo $file_url !== '' ? '' : 'display:none;'; ?>">
<a class="itstudio-join-result-file-link" href="<?php echo esc_url($file_url); ?>" target="_blank" rel="noopener"><?php echo esc_html($filename !== '' ? $filename : $file_url); ?></a>
</div>
<button type="button" class="button itstudio-join-result-file-upload"><?php esc_html_e('上传 / 选择结果文件', 'itstudio'); ?></button>
<button type="button" class="button-link-delete itstudio-join-result-file-clear" style="margin-left:8px;<?php echo $file_url !== '' ? '' : 'display:none;'; ?>"><?php esc_html_e('移除', 'itstudio'); ?></button>
<?php if ($description !== '') : ?>
<p class="description"><?php echo esc_html($description); ?></p>
<?php else : ?>
<p class="description">支持 CSV / XLSX。列结构:姓名,QQ,邮箱,学号,手机,是否通过(1=通过,其他=未通过)。</p>
<?php endif; ?>
</td>
</tr>
<?php
}
function itstudio_join_render_settings_page() { function itstudio_join_render_settings_page() {
if (!current_user_can('manage_options')) { if (!current_user_can('manage_options')) {
return; return;
@@ -2639,11 +3375,18 @@ function itstudio_join_render_settings_page() {
<?php itstudio_join_render_photo_field_row('photo_second_interview', '第二次面试图片', $settings); ?> <?php itstudio_join_render_photo_field_row('photo_second_interview', '第二次面试图片', $settings); ?>
<?php itstudio_join_render_photo_field_row('photo_public_notice', '录取结果公布阶段图片', $settings); ?> <?php itstudio_join_render_photo_field_row('photo_public_notice', '录取结果公布阶段图片', $settings); ?>
<?php itstudio_join_render_photo_field_row('photo_inactive', '非招新时段图片', $settings); ?> <?php itstudio_join_render_photo_field_row('photo_inactive', '非招新时段图片', $settings); ?>
<?php itstudio_join_render_result_records_row('result_registration_records', '报名阶段结果(已报名名单)', $settings, '用于“报名阶段查询”:匹配到即显示“已报名”,否则显示“未报名”。'); ?> <tr>
<?php itstudio_join_render_result_records_row('result_first_interview_records', '第一次面试结果(通过名单)', $settings, '用于“一面结束后查询”:在名单内显示“已通过第一次面试”,否则显示未通过。'); ?> <th scope="row"><label>结果数据来源</label></th>
<?php itstudio_join_render_result_records_row('result_assessment_records', '国庆能力摸底结果(通过名单)', $settings, '用于“摸底结束后查询”:在名单内显示“已通过国庆能力摸底”,否则显示未通过。'); ?> <td>
<?php itstudio_join_render_result_records_row('result_second_interview_records', '第二次面试结果(通过名单)', $settings, '用于“二面结束后查询”:在名单内显示“已通过第二次面试”,否则显示未通过。'); ?> <input type="hidden" name="itstudio_join_settings[result_data_source]" value="file">
<?php itstudio_join_render_result_records_row('result_admission_records', '录取结果(录取名单)', $settings, '用于“录取结果公布阶段查询”:在名单内显示“已录取”,否则显示未录取。'); ?> <strong>CSV / XLSX 文件上传</strong>
<p class="description">上传第一次面试、国庆能力摸底、第二次面试、录取结果四个阶段文件,系统按“姓名,QQ,邮箱,学号,手机,是否通过”进行查询。最后一列为 1 视为通过,否则未通过。</p>
</td>
</tr>
<?php itstudio_join_render_result_file_row('result_first_interview_file', '第一次面试结果文件', $settings); ?>
<?php itstudio_join_render_result_file_row('result_assessment_file', '国庆能力摸底结果文件', $settings); ?>
<?php itstudio_join_render_result_file_row('result_second_interview_file', '第二次面试结果文件', $settings); ?>
<?php itstudio_join_render_result_file_row('result_admission_file', '录取结果文件', $settings); ?>
<tr> <tr>
<th scope="row"><label for="itstudio_join_signup_shortcode">报名表单 Shortcode</label></th> <th scope="row"><label for="itstudio_join_signup_shortcode">报名表单 Shortcode</label></th>
<td> <td>
@@ -2655,6 +3398,15 @@ function itstudio_join_render_settings_page() {
<?php submit_button('保存设置'); ?> <?php submit_button('保存设置'); ?>
</form> </form>
<div class="notice notice-info" style="padding:12px 14px; margin: 16px 0;">
<h2 style="margin:0 0 8px;">阶段结果文件使用说明</h2>
<ol style="margin:0 0 0 18px;">
<li>上传四个阶段结果文件:第一次面试、国庆能力摸底、第二次面试、录取结果(报名阶段无需结果文件)。</li>
<li>文件列顺序固定为:<code>姓名,QQ,邮箱,学号,手机,是否通过</code>。</li>
<li><strong>是否通过</strong> 字段:<code>1</code> 表示通过;其他任意值(含 <code>0</code>、空)都按未通过处理。</li>
<li>前台用户可使用姓名、QQ、邮箱、学号进行查询,系统会按当前阶段自动返回对应进度。</li>
</ol>
</div>
<hr> <hr>
<h2>阶段预览</h2> <h2>阶段预览</h2>
<p>国庆能力摸底默认固定为每年 10 月 1 日至 10 月 7 日;如填写上方“摸底开始/结束日期(调试)”则优先使用调试时间,留空则恢复默认固定窗口。</p> <p>国庆能力摸底默认固定为每年 10 月 1 日至 10 月 7 日;如填写上方“摸底开始/结束日期(调试)”则优先使用调试时间,留空则恢复默认固定窗口。</p>
@@ -2758,6 +3510,82 @@ function itstudio_join_render_settings_page() {
button.style.display = 'none'; button.style.display = 'none';
}); });
}); });
const resultFileButtons = document.querySelectorAll('.itstudio-join-result-file-upload');
resultFileButtons.forEach((button) => {
button.addEventListener('click', () => {
if (!window.wp || !wp.media) {
return;
}
const row = button.closest('td');
if (!row) {
return;
}
const input = row.querySelector('.itstudio-join-result-file-id');
const preview = row.querySelector('.itstudio-join-result-file-preview');
const link = row.querySelector('.itstudio-join-result-file-link');
const clearBtn = row.querySelector('.itstudio-join-result-file-clear');
if (!input || !preview || !link) {
return;
}
const frame = wp.media({
title: '选择结果文件',
button: { text: '使用此文件' },
multiple: false,
});
frame.on('select', () => {
const selection = frame.state().get('selection').first();
if (!selection) {
return;
}
const data = selection.toJSON();
const fileUrl = data.url || '';
const filename = data.filename || (fileUrl ? fileUrl.split('/').pop() : '');
const ext = filename.includes('.') ? filename.split('.').pop().toLowerCase() : '';
if (!['csv', 'xlsx'].includes(ext)) {
window.alert('请上传 CSV 或 XLSX 文件。');
return;
}
input.value = data.id || '';
link.href = fileUrl || '#';
link.textContent = filename || fileUrl || '';
preview.style.display = fileUrl ? 'block' : 'none';
if (clearBtn) {
clearBtn.style.display = fileUrl ? 'inline' : 'none';
}
});
frame.open();
});
});
document.querySelectorAll('.itstudio-join-result-file-clear').forEach((button) => {
button.addEventListener('click', () => {
const row = button.closest('td');
if (!row) {
return;
}
const input = row.querySelector('.itstudio-join-result-file-id');
const preview = row.querySelector('.itstudio-join-result-file-preview');
const link = row.querySelector('.itstudio-join-result-file-link');
if (input) {
input.value = '';
}
if (link) {
link.href = '#';
link.textContent = '';
}
if (preview) {
preview.style.display = 'none';
}
button.style.display = 'none';
});
});
})(); })();
</script> </script>
</div> </div>
+24 -11
View File
@@ -19,8 +19,9 @@ $current_stage_mode = isset($join_runtime['current_stage_mode']) ? (string) $joi
$is_registration_open = !empty($join_runtime['is_registration_open']); $is_registration_open = !empty($join_runtime['is_registration_open']);
$is_notice_open = !empty($join_runtime['is_notice_open']); $is_notice_open = !empty($join_runtime['is_notice_open']);
$is_notice_finished = !empty($join_runtime['is_notice_finished']);
$show_progress_visual = !empty($join_runtime['show_progress_visual']); $show_progress_visual = !empty($join_runtime['show_progress_visual']);
$is_progress_query_open = !empty($join_runtime['show_progress_visual']); $is_progress_query_open = !empty($join_runtime['is_query_open']);
$current_label_cn = isset($current_stage['label_cn']) ? (string) $current_stage['label_cn'] : '当前未在招新时段'; $current_label_cn = isset($current_stage['label_cn']) ? (string) $current_stage['label_cn'] : '当前未在招新时段';
$current_label_en = isset($current_stage['label_en']) ? (string) $current_stage['label_en'] : 'Recruitment is currently closed'; $current_label_en = isset($current_stage['label_en']) ? (string) $current_stage['label_en'] : 'Recruitment is currently closed';
@@ -211,11 +212,15 @@ if ($is_post_request && function_exists('itstudio_join_detect_form_submission_st
<?php foreach ($join_stages as $stage) : ?> <?php foreach ($join_stages as $stage) : ?>
<?php <?php
$stage_status = isset($stage['status']) ? (string) $stage['status'] : 'pending'; $stage_status = isset($stage['status']) ? (string) $stage['status'] : 'pending';
if ($is_notice_finished) {
// 录取结果公布结束后,所有阶段统一显示为“已结束”。
$stage_status = 'completed';
}
$status_cn = '待设置'; $status_cn = '待设置';
$status_en = 'Pending'; $status_en = 'Pending';
if ($stage_status === 'completed') { if ($stage_status === 'completed') {
$status_cn = '已完成'; $status_cn = '已结束';
$status_en = 'Completed'; $status_en = 'Ended';
} elseif ($stage_status === 'active') { } elseif ($stage_status === 'active') {
$status_cn = '进行中'; $status_cn = '进行中';
$status_en = 'In Progress'; $status_en = 'In Progress';
@@ -239,18 +244,26 @@ if ($is_post_request && function_exists('itstudio_join_detect_form_submission_st
&& $stage_status === 'completed' && $stage_status === 'completed'
&& $stage_result_uploaded; && $stage_result_uploaded;
$is_query_ready_status = false; $is_query_ready_status = false;
if ($is_mid_stage_query_ready) { $is_waiting_notice_status = false;
if (!$is_notice_finished && $is_mid_stage_query_ready) {
$status_cn = '可查询结果'; $status_cn = '可查询结果';
$status_en = 'Query Available'; $status_en = 'Query Available';
$is_query_ready_status = true; $is_query_ready_status = true;
} }
if ($is_public_notice_stage && in_array($stage_status, array('active', 'completed'), true)) { if (!$is_notice_finished && $is_public_notice_stage && in_array($stage_status, array('active', 'completed'), true)) {
$status_cn = '可查询结果'; if ($stage_result_uploaded) {
$status_en = 'Query Available'; $status_cn = '可查询结果';
$is_query_ready_status = true; $status_en = 'Query Available';
$is_query_ready_status = true;
} else {
$status_cn = '请耐心等待通知';
$status_en = 'Please wait for notice';
$is_query_ready_status = false;
$is_waiting_notice_status = true;
}
} }
?> ?>
<li class="join-stage-item is-<?php echo esc_attr($stage_status); ?><?php echo $is_current_stage ? ' is-current' : ''; ?>"> <li class="join-stage-item is-<?php echo esc_attr($stage_status); ?><?php echo $is_current_stage ? ' is-current' : ''; ?><?php echo $is_waiting_notice_status ? ' is-waiting-notice' : ''; ?>">
<div class="join-stage-title-row"> <div class="join-stage-title-row">
<h3 <h3
class="join-stage-name" class="join-stage-name"
@@ -260,7 +273,7 @@ if ($is_post_request && function_exists('itstudio_join_detect_form_submission_st
<?php echo esc_html((string) ($stage['label_cn'] ?? '')); ?> <?php echo esc_html((string) ($stage['label_cn'] ?? '')); ?>
</h3> </h3>
<span <span
class="join-stage-status<?php echo $is_query_ready_status ? ' is-query-ready' : ''; ?>" class="join-stage-status<?php echo $is_query_ready_status ? ' is-query-ready' : ''; ?><?php echo $is_waiting_notice_status ? ' is-waiting-notice' : ''; ?>"
data-cn="<?php echo esc_attr($status_cn); ?>" data-cn="<?php echo esc_attr($status_cn); ?>"
data-en="<?php echo esc_attr($status_en); ?>" data-en="<?php echo esc_attr($status_en); ?>"
> >
@@ -321,7 +334,7 @@ if ($is_post_request && function_exists('itstudio_join_detect_form_submission_st
<article class="join-form-card"> <article class="join-form-card">
<header class="join-form-head"> <header class="join-form-head">
<h2 data-cn="录取进度查询" data-en="Admission Progress Lookup">录取进度查询</h2> <h2 data-cn="录取进度查询" data-en="Admission Progress Lookup">录取进度查询</h2>
<p data-cn="报名阶段至录取结果公布阶段可查询" data-en="Available from registration stage to final result release stage.">报名阶段至录取结果公布阶段可查询</p> <p data-cn="报名结束后至录取结果公布阶段可查询" data-en="Available after registration ends until the final result release stage.">报名结束后至录取结果公布阶段可查询</p>
</header> </header>
<div class="join-form-content"> <div class="join-form-content">
<form method="get" class="join-progress-query-form"> <form method="get" class="join-progress-query-form">