简化结果上传查询的方法
现在,在后端使用csv/xlsx上传结果文件即可 优化前端信息显示
This commit is contained in:
@@ -223,7 +223,7 @@ MIT License - 详见 LICENSE 文件
|
||||
### 1. 需要安装的插件
|
||||
|
||||
1. **Formidable Forms**
|
||||
用于报名表单、进度查询表单、公示视图(可选使用 Formidable Views)。
|
||||
用于报名表单渲染(前台报名)。
|
||||
2. **WP Mail SMTP**
|
||||
用于 Formidable 提交邮件发送(站点邮件走 SMTP)。
|
||||
|
||||
@@ -234,30 +234,44 @@ MIT License - 详见 LICENSE 文件
|
||||
可配置字段:
|
||||
- 报名开始时间(datetime)
|
||||
- 报名结束时间(datetime)
|
||||
- 第一次面试日期(date)
|
||||
- 第二次面试日期(date)
|
||||
- 录取公示开始日期(date,系统自动延续 7 天)
|
||||
- 第一次面试开始/结束时间(datetime)
|
||||
- 第一次面试地点(中/英)
|
||||
- 第二次面试开始/结束时间(datetime)
|
||||
- 第二次面试地点(中/英)
|
||||
- 国庆能力摸底开始/结束日期(调试)
|
||||
- 录取结果公布开始日期(date,系统自动延续 7 天)
|
||||
- 报名表单 Shortcode
|
||||
- 结果查询 Shortcode
|
||||
- 公示视图 Shortcode
|
||||
|
||||
说明:
|
||||
- 「国庆能力摸底阶段」固定为每年 **10/01 - 10/07**。
|
||||
- 年份优先取报名开始时间年份;若未设置,则取公示开始年份;仍未设置则取当前年份。
|
||||
- 若填写“国庆能力摸底开始/结束日期(调试)”,将优先使用调试时间。
|
||||
|
||||
### 3. 前台显示逻辑
|
||||
### 3. 阶段结果文件配置(重点)
|
||||
|
||||
在同一页面中,为以下阶段上传结果文件(CSV 或 XLSX):
|
||||
- 第一次面试结果文件
|
||||
- 国庆能力摸底结果文件
|
||||
- 第二次面试结果文件
|
||||
- 录取结果文件
|
||||
|
||||
文件列顺序必须为:
|
||||
|
||||
`姓名,QQ,邮箱,学号,手机,是否通过`
|
||||
|
||||
其中:
|
||||
- `是否通过 = 1` 表示通过
|
||||
- 其他任意值(含 `0`、空)都按未通过处理
|
||||
|
||||
注意:
|
||||
- 不需要配置任何“字段映射”或“阶段识别字段”。
|
||||
- 系统会自动按上述固定列进行识别与查询。
|
||||
|
||||
### 4. 前台显示逻辑
|
||||
|
||||
- 报名表单:仅在报名阶段显示。
|
||||
- 结果查询表单:从报名开始到公示结束前可用。
|
||||
- 公示视图:仅在公示 7 天窗口内显示。
|
||||
- 录取进度查询:报名阶段不显示;报名结束后按当前阶段显示对应结果(使用姓名/QQ/邮箱/学号查询)。
|
||||
- Canvas 进度条:根据当前时间自动高亮阶段(已完成/进行中/未开始)。
|
||||
|
||||
### 4. Formidable 推荐配置方式
|
||||
|
||||
1. 新建报名表单,复制 shortcode,填入「报名表单 Shortcode」。
|
||||
2. 新建结果查询表单(例如:学号/邮箱/手机号 + 状态查询逻辑),填入「结果查询 Shortcode」。
|
||||
3. 如需公示名单展示,创建 Formidable View,复制 shortcode,填入「公示视图 Shortcode」。
|
||||
|
||||
### 5. WP Mail SMTP 配置建议
|
||||
|
||||
1. 安装并启用 **WP Mail SMTP**。
|
||||
@@ -265,17 +279,10 @@ MIT License - 详见 LICENSE 文件
|
||||
3. 设置发件人邮箱与发件人名称。
|
||||
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).
|
||||
- Set Formidable shortcodes:
|
||||
- registration form shortcode
|
||||
- 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.
|
||||
- 后台路径:`设置 -> 招新设置`。
|
||||
- 先配置时间节点:报名开始/结束、一面开始/结束、二面开始/结束、录取结果公布开始日期。
|
||||
- “国庆能力摸底”默认是每年 `10/01 - 10/07`(可用调试日期覆盖)。
|
||||
- 报名表单短代码使用 Formidable 表单,邮件发送建议配合 WP Mail SMTP。
|
||||
- 阶段结果统一使用 CSV/XLSX 上传,不再使用 Formidable Entries 做结果查询。
|
||||
|
||||
@@ -501,6 +501,31 @@
|
||||
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 {
|
||||
border-color: color-mix(in srgb, #0f8a4f 48%, var(--border-default) 52%);
|
||||
background: linear-gradient(
|
||||
@@ -518,6 +543,11 @@
|
||||
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-pending .join-stage-status {
|
||||
color: var(--text-secondary);
|
||||
|
||||
+856
-28
@@ -1401,6 +1401,21 @@ function itstudio_join_get_default_settings() {
|
||||
'assessment_start_date' => '',
|
||||
'assessment_end_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_first_interview_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_end_date' => itstudio_join_sanitize_date_value($input['assessment_end_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_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'] ?? ''),
|
||||
@@ -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) {
|
||||
$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_key = isset($field_map[$stage_key]) ? $field_map[$stage_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) {
|
||||
$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_key = isset($field_map[$stage_key]) ? $field_map[$stage_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_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_assessment = itstudio_join_has_uploaded_result_for_stage($settings, 'assessment');
|
||||
$uploaded_second = itstudio_join_has_uploaded_result_for_stage($settings, 'second_interview');
|
||||
$uploaded_notice = itstudio_join_has_uploaded_result_for_stage($settings, 'public_notice');
|
||||
|
||||
if (!$uploaded_registration) {
|
||||
$response['message_cn'] = '报名数据尚未上传,暂无法查询。';
|
||||
$response['message_en'] = 'Registration data has not been uploaded yet.';
|
||||
$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.';
|
||||
if ($status_registration === 'active' || $status_registration === 'upcoming') {
|
||||
$response['message_cn'] = '报名阶段暂不开放进度查询。';
|
||||
$response['message_en'] = 'Progress lookup is not available during registration stage.';
|
||||
$response['tone'] = 'success';
|
||||
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 (!$uploaded_first) {
|
||||
@@ -2208,7 +2881,7 @@ function itstudio_join_get_runtime_data() {
|
||||
'all_day' => true,
|
||||
'location_cn' => '',
|
||||
'location_en' => '',
|
||||
'result_uploaded' => itstudio_join_has_uploaded_result_for_stage($settings, 'registration'),
|
||||
'result_uploaded' => false,
|
||||
),
|
||||
array(
|
||||
'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_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) {
|
||||
$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 {
|
||||
$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_finished = ($notice_end instanceof DateTimeImmutable) && ($now > $notice_end);
|
||||
$current_stage_photo_url = itstudio_join_get_stage_photo_url($settings, (string) ($current_stage['key'] ?? ''));
|
||||
if ($current_stage_photo_url === '') {
|
||||
$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_query_open' => $is_query_open,
|
||||
'is_notice_open' => $is_notice_open,
|
||||
'is_notice_finished' => $is_notice_finished,
|
||||
'show_progress_visual' => $show_progress_visual,
|
||||
'current_stage_photo_url' => $current_stage_photo_url,
|
||||
'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');
|
||||
|
||||
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() {
|
||||
add_options_page(
|
||||
'招新设置',
|
||||
@@ -2521,6 +3211,52 @@ function itstudio_join_render_result_records_row($field_key, $label, $settings,
|
||||
<?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() {
|
||||
if (!current_user_can('manage_options')) {
|
||||
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_public_notice', '录取结果公布阶段图片', $settings); ?>
|
||||
<?php itstudio_join_render_photo_field_row('photo_inactive', '非招新时段图片', $settings); ?>
|
||||
<?php itstudio_join_render_result_records_row('result_registration_records', '报名阶段结果(已报名名单)', $settings, '用于“报名阶段查询”:匹配到即显示“已报名”,否则显示“未报名”。'); ?>
|
||||
<?php itstudio_join_render_result_records_row('result_first_interview_records', '第一次面试结果(通过名单)', $settings, '用于“一面结束后查询”:在名单内显示“已通过第一次面试”,否则显示未通过。'); ?>
|
||||
<?php itstudio_join_render_result_records_row('result_assessment_records', '国庆能力摸底结果(通过名单)', $settings, '用于“摸底结束后查询”:在名单内显示“已通过国庆能力摸底”,否则显示未通过。'); ?>
|
||||
<?php itstudio_join_render_result_records_row('result_second_interview_records', '第二次面试结果(通过名单)', $settings, '用于“二面结束后查询”:在名单内显示“已通过第二次面试”,否则显示未通过。'); ?>
|
||||
<?php itstudio_join_render_result_records_row('result_admission_records', '录取结果(录取名单)', $settings, '用于“录取结果公布阶段查询”:在名单内显示“已录取”,否则显示未录取。'); ?>
|
||||
<tr>
|
||||
<th scope="row"><label>结果数据来源</label></th>
|
||||
<td>
|
||||
<input type="hidden" name="itstudio_join_settings[result_data_source]" value="file">
|
||||
<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>
|
||||
<th scope="row"><label for="itstudio_join_signup_shortcode">报名表单 Shortcode</label></th>
|
||||
<td>
|
||||
@@ -2655,6 +3398,15 @@ function itstudio_join_render_settings_page() {
|
||||
<?php submit_button('保存设置'); ?>
|
||||
</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>
|
||||
<h2>阶段预览</h2>
|
||||
<p>国庆能力摸底默认固定为每年 10 月 1 日至 10 月 7 日;如填写上方“摸底开始/结束日期(调试)”则优先使用调试时间,留空则恢复默认固定窗口。</p>
|
||||
@@ -2758,6 +3510,82 @@ function itstudio_join_render_settings_page() {
|
||||
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>
|
||||
</div>
|
||||
|
||||
+21
-8
@@ -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_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']);
|
||||
$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_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
|
||||
$stage_status = isset($stage['status']) ? (string) $stage['status'] : 'pending';
|
||||
if ($is_notice_finished) {
|
||||
// 录取结果公布结束后,所有阶段统一显示为“已结束”。
|
||||
$stage_status = 'completed';
|
||||
}
|
||||
$status_cn = '待设置';
|
||||
$status_en = 'Pending';
|
||||
if ($stage_status === 'completed') {
|
||||
$status_cn = '已完成';
|
||||
$status_en = 'Completed';
|
||||
$status_cn = '已结束';
|
||||
$status_en = 'Ended';
|
||||
} elseif ($stage_status === 'active') {
|
||||
$status_cn = '进行中';
|
||||
$status_en = 'In Progress';
|
||||
@@ -239,18 +244,26 @@ if ($is_post_request && function_exists('itstudio_join_detect_form_submission_st
|
||||
&& $stage_status === 'completed'
|
||||
&& $stage_result_uploaded;
|
||||
$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_en = 'Query Available';
|
||||
$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)) {
|
||||
if ($stage_result_uploaded) {
|
||||
$status_cn = '可查询结果';
|
||||
$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">
|
||||
<h3
|
||||
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'] ?? '')); ?>
|
||||
</h3>
|
||||
<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-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">
|
||||
<header class="join-form-head">
|
||||
<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>
|
||||
<div class="join-form-content">
|
||||
<form method="get" class="join-progress-query-form">
|
||||
|
||||
Reference in New Issue
Block a user