答案对象): * - 文本/日期等:{ "t": "text"|"date"|"datetime"|"file", "v": "..." } * - 单选/下拉:{ "t": "option", "k": "" } * - 多选: { "t": "options", "ks": ["", ""] } * * signup_form_schema 中选项字段需声明 options_source: * - static: options[] 每项 { key, label, value? } * - dict: { type:"dict", dict_code:"signup_gender" },运行时 key = (string) dict_items.id * - table: { type:"table", table:"signup_channels", key_field:"code", ... }(预留) */ class SignupFormAnswers { private const PLAIN_TYPES = ['text', 'textarea', 'date', 'datetime', 'file']; private const OPTION_TYPES = ['radio', 'select']; private const MULTI_OPTION_TYPES = ['checkbox']; /** * 规范化并校验 form_answers(创建报名时调用)。 * * @param array $answers * @param array>|null $schema * @return array */ public static function normalize(array $answers, ?array $schema): array { if ($schema === null || $schema === []) { return $answers; } $fieldMap = []; foreach ($schema as $field) { if (! is_array($field)) { continue; } $key = $field['key'] ?? null; if (is_string($key) && $key !== '') { $fieldMap[$key] = $field; } } $out = []; foreach ($answers as $fieldKey => $raw) { if (! is_string($fieldKey) || $fieldKey === '') { continue; } if (! isset($fieldMap[$fieldKey])) { continue; } $normalized = self::normalizeOne($fieldMap[$fieldKey], $raw); if ($normalized !== null) { $out[$fieldKey] = $normalized; } } return $out; } /** * @param array $fieldDef * @param mixed $raw * @return array|null */ protected static function normalizeOne(array $fieldDef, mixed $raw): ?array { if (! is_array($raw)) { return null; } $type = (string) ($fieldDef['type'] ?? 'text'); if (in_array($type, self::OPTION_TYPES, true)) { $k = $raw['k'] ?? $raw['key'] ?? null; if ($k === null || $k === '') { return null; } return ['t' => 'option', 'k' => (string) $k]; } if (in_array($type, self::MULTI_OPTION_TYPES, true)) { $ks = $raw['ks'] ?? $raw['keys'] ?? null; if (! is_array($ks)) { return null; } $keys = array_values(array_filter(array_map('strval', $ks), fn ($x) => $x !== '')); return ['t' => 'options', 'ks' => $keys]; } if (in_array($type, self::PLAIN_TYPES, true)) { $t = $type === 'textarea' ? 'text' : $type; if (! array_key_exists('v', $raw) && ! array_key_exists('value', $raw)) { return null; } $v = $raw['v'] ?? $raw['value']; return ['t' => $t, 'v' => is_scalar($v) ? (string) $v : json_encode($v)]; } return null; } }