From 17dc5d434b7fd1128e9b8a710bc455c48e1e08c6 Mon Sep 17 00:00:00 2001 From: nanguaboss <602995148@qq.com> Date: Sun, 9 Aug 2026 21:13:33 +0800 Subject: [PATCH] =?UTF-8?q?v1.0.29:=20=E7=A2=8E=E7=89=87=E5=A4=84=E7=90=86?= =?UTF-8?q?=E6=8C=89dp=E6=96=B9=E6=A1=882=E4=BF=AE=E8=AE=A2=20-=20?= =?UTF-8?q?=E5=AE=8C=E6=95=B4=E5=BA=A6=E6=94=B9=E4=B8=89=E5=B1=82=E7=8B=AC?= =?UTF-8?q?=E7=AB=8B=E5=88=A4=E5=AE=9A(=E6=A0=B8=E5=BF=83100%/=E9=87=8D?= =?UTF-8?q?=E8=A6=8160%/=E9=9D=9E=E5=BF=85=E8=A6=8140%,=E4=BB=BB=E4=B8=80?= =?UTF-8?q?=E4=B8=8D=E8=BE=BE=E6=A0=87=E5=8D=B3=E7=A2=8E=E7=89=87)+?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=88=86=E5=B1=82=E8=B0=83=E6=95=B4(persons?= =?UTF-8?q?=20email=E5=8D=87=E6=A0=B8=E5=BF=83,companies=E6=A0=B8=E5=BF=83?= =?UTF-8?q?=E6=89=A95=E5=AD=97=E6=AE=B5)+=E6=8C=87=E6=A0=87=E5=8D=A15?= =?UTF-8?q?=E5=BC=A0(=E6=96=B0=E5=A2=9E=E7=B4=AF=E8=AE=A1=E5=A4=84?= =?UTF-8?q?=E7=90=86,=E6=9C=AC=E6=9C=88/=E7=B4=AF=E8=AE=A1=3D=E7=A1=AC+?= =?UTF-8?q?=E8=BD=AF=E5=90=88=E8=AE=A1)+=E8=BD=AF=E7=A2=8E=E7=89=87?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E5=8A=A0=E5=90=84=E5=B1=82=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E5=BA=A6=E5=88=97+=E7=A1=AC=E7=A2=8E=E7=89=87=E5=AE=8C?= =?UTF-8?q?=E6=95=B4=E5=BA=A6=E6=94=B9=E5=B7=B2=E5=A1=AB/=E6=80=BB?= =?UTF-8?q?=E5=8F=AF=E5=A1=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/common/completeness.php | 155 +++++++++++++++++++------------- api/fragment/hard_common.php | 42 +++------ api/fragment/hard_detail.php | 1 - api/fragment/hard_list.php | 1 - api/fragment/soft_detail.php | 17 ++-- api/fragment/soft_list.php | 44 +++++---- api/fragment/soft_update.php | 6 +- api/fragment/stats_overview.php | 69 +++++++++----- static/css/style.css | 2 +- static/js/config.js | 2 +- static/js/fragment.js | 28 +++--- 11 files changed, 216 insertions(+), 151 deletions(-) diff --git a/api/common/completeness.php b/api/common/completeness.php index d5474bf..cc41eee 100644 --- a/api/common/completeness.php +++ b/api/common/completeness.php @@ -1,46 +1,49 @@ 层级),threshold 为完整度阈值 */ +/** 四表字段分层定义(key => 所属层;virtual 为关联虚拟字段) */ const COMPLETENESS_LAYERS = [ 'persons' => [ - 'core' => ['full_name'], - 'important' => ['work_location'], - 'supplementary' => ['gender', 'nationality', 'education', 'graduated_from', 'hometown', 'id_type', 'id_number'], - 'virtual' => ['phone' => 'core', 'email' => 'important'], // 关联 social_accounts.platform - 'threshold' => 65, + 'core' => ['full_name'], + 'important' => ['work_location'], + 'optional' => ['gender', 'nationality', 'education', 'graduated_from', 'hometown', 'id_type', 'id_number'], + 'virtual' => ['phone' => 'core', 'email' => 'core'], // 关联 social_accounts.platform ], 'companies' => [ - 'core' => ['display_name', 'industry'], - 'important' => ['address', 'business_role', 'country'], - 'supplementary' => ['name_zh', 'name_en', 'registration_number', 'legal_form', 'legal_representative', - 'business_scope', 'established_date', 'registered_capital', 'industry_subdivision', - 'latest_employee_count', 'latest_annual_revenue', 'is_listed', 'stock_code', 'website'], - 'virtual' => [], - 'threshold' => 65, + 'core' => ['display_name', 'name_zh', 'name_en', 'business_role', 'industry'], + 'important' => ['address', 'country'], + 'optional' => ['registration_number', 'legal_form', 'legal_representative', 'business_scope', + 'established_date', 'registered_capital', 'industry_subdivision', + 'latest_employee_count', 'latest_annual_revenue', 'is_listed', 'stock_code', 'website'], + 'virtual' => [], ], 'social_accounts' => [ - 'core' => ['platform', 'account_id'], - 'important' => ['is_primary'], - 'supplementary' => ['profile_url', 'remark'], - 'virtual' => [], - 'threshold' => 60, + 'core' => ['platform', 'account_id'], + 'important' => ['is_primary'], + 'optional' => ['profile_url', 'remark'], + 'virtual' => [], ], 'media' => [ - 'core' => ['social_account_id', 'account_level'], - 'important' => ['follower_count', 'content_categories'], - 'supplementary' => ['certification_type', 'avg_read_count', 'special_requirements', 'media_remark'], - 'virtual' => [], - 'threshold' => 55, + 'core' => ['social_account_id', 'account_level'], + 'important' => ['follower_count', 'content_categories'], + 'optional' => ['avg_read_count', 'certification_type', 'special_requirements', 'media_remark'], + 'virtual' => [], ], ]; +/** 各层达标门槛(%) */ +const COMPLETENESS_THRESHOLDS = ['core' => 100, 'important' => 60, 'optional' => 40]; + /** 字段中文名(前端展示缺失字段用) */ const COMPLETENESS_LABELS = [ 'full_name' => '姓名', 'phone' => '手机', 'email' => '邮箱', 'work_location' => '工作所在地', @@ -65,54 +68,85 @@ function completenessIsMissing($v) } /** - * 根据分层定义计算完整度 + * 三层完整度计算 * @param string $table persons|companies|social_accounts|media * @param array $row 该表记录(persons 需已注入虚拟字段 phone/email) - * @return array { score:int, missing_core:[], missing_important:[], missing_supp:[], level:string } + * @return array { + * layers: { core:{filled,total,rate,passed}, important:{...}, optional:{...} }, + * overall: float, overall_passed: bool, + * missing: { core:[], important:[], optional:[] }, + * level: core|important|optional|complete + * } */ function completenessCalc($table, $row) { $layers = COMPLETENESS_LAYERS[$table] ?? null; + $default = [ + 'layers' => ['core' => ['filled' => 0, 'total' => 0, 'rate' => 100, 'passed' => true]], + 'overall' => 100, 'overall_passed' => true, + 'missing' => ['core' => [], 'important' => [], 'optional' => []], + 'level' => 'complete', + ]; if (!$layers) { - return ['score' => 100, 'missing_core' => [], 'missing_important' => [], 'missing_supp' => [], 'level' => 'complete']; + return $default; } - $missing = ['core' => [], 'important' => [], 'supplementary' => []]; - foreach (['core' => 'core', 'important' => 'important', 'supplementary' => 'supplementary'] as $layer => $bucket) { + $result = []; + $missing = ['core' => [], 'important' => [], 'optional' => []]; + $totalAll = 0; + $filledAll = 0; + + foreach (['core', 'important', 'optional'] as $layer) { + $total = count($layers[$layer]); + $filled = 0; foreach ($layers[$layer] as $field) { if (completenessIsMissing($row[$field] ?? null)) { - $missing[$bucket][] = $field; + $missing[$layer][] = $field; + } else { + $filled++; } } - } - // 虚拟字段(如人员 phone/email)按配置的层级计入 - foreach (($layers['virtual'] ?? []) as $field => $layer) { - if (completenessIsMissing($row[$field] ?? null)) { - $missing[$layer][] = $field; + // 虚拟字段(persons phone/email)按配置层计入 + foreach (($layers['virtual'] ?? []) as $vf => $vLayer) { + if ($vLayer !== $layer) { + continue; + } + $total++; + if (completenessIsMissing($row[$vf] ?? null)) { + $missing[$layer][] = $vf; + } else { + $filled++; + } } + $rate = $total > 0 ? round($filled / $total * 100, 1) : 100; + $result[$layer] = [ + 'filled' => $filled, + 'total' => $total, + 'rate' => $rate, + 'passed' => $rate >= COMPLETENESS_THRESHOLDS[$layer], + ]; + $totalAll += $total; + $filledAll += $filled; } - $score = 100 - - count($missing['core']) * 20 - - count($missing['important']) * 8 - - count($missing['supplementary']) * 2; - $score = max(0, $score); + $overall = $totalAll > 0 ? round($filledAll / $totalAll * 100, 1) : 100; + $overallPassed = $result['core']['passed'] && $result['important']['passed'] && $result['optional']['passed']; $level = 'complete'; - if (!empty($missing['core'])) { + if (!$result['core']['passed']) { $level = 'core'; // 缺核心(红) - } elseif (!empty($missing['important'])) { + } elseif (!$result['important']['passed']) { $level = 'important'; // 缺重要(橙) - } elseif (!empty($missing['supplementary'])) { - $level = 'supplementary'; // 待补充(蓝) + } elseif (!$result['optional']['passed']) { + $level = 'optional'; // 待补充(蓝) } return [ - 'score' => $score, - 'missing_core' => $missing['core'], - 'missing_important' => $missing['important'], - 'missing_supp' => $missing['supplementary'], - 'level' => $level, + 'layers' => $result, + 'overall' => $overall, + 'overall_passed' => $overallPassed, + 'missing' => $missing, + 'level' => $level, ]; } @@ -138,7 +172,7 @@ function personWithVirtualFields(PDO $pdo, array $row) * @param PDO $pdo * @param string $table persons|companies|social_accounts|media * @param int $id - * @return array { row, score, missing_core, missing_important, missing_supp, level, threshold } + * @return array|null { row, layers, overall, overall_passed, missing, level, is_complete } */ function completenessInfo(PDO $pdo, $table, $id) { @@ -176,19 +210,18 @@ function completenessInfo(PDO $pdo, $table, $id) } $calc = completenessCalc($table, $row); return [ - 'row' => $row, - 'score' => $calc['score'], - 'missing_core' => $calc['missing_core'], - 'missing_important' => $calc['missing_important'], - 'missing_supp' => $calc['missing_supp'], - 'level' => $calc['level'], - 'threshold' => COMPLETENESS_LAYERS[$table]['threshold'] ?? 65, - 'is_complete' => $calc['score'] >= (COMPLETENESS_LAYERS[$table]['threshold'] ?? 65), + 'row' => $row, + 'layers' => $calc['layers'], + 'overall' => $calc['overall'], + 'overall_passed' => $calc['overall_passed'], + 'missing' => $calc['missing'], + 'level' => $calc['level'], + 'is_complete' => $calc['overall_passed'], ]; } /** - * 更新某条记录的 is_incomplete 标记(完整度低于阈值 → 1) + * 更新某条记录的 is_incomplete 标记(任一楼层不达标 → 1) * @param PDO $pdo * @param string $table persons|companies|social_accounts|media * @param int $id diff --git a/api/fragment/hard_common.php b/api/fragment/hard_common.php index a00033d..78e29f3 100644 --- a/api/fragment/hard_common.php +++ b/api/fragment/hard_common.php @@ -1,45 +1,31 @@ ['core' => ['company_name', 'contact_phone'], 'important' => ['contact_email'], 'supplementary' => ['person_name', 'description', 'target_product_category']], - 'person' => ['core' => ['person_name', 'contact_phone'], 'important' => ['contact_email'], 'supplementary' => ['company_name', 'description', 'target_product_category']], - 'product' => ['core' => ['company_name', 'target_product_category'], 'important' => ['contact_phone', 'contact_email'], 'supplementary' => ['person_name', 'description']], - 'need' => ['core' => ['company_name', 'target_product_category'], 'important' => ['contact_phone', 'contact_email'], 'supplementary' => ['person_name', 'description']], - 'mixed' => ['core' => ['company_name', 'person_name'], 'important' => ['contact_phone', 'contact_email'], 'supplementary' => ['description', 'target_product_category']], -]; +/** 硬碎片可统计字段(公司名/联系人/电话/邮箱/品类/描述) */ +const HARD_FIELDS = ['company_name', 'person_name', 'contact_phone', 'contact_email', 'target_product_category', 'description']; const HARD_LABELS = [ 'company_name' => '公司名称', 'person_name' => '联系人', 'contact_phone' => '电话', 'contact_email' => '邮箱', 'target_product_category' => '产品品类', 'description' => '描述', ]; -/** 计算硬碎片完整度与缺失层级 */ +/** 计算硬碎片完整度:已填字段数 ÷ 总可填字段数 × 100% */ function hardCompleteness($row) { - $layers = HARD_LAYERS[$row['source_type']] ?? HARD_LAYERS['mixed']; - $missing = ['core' => [], 'important' => [], 'supplementary' => []]; - foreach (['core', 'important', 'supplementary'] as $layer) { - foreach ($layers[$layer] as $field) { - if ($row[$field] === null || trim((string)$row[$field]) === '') { - $missing[$layer][] = $field; - } + $filled = 0; + $missing = []; + foreach (HARD_FIELDS as $field) { + if ($row[$field] !== null && trim((string)$row[$field]) !== '') { + $filled++; + } else { + $missing[] = $field; } } - $score = 100 - count($missing['core']) * 20 - count($missing['important']) * 8 - count($missing['supplementary']) * 2; - $score = max(0, $score); - $level = 'complete'; - if (!empty($missing['core'])) { - $level = 'core'; - } elseif (!empty($missing['important'])) { - $level = 'important'; - } elseif (!empty($missing['supplementary'])) { - $level = 'supplementary'; - } - return ['score' => $score, 'level' => $level, 'missing' => $missing]; + $total = count(HARD_FIELDS); + $score = $total > 0 ? (int)round($filled / $total * 100) : 0; + return ['score' => $score, 'missing' => $missing]; } /** 内容摘要:拼接已有关键信息 */ diff --git a/api/fragment/hard_detail.php b/api/fragment/hard_detail.php index 5b205de..afca75d 100644 --- a/api/fragment/hard_detail.php +++ b/api/fragment/hard_detail.php @@ -24,7 +24,6 @@ if (!$row) { $calc = hardCompleteness($row); $row['completeness'] = $calc['score']; -$row['level'] = $calc['level']; $row['missing'] = $calc['missing']; $row['summary'] = hardSummary($row); $row['source_type_label'] = ['company' => '企业', 'person' => '人员', 'product' => '产品', 'need' => '需求', 'mixed' => '混合'][$row['source_type']] ?? $row['source_type']; diff --git a/api/fragment/hard_list.php b/api/fragment/hard_list.php index 08b9781..4d5c2d2 100644 --- a/api/fragment/hard_list.php +++ b/api/fragment/hard_list.php @@ -52,7 +52,6 @@ $list = []; foreach ($rows as $r) { $calc = hardCompleteness($r); $r['completeness'] = $calc['score']; - $r['level'] = $calc['level']; $r['missing'] = $calc['missing']; $r['summary'] = hardSummary($r); $r['source_type_label'] = ['company' => '企业', 'person' => '人员', 'product' => '产品', 'need' => '需求', 'mixed' => '混合'][$r['source_type']] ?? $r['source_type']; diff --git a/api/fragment/soft_detail.php b/api/fragment/soft_detail.php index a42d760..271decc 100644 --- a/api/fragment/soft_detail.php +++ b/api/fragment/soft_detail.php @@ -1,7 +1,8 @@ $f, 'label' => COMPLETENESS_LABELS[$f] ?? $f]; }; + Response::success([ 'table' => $table, 'table_label' => $tableLabels[$table], 'id' => $id, 'name' => $name ?: ('#' . $id), - 'score' => $info['score'], + 'overall' => $info['overall'], 'level' => $info['level'], - 'threshold' => $info['threshold'], - 'missing_core' => array_map(function ($f) { return ['key' => $f, 'label' => COMPLETENESS_LABELS[$f] ?? $f]; }, $info['missing_core']), - 'missing_important' => array_map(function ($f) { return ['key' => $f, 'label' => COMPLETENESS_LABELS[$f] ?? $f]; }, $info['missing_important']), - 'missing_supp' => array_map(function ($f) { return ['key' => $f, 'label' => COMPLETENESS_LABELS[$f] ?? $f]; }, $info['missing_supp']), + 'layers' => $info['layers'], + 'missing_core' => array_map($label, $info['missing']['core']), + 'missing_important' => array_map($label, $info['missing']['important']), + 'missing_optional' => array_map($label, $info['missing']['optional']), + 'missing_labels' => array_map(function ($f) { return COMPLETENESS_LABELS[$f] ?? $f; }, + array_merge($info['missing']['core'], $info['missing']['important'], $info['missing']['optional'])), 'row' => $info['row'], ]); diff --git a/api/fragment/soft_list.php b/api/fragment/soft_list.php index 944d445..13baad0 100644 --- a/api/fragment/soft_list.php +++ b/api/fragment/soft_list.php @@ -1,8 +1,10 @@ ['label' => '人员', 'name' => 'full_name'], - 'companies' => ['label' => '企业', 'name' => 'display_name'], - 'social_accounts' => ['label' => '联系方式', 'name' => 'account_id'], - 'media' => ['label' => '媒体', 'name' => null], // media 需 JOIN social_accounts 取名 + 'persons' => ['label' => '人员', 'name' => 'full_name'], + 'companies' => ['label' => '企业', 'name' => 'display_name'], + 'social_accounts' => ['label' => '联系方式', 'name' => 'account_id'], + 'media' => ['label' => '媒体', 'name' => null], // media 需 JOIN social_accounts 取名 ]; $pdo = DB::getInstance()->getPdo(); @@ -32,7 +34,7 @@ foreach ($tableMap as $t => $cfg) { } if ($t === 'media') { $rows = $pdo->query( - "SELECT m.social_account_id AS id, sa.account_id AS name, m.updated_at AS created_at + "SELECT m.social_account_id AS id, sa.account_id AS name, sa.created_at AS created_at FROM media_commercial_attributes m LEFT JOIN social_accounts sa ON sa.id = m.social_account_id WHERE m.is_incomplete = 1" @@ -48,24 +50,30 @@ foreach ($tableMap as $t => $cfg) { if ($level !== '' && $info['level'] !== $level) { continue; } - $missingFields = array_merge($info['missing_core'], $info['missing_important'], $info['missing_supp']); + // 缺失字段按优先级:核心 → 重要 → 非必要 + $missingFields = array_merge($info['missing']['core'], $info['missing']['important'], $info['missing']['optional']); $all[] = [ - 'table' => $t, - 'table_label' => $cfg['label'], - 'id' => (int)$r['id'], - 'name' => $r['name'] ?: ('#' . $r['id']), - 'score' => $info['score'], - 'level' => $info['level'], + 'table' => $t, + 'table_label' => $cfg['label'], + 'id' => (int)$r['id'], + 'name' => $r['name'] ?: ('#' . $r['id']), + 'overall' => $info['overall'], + 'level' => $info['level'], + 'layer_rates' => [ + 'core' => $info['layers']['core']['rate'], + 'important' => $info['layers']['important']['rate'], + 'optional' => $info['layers']['optional']['rate'], + ], 'missing_fields' => array_map(function ($f) { return COMPLETENESS_LABELS[$f] ?? $f; }, $missingFields), - 'created_at' => $r['created_at'] ?? null, + 'created_at' => $r['created_at'] ?? null, ]; } } -// 按完整度升序(最残缺的排前面),再按时间倒序 +// 排序:整体完整度升序(最残缺在前),同分按时间倒序 usort($all, function ($a, $b) { - if ($a['score'] !== $b['score']) { - return $a['score'] <=> $b['score']; + if ($a['overall'] !== $b['overall']) { + return $a['overall'] <=> $b['overall']; } return strcmp($b['created_at'] ?? '', $a['created_at'] ?? ''); }); diff --git a/api/fragment/soft_update.php b/api/fragment/soft_update.php index 90bc80e..0d64dbe 100644 --- a/api/fragment/soft_update.php +++ b/api/fragment/soft_update.php @@ -135,10 +135,10 @@ if (!$info) { Response::error('记录不存在'); } -logCurrent('complete', 'fragment', $table, $id, ['fields' => array_keys($fields), 'score' => $info['score']]); +logCurrent('complete', 'fragment', $table, $id, ['fields' => array_keys($fields), 'overall' => $info['overall']]); Response::success([ - 'score' => $info['score'], + 'overall' => $info['overall'], 'level' => $info['level'], 'is_complete' => $info['is_complete'], -], $info['is_complete'] ? '补全完成' : '已更新,完整度 ' . $info['score'] . '%'); +], $info['is_complete'] ? '补全完成' : '已更新,整体完整度 ' . $info['overall'] . '%'); diff --git a/api/fragment/stats_overview.php b/api/fragment/stats_overview.php index 07a3db6..989ef0c 100644 --- a/api/fragment/stats_overview.php +++ b/api/fragment/stats_overview.php @@ -1,8 +1,13 @@ getPdo(); +$monthStart = "DATE_FORMAT(CURDATE(), '%Y-%m-01')"; -// 硬碎片:preliminary_data 中未终结(未转换/未废弃)的记录 +// 硬碎片待处理数 $hardTotal = (int)$pdo->query( "SELECT COUNT(*) FROM preliminary_data WHERE is_active = 1 AND status NOT IN ('已转换','已废弃')" )->fetchColumn(); -// 软碎片:四张主表 is_incomplete=1 之和 +// 软碎片待处理数(四主表 is_incomplete=1 之和) $softTotal = 0; foreach (['companies', 'persons', 'social_accounts', 'media_commercial_attributes'] as $t) { $softTotal += (int)$pdo->query("SELECT COUNT(*) FROM `$t` WHERE is_incomplete = 1")->fetchColumn(); } -// 本月新增(硬碎片按 created_at) -$monthNew = (int)$pdo->query( - "SELECT COUNT(*) FROM preliminary_data WHERE is_active = 1 AND created_at >= DATE_FORMAT(CURDATE(), '%Y-%m-01')" +// 本月新增硬碎片(created_at 在本月) +$hardMonthNew = (int)$pdo->query( + "SELECT COUNT(*) FROM preliminary_data WHERE is_active = 1 AND created_at >= $monthStart" )->fetchColumn(); -// 本月处理(已转换/已废弃且 processed_at 在本月) -$monthProcessed = (int)$pdo->query( +// 本月新增软碎片(is_incomplete=1 且本月创建;media 的创建时间取关联 social_accounts) +$softMonthNew = 0; +foreach (['companies', 'persons', 'social_accounts'] as $t) { + $softMonthNew += (int)$pdo->query( + "SELECT COUNT(*) FROM `$t` WHERE is_incomplete = 1 AND created_at >= $monthStart" + )->fetchColumn(); +} +$softMonthNew += (int)$pdo->query( + "SELECT COUNT(*) FROM media_commercial_attributes m + WHERE m.is_incomplete = 1 + AND EXISTS (SELECT 1 FROM social_accounts sa WHERE sa.id = m.social_account_id AND sa.created_at >= $monthStart)" +)->fetchColumn(); + +// 本月已转换硬碎片 +$hardConvertedMonth = (int)$pdo->query( "SELECT COUNT(*) FROM preliminary_data - WHERE is_active = 1 AND status IN ('已转换','已废弃') - AND processed_at >= DATE_FORMAT(CURDATE(), '%Y-%m-01')" + WHERE is_active = 1 AND status = '已转换' AND processed_at >= $monthStart" )->fetchColumn(); -// 待处理碎片 -$pending = (int)$pdo->query( - "SELECT COUNT(*) FROM preliminary_data WHERE is_active = 1 AND status = '待处理'" +// 本月已补全软碎片(fragment 模块 complete 日志) +$softCompletedMonth = (int)$pdo->query( + "SELECT COUNT(*) FROM system_logs WHERE action = 'complete' AND module = 'fragment' AND created_at >= $monthStart" +)->fetchColumn(); + +// 历史累计已转换硬碎片 +$hardConvertedTotal = (int)$pdo->query( + "SELECT COUNT(*) FROM preliminary_data WHERE is_active = 1 AND status = '已转换'" +)->fetchColumn(); + +// 历史累计已补全软碎片 +$softCompletedTotal = (int)$pdo->query( + "SELECT COUNT(*) FROM system_logs WHERE action = 'complete' AND module = 'fragment'" )->fetchColumn(); Response::success([ - 'hard_total' => $hardTotal, - 'soft_total' => $softTotal, - 'month_new' => $monthNew, - 'month_processed' => $monthProcessed, - 'pending' => $pending, + 'hard_total' => $hardTotal, + 'soft_total' => $softTotal, + 'month_new' => $hardMonthNew + $softMonthNew, + 'month_processed' => $hardConvertedMonth + $softCompletedMonth, + 'cumulative' => $hardConvertedTotal + $softCompletedTotal, + 'pending' => $hardTotal + $softTotal, ]); diff --git a/static/css/style.css b/static/css/style.css index abd5967..7d99be0 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -472,7 +472,7 @@ table.grid .ops a:hover { text-decoration: underline; } /* ---------- 指标卡 ---------- */ .stat-cards { display: grid; - grid-template-columns: repeat(4, 1fr); + grid-template-columns: repeat(5, 1fr); gap: 14px; margin-bottom: 16px; } diff --git a/static/js/config.js b/static/js/config.js index 4562649..b18ca50 100644 --- a/static/js/config.js +++ b/static/js/config.js @@ -5,7 +5,7 @@ var BASE_URL = '/api/'; var PAGE_SIZE = 20; /** 系统版本号(logo旁展示):修改代码后运行 tools/bump_version.php 自动递增 */ -var APP_VERSION = 'v1.0.28'; +var APP_VERSION = 'v1.0.29'; /** 页脚版权/备案信息(在 config.js 中修改) */ var FOOTER_TEXT = '© 2026 SuperLink 管理系统 版权所有 | 备案号:请替换为真实备案号'; diff --git a/static/js/fragment.js b/static/js/fragment.js index 3cb059a..46a5c9f 100644 --- a/static/js/fragment.js +++ b/static/js/fragment.js @@ -22,7 +22,7 @@ $(function () { function levelTagHtml(level) { if (level === 'core') return '缺核心'; if (level === 'important') return '缺重要'; - if (level === 'supplementary') return '待补充'; + if (level === 'optional') return '待补充'; return '完整'; } @@ -75,13 +75,13 @@ $(function () { ' ' + ' ' + + ' ' + ' ' + ' ' + ' ' + ' ' + '
| 完整度 | 缺失层级 | 名称 | 缺失字段 | 所属表 | 录入时间 | 操作 | |
|---|---|---|---|---|---|---|---|
| 完整度 | 缺失层级 | 名称 | 各层完整度 | 缺失字段 | 所属表 | 录入时间 | 操作 |