[ ['name'=>'product_type','label'=>'类型','type'=>'select','source'=>'type_list','required'=>true,'enabled'=>true,'placeholder'=>'请选择类型','col_width'=>2,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'类型'], ['name'=>'product_model','label'=>'产品型号','type'=>'select','source'=>'product_models','required'=>true,'enabled'=>true,'placeholder'=>'请选择产品型号','col_width'=>2,'is_scan'=>false,'show_in_table'=>false,'table_label'=>'产品型号'], ['name'=>'serial_no','label'=>'产品序列号','type'=>'text','required'=>true,'enabled'=>true,'placeholder'=>'扫描/输入后按回车','col_width'=>3,'is_scan'=>true,'show_in_table'=>true,'table_label'=>'产品序列号'], ], 'pcb_test' => [ ['name'=>'product_type','label'=>'产品类型','type'=>'hidden','required'=>true,'enabled'=>true,'placeholder'=>'PCBA','col_width'=>0,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'产品类型','fixed_value'=>'PCBA'], ['name'=>'product_model','label'=>'产品型号','type'=>'select','source'=>'product_models','required'=>true,'enabled'=>true,'placeholder'=>'请选择产品型号','col_width'=>2,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'产品型号'], ['name'=>'static_power','label'=>'静态功耗','type'=>'number','required'=>false,'enabled'=>true,'placeholder'=>'0.0000','col_width'=>2,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'静态功耗'], ['name'=>'input_output','label'=>'输入输出','type'=>'text','required'=>false,'enabled'=>true,'placeholder'=>'正常/异常','col_width'=>2,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'输入输出'], ['name'=>'test_status','label'=>'测试状态','type'=>'select','source'=>'test_status_options','required'=>false,'enabled'=>true,'placeholder'=>'请选择','col_width'=>2,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'测试状态'], ['name'=>'serial_no','label'=>'PCBA序列号','type'=>'text','required'=>true,'enabled'=>true,'placeholder'=>'扫描 PCBA 序列号后按回车','col_width'=>6,'is_scan'=>true,'show_in_table'=>true,'table_label'=>'PCBA序列号'], ], 'battery' => [ ['name'=>'product_type','label'=>'类型','type'=>'hidden','required'=>true,'enabled'=>true,'placeholder'=>'电池','col_width'=>0,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'类型','fixed_value'=>'电池'], ['name'=>'product_model','label'=>'产品型号','type'=>'select','source'=>'product_models','required'=>true,'enabled'=>true,'placeholder'=>'请选择产品型号','col_width'=>3,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'产品型号'], ['name'=>'voltage_platform','label'=>'电压平台','type'=>'text','required'=>false,'enabled'=>true,'placeholder'=>'如:3.7V','col_width'=>2,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'电压平台'], ['name'=>'no_load_voltage','label'=>'空载电压','type'=>'number','required'=>false,'enabled'=>true,'placeholder'=>'0.0000','col_width'=>2,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'空载电压'], ['name'=>'internal_resistance','label'=>'电池内阻','type'=>'number','required'=>false,'enabled'=>true,'placeholder'=>'0.0000','col_width'=>2,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'电池内阻'], ['name'=>'serial_no','label'=>'产品序列号','type'=>'text','required'=>true,'enabled'=>true,'placeholder'=>'扫描/输入后按回车','col_width'=>4,'is_scan'=>true,'show_in_table'=>true,'table_label'=>'产品序列号'], ], 'assembly' => [ ['name'=>'product_model','label'=>'产品型号','type'=>'select','source'=>'product_models','required'=>true,'enabled'=>true,'placeholder'=>'请选择产品型号','col_width'=>2,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'产品型号'], ['name'=>'shell_color','label'=>'外壳颜色','type'=>'select','source'=>'shell_color_options','required'=>false,'enabled'=>true,'placeholder'=>'请选择外壳颜色','col_width'=>2,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'外壳颜色'], ['name'=>'finished_serial','label'=>'成品序列号','type'=>'text','required'=>true,'enabled'=>true,'placeholder'=>'扫描成品序列号','col_width'=>3,'is_scan'=>true,'show_in_table'=>true,'table_label'=>'成品序列号'], ['name'=>'battery_serial','label'=>'电池序列号','type'=>'text','required'=>true,'enabled'=>true,'placeholder'=>'扫描电池序列号','col_width'=>3,'is_scan'=>true,'show_in_table'=>true,'table_label'=>'电池序列号'], ['name'=>'pcb_serial','label'=>'PCB序列号','type'=>'text','required'=>true,'enabled'=>true,'placeholder'=>'扫描PCB序列号','col_width'=>2,'is_scan'=>true,'show_in_table'=>true,'table_label'=>'PCB序列号'], ], 'finished_test' => [ ['name'=>'product_model','label'=>'产品型号','type'=>'select','source'=>'product_models','required'=>false,'enabled'=>true,'placeholder'=>'请选择产品型号','col_width'=>2,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'产品型号'], ['name'=>'test_item','label'=>'测试项目','type'=>'text','required'=>true,'enabled'=>true,'placeholder'=>'如:功能测试','col_width'=>2,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'测试项目'], ['name'=>'test_result','label'=>'测试结果','type'=>'select','source'=>'test_result_options','required'=>false,'enabled'=>true,'placeholder'=>'请选择','col_width'=>2,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'测试结果'], ['name'=>'test_steps','label'=>'测试步骤','type'=>'text','required'=>false,'enabled'=>true,'placeholder'=>'步骤描述','col_width'=>2,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'测试步骤'], ['name'=>'finished_serial','label'=>'成品序列号','type'=>'text','required'=>true,'enabled'=>true,'placeholder'=>'扫描序列号后按回车','col_width'=>4,'is_scan'=>true,'show_in_table'=>true,'table_label'=>'成品序列号'], ], 'warehouse' => [ ['name'=>'product_model','label'=>'产品型号','type'=>'select','source'=>'product_models','required'=>false,'enabled'=>true,'placeholder'=>'请选择产品型号','col_width'=>3,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'产品型号'], ['name'=>'finished_serial','label'=>'成品序列号','type'=>'text','required'=>true,'enabled'=>true,'placeholder'=>'扫描成品序列号后按回车','col_width'=>4,'is_scan'=>true,'show_in_table'=>true,'table_label'=>'成品序列号'], ['name'=>'box_serial','label'=>'箱序列号','type'=>'text','required'=>false,'enabled'=>true,'placeholder'=>'箱序列号','col_width'=>3,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'箱序列号'], ], 'delivery' => [ ['name'=>'customer_name','label'=>'客户名称','type'=>'select','source'=>'customers','required'=>true,'enabled'=>true,'placeholder'=>'请选择客户','col_width'=>3,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'客户名称'], ['name'=>'finished_serial','label'=>'成品/箱序列号','type'=>'text','required'=>true,'enabled'=>true,'placeholder'=>'扫描序列号后按回车(自动识别型号)','col_width'=>4,'is_scan'=>true,'show_in_table'=>true,'table_label'=>'序列号'], ['name'=>'product_model','label'=>'产品型号','type'=>'hidden','required'=>false,'enabled'=>true,'placeholder'=>'','col_width'=>0,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'产品型号'], ], 'semi_finished' => [ ['name'=>'product_type','label'=>'类型','type'=>'hidden','required'=>true,'enabled'=>true,'placeholder'=>'半成品','col_width'=>0,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'类型','fixed_value'=>'半成品'], ['name'=>'product_model','label'=>'产品型号','type'=>'select','source'=>'product_models','required'=>true,'enabled'=>true,'placeholder'=>'请选择产品型号','col_width'=>3,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'产品型号'], ['name'=>'serial_no','label'=>'产品序列号','type'=>'text','required'=>true,'enabled'=>true,'placeholder'=>'扫描/输入后按回车','col_width'=>4,'is_scan'=>true,'show_in_table'=>true,'table_label'=>'产品序列号'], ], 'aging' => [ ['name'=>'product_type','label'=>'类型','type'=>'hidden','required'=>true,'enabled'=>true,'placeholder'=>'半成品','col_width'=>0,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'类型','fixed_value'=>'半成品'], ['name'=>'product_model','label'=>'产品型号','type'=>'select','source'=>'product_models','required'=>true,'enabled'=>true,'placeholder'=>'请选择产品型号','col_width'=>3,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'产品型号'], ['name'=>'serial_no','label'=>'产品序列号','type'=>'text','required'=>true,'enabled'=>true,'placeholder'=>'扫描/输入后按回车','col_width'=>4,'is_scan'=>true,'show_in_table'=>true,'table_label'=>'产品序列号'], ['name'=>'aging_duration','label'=>'老化时长(h)','type'=>'number','required'=>false,'enabled'=>true,'placeholder'=>'如:24','col_width'=>2,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'老化时长'], ['name'=>'aging_temp','label'=>'老化温度(℃)','type'=>'number','required'=>false,'enabled'=>true,'placeholder'=>'如:85','col_width'=>2,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'老化温度'], ['name'=>'aging_result','label'=>'老化结果','type'=>'select','source'=>'test_result_options','required'=>false,'enabled'=>true,'placeholder'=>'请选择','col_width'=>2,'is_scan'=>false,'show_in_table'=>true,'table_label'=>'老化结果'], ], ]; $fields = isset($configs[$stationType]) ? $configs[$stationType] : []; // ★ 关键修复:附加默认 _meta 信息 // 当 workstation.fields_config 为空时,FrontController::getStationFieldConfig() // 会 fallback 到 getDefaultFieldsConfig(),但之前没有附加 _meta, // 导致 model_type_filter、type_filter 等元信息丢失。 $meta = getDefaultMetaConfig($stationType); if (!empty($meta)) { $fields['_meta'] = $meta; } return $fields; } /** * 获取各工位类型的默认元信息配置 * 当数据库 fields_config 为空时作为兜底 */ function getDefaultMetaConfig($stationType) { $metas = [ 'inbound' => [ 'model_type_filter' => '半成品', 'type_filter' => 'inbound', ], 'pcb_test' => [ 'model_type_filter' => 'PCBA', ], 'battery' => [ 'model_type_filter' => '电池', ], 'assembly' => [ 'model_type_filter' => '成品', 'extra_data_sources' => ['shellColors' => true], ], 'finished_test' => [ 'model_type_filter' => '成品', ], 'warehouse' => [ 'model_type_filter' => '成品', ], 'delivery' => [], 'semi_finished' => [ 'model_type_filter' => '半成品', ], 'aging' => [ 'model_type_filter' => '半成品', ], ]; return $metas[$stationType] ?? []; } /** * 解析工位字段配置 * 返回数组,如果为空则使用默认配置 * * JSON 格式支持两种结构: * 1. 纯字段数组:[{name,label,...}, ...] (旧格式,兼容) * 2. 元信息 + 字段:{"_meta":{...}, "fields":[{...}, ...]} (新格式) * * _meta 支持的字段: * - model_type_filter: string 产品型号筛选类型(如 "成品"、"PCBA"、"电池") * - type_filter: string 类型列表筛选方式(如 "inbound"、"enabled") * - extra_data_sources: object 额外数据源配置 */ function parseFieldsConfig($stationType, $fieldsConfigJson = '') { if (!empty($fieldsConfigJson)) { $config = json_decode($fieldsConfigJson, true); if (is_array($config) && !empty($config)) { // 新格式:{"_meta":{...}, "fields":[...]} if (isset($config['_meta']) || isset($config['fields'])) { $result = $config['fields'] ?? []; $meta = $config['_meta'] ?? []; // 用默认元信息补全缺失的 key(如 model_type_filter) $defaultMeta = getDefaultMetaConfig($stationType); if (!empty($defaultMeta)) { foreach ($defaultMeta as $k => $v) { if (!array_key_exists($k, $meta)) { $meta[$k] = $v; } } } $result['_meta'] = $meta; return $result; } // 旧格式:纯字段数组 — 附加默认元信息 $meta = getDefaultMetaConfig($stationType); if (!empty($meta)) { $config['_meta'] = $meta; } return $config; } } // 回退到默认配置,附加默认元信息 $defaultFields = getDefaultFieldsConfig($stationType); $meta = getDefaultMetaConfig($stationType); if (!empty($meta)) { $defaultFields['_meta'] = $meta; } return $defaultFields; } /** * 渲染前端表单输入字段 (根据配置动态生成) * $fieldConfigs: 解析后的字段配置数组 * $dataSources: ['types' => [], 'models' => [], 'customers' => []] * $isReadonly: 操作人显示名称 */ function renderStationFields($fieldConfigs, $dataSources = [], $operatorName = '', $excludeFields = []) { $html = ''; $enabledFields = array_filter($fieldConfigs, function($f) { return !empty($f['enabled']); }); // 排除指定字段 if (!empty($excludeFields)) { $enabledFields = array_filter($enabledFields, function($f) use ($excludeFields) { return !in_array($f['name'], $excludeFields); }); } // 分组:扫描字段和其他字段 $scanFields = array_filter($enabledFields, function($f) { return !empty($f['is_scan']); }); $normalFields = array_filter($enabledFields, function($f) { return empty($f['is_scan']); }); // 输出所有非扫描字段 + 操作人(CSS Grid 自适应布局) $html .= '
'; foreach ($normalFields as $field) { $html .= '
'; $html .= renderSingleField($field, $dataSources); $html .= '
'; } // 操作人(固定显示) if ($operatorName) { $html .= '
'; $html .= ''; $html .= ''; $html .= '
'; } $html .= '
'; // 扫描字段(序列号类)- 单独一行,宽度自适应 if (!empty($scanFields)) { $html .= '
'; foreach ($scanFields as $field) { $html .= '
'; $html .= renderSingleField($field, $dataSources, true); $html .= '
'; } $html .= '
'; } return $html; } /** * 渲染单个字段 */ function renderSingleField($field, $dataSources = [], $isScan = false) { $name = htmlspecialchars($field['name']); $label = htmlspecialchars($field['label']); $type = $field['type'] ?? 'text'; $required = !empty($field['required']) ? 'required' : ''; $placeholder = htmlspecialchars($field['placeholder'] ?? ''); $html = ''; // hidden 类型:只输出隐藏域,不显示标签 if ($type === 'hidden') { $fixedValue = htmlspecialchars($field['fixed_value'] ?? ''); $html = ''; return $html; } $html .= ''; if ($type === 'select') { $source = $field['source'] ?? ''; $cssClass = $isScan ? 'form-control scan-submit' : 'form-control sticky-save'; $html .= ''; } elseif ($type === 'number') { $cssClass = $isScan ? 'form-control scan-submit' : 'form-control sticky-save'; $html .= ''; } elseif ($type === 'textarea') { $html .= ''; } else { // text 类型 $cssClass = $isScan ? 'form-control scan-submit' : 'form-control sticky-save'; $autofocus = $isScan ? ' autofocus' : ''; $html .= ''; } return $html; } /** * 渲染记录表格表头 */ function renderRecordTableHeader($fieldConfigs) { $html = '序号'; $colIdx = 0; foreach ($fieldConfigs as $field) { if (!empty($field['enabled']) && !empty($field['show_in_table'])) { $colKey = htmlspecialchars($field['name']); $html .= '' . htmlspecialchars($field['table_label'] ?? $field['label']) . ''; $colIdx++; } } $html .= '操作人时间'; return $html; } /** * 渲染记录表格数据行 */ function renderRecordTableRow($record, $fieldConfigs, $specialFormats = []) { $html = ''; $html .= '' . $record['id'] . ''; foreach ($fieldConfigs as $field) { if (empty($field['enabled']) || empty($field['show_in_table'])) continue; $name = $field['name']; $val = $record[$name] ?? ''; $colKey = htmlspecialchars($name); if (isset($specialFormats[$name])) { $html .= '' . $specialFormats[$name]($val, $record) . ''; } else { $html .= '' . htmlspecialchars($val) . ''; } } $html .= '' . htmlspecialchars($record['operator'] ?? '') . ''; // 兼容各种时间列名:created_at, create_time, assembly_time, test_time, delivery_time, in_time $timeValue = $record['created_at'] ?? $record['create_time'] ?? $record['assembly_time'] ?? $record['test_time'] ?? $record['delivery_time'] ?? $record['in_time'] ?? ''; $html .= '' . htmlspecialchars($timeValue) . ''; $html .= ''; return $html; } /** * 渲染表格列选择器(下拉多选按钮) * @param string $pageKey 页面唯一标识,用于 localStorage 区分不同页面 * @param array $fieldConfigs 字段配置数组 * @return string HTML */ function renderTableColumnSelector($pageKey, $fieldConfigs) { $cols = [['key' => 'seq', 'label' => '序号']]; foreach ($fieldConfigs as $field) { if (!empty($field['enabled']) && !empty($field['show_in_table'])) { $cols[] = [ 'key' => $field['name'], 'label'=> $field['table_label'] ?? $field['label'], ]; } } $cols[] = ['key' => 'operator', 'label' => '操作人']; $cols[] = ['key' => 'time', 'label' => '时间']; $colsJson = json_encode($cols, JSON_UNESCAPED_UNICODE); $pageKeyJson = json_encode($pageKey); $html = ''; $html .= ''; $html .= ''; $html .= ''; // 内联初始化脚本(在元素渲染后立即执行) $html .= ''; return $html; } /** * 生成字段配置编辑UI的JSON字符串(供JS使用) */ function renderFieldConfigEditor($stationType, $fieldsConfigJson = '', $stationDefinition = null) { $rawConfig = parseFieldsConfig($stationType, $fieldsConfigJson); // 分离 _meta 和 fields $meta = $rawConfig['_meta'] ?? []; $fields = $rawConfig; unset($fields['_meta']); $fields = array_values($fields); $fieldsJson = json_encode($fields, JSON_UNESCAPED_UNICODE); $metaJson = json_encode($meta, JSON_UNESCAPED_UNICODE); $stationTypeJson = json_encode($stationType); // 工位定义元数据(用于数据表结构管控) $stationDef = $stationDefinition ?: []; $hasProductType = !empty($stationDef['has_product_type']); $hasProductModel = !empty($stationDef['has_product_model']); $filterEnabled = !empty($stationDef['filter_enabled']); $showTodayCount = !empty($stationDef['show_today_count']); $showTotalCount = !empty($stationDef['show_total_count']); $defId = $stationDef['id'] ?? 0; // 产品类型选项(用于型号筛选下拉) $productTypeOptions = json_encode(['', '半成品', '成品', 'PCBA', '电池', '外壳', '外壳颜色'], JSON_UNESCAPED_UNICODE); ob_start(); ?>

输入数据项配置

数据表结构管控 工位定义 ID:

记录可按产品类型筛选

记录可按产品型号筛选

前端显示类型/型号筛选联动
数据筛选设置
选择后产品型号下拉只显示该类型
控制"类型"下拉框显示哪些选项

组装工位使用,加载外壳颜色选项
序列号前缀规则(防错) 系统配置 → 序列号前缀规则 中管理前缀

为扫描字段(is_scan=true)指定对应的产品类型,提交时将自动验证序列号前缀是否正确。
选择「动态匹配」则根据提交时选择的 product_type 自动匹配前缀。

扫描字段 对应产品类型 预期前缀
提示:留空则自动从工位的 product_type 字段推断(如有 hidden 类型的 product_type 则使用其 fixed_value,否则动态匹配)
启用 字段名 显示标签 类型 数据源 必填 唯一 占位提示 列宽 扫码 表中 表头标签 操作
勾选「扫码」的字段会以扫码枪回车提交方式处理。勾选「表中」会在记录表格中显示该列。
function initScanChain() { var $form = $(''); if (!$form.length) return; // 找到表单内所有输入元素(按DOM顺序) function getInputsInOrder() { var inputs = []; $form.find('input:not([disabled]):not([type="hidden"]):not(:submit):not(:button), select:not([disabled]), textarea:not([disabled])').each(function() { var $el = $(this); if ($el.closest('#fieldConfigEditor').length) return; // 跳过配置编辑器 inputs.push(this); }); // 按tabindex排序(如果有的话),否则按DOM顺序 inputs.sort(function(a, b) { var ta = parseInt($(a).attr('tabindex')) || 0; var tb = parseInt($(b).attr('tabindex')) || 0; if (ta && tb) return ta - tb; if (ta) return -1; if (tb) return 1; return 0; }); return inputs; } // 检查是否所有扫描字段都已填值 function allScanFieldsFilled() { var allFilled = true; $form.find('.scan-submit').each(function() { if ($(this).val().trim() === '') allFilled = false; }); return allFilled; } function doSubmit() { $('').click(); $form.submit(); } // 回车事件:扫描字段 → 跳到下一个空位;最后一位 → 提交 $form.on('keypress', '.scan-submit', function(e) { if (e.which !== 13) return; e.preventDefault(); var $this = $(this); var val = $this.val().trim(); if (val === '') return; // 空值不处理 // 找到所有扫描字段 var scanInputs = []; $form.find('.scan-submit').each(function() { scanInputs.push(this); }); // 如果所有扫描字段都填满了 → 提交 if (allScanFieldsFilled()) { doSubmit(); return; } // 找到下一个空的扫描字段 var found = false; for (var i = 0; i < scanInputs.length; i++) { if (scanInputs[i] === this) { // 从当前之后找第一个空的 for (var j = 1; j < scanInputs.length; j++) { var idx = (i + j) % scanInputs.length; if ($(scanInputs[idx]).val().trim() === '') { $(scanInputs[idx]).focus().select(); found = true; break; } } break; } } if (!found) { // 没找到空的,提交 doSubmit(); } }); // 非扫描字段的回车 → 跳到下一个输入框 $form.on('keypress', 'input:not(.scan-submit):not([type="hidden"]):not(:submit):not(:button), select, textarea', function(e) { if (e.which !== 13) return; var $el = $(this); if ($el.closest('#fieldConfigEditor').length) return; e.preventDefault(); var inputs = getInputsInOrder(); var idx = inputs.indexOf(this); if (idx >= 0 && idx < inputs.length - 1) { $(inputs[idx + 1]).focus().select(); } }); // 提交后清空扫描字段(重新聚焦第一个) $form.on('submit', function() { setTimeout(function() { var $firstScan = $form.find('.scan-submit').first(); if ($firstScan.length) $firstScan.focus(); }, 100); }); } $(function() { initScanChain(); });