v1.0.50: 碎片处理6项修改 - 1)硬数据板块改名为「硬数据碎片新增与处理」;2)硬数据彻底移除「状态」字段(查看弹窗/数据表status列/列表接口全部删除,已转换以converted_id非空标识,preliminary旧页同步清理);3)硬数据操作列「继续补全」改「手动补全」+新增「AI补全」占位;4)软数据操作列「补全」改「手动补全」+新增「AI补全」占位;5)硬数据「废弃」改为从preliminary_data彻底硬删除;6)人员性别枚举收敛为男/女/未知(迁移归并其他/保密)

This commit is contained in:
nanguaboss
2026-08-11 00:09:33 +08:00
parent 9f06ee1cd9
commit 69fd4246c2
15 changed files with 92 additions and 85 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ $sqlMetrics = [
'company_new' => "SELECT COUNT(*) AS c FROM companies WHERE is_active = 1 AND DATE(created_at) = CURDATE() - INTERVAL 1 DAY", 'company_new' => "SELECT COUNT(*) AS c FROM companies WHERE is_active = 1 AND DATE(created_at) = CURDATE() - INTERVAL 1 DAY",
'person_new' => "SELECT COUNT(*) AS c FROM persons WHERE is_active = 1 AND DATE(created_at) = CURDATE() - INTERVAL 1 DAY", 'person_new' => "SELECT COUNT(*) AS c FROM persons WHERE is_active = 1 AND DATE(created_at) = CURDATE() - INTERVAL 1 DAY",
'preliminary' => "SELECT 'preliminary' => "SELECT
(SELECT COUNT(*) FROM preliminary_data WHERE is_active = 1 AND status NOT IN ('已转换','已废弃')) (SELECT COUNT(*) FROM preliminary_data WHERE is_active = 1 AND converted_id IS NULL)
+ (SELECT COUNT(*) FROM companies WHERE is_incomplete = 1) + (SELECT COUNT(*) FROM companies WHERE is_incomplete = 1)
+ (SELECT COUNT(*) FROM persons WHERE is_incomplete = 1) + (SELECT COUNT(*) FROM persons WHERE is_incomplete = 1)
+ (SELECT COUNT(*) FROM social_accounts WHERE is_incomplete = 1) + (SELECT COUNT(*) FROM social_accounts WHERE is_incomplete = 1)
+3 -3
View File
@@ -4,7 +4,7 @@
* 入参:source_type(company|person|product|need|mixed,必填)/ * 入参:source_type(company|person|product|need|mixed,必填)/
* company_name / person_name / contact_phone / contact_email / * company_name / person_name / contact_phone / contact_email /
* target_product_category / description / source_channel * target_product_category / description / source_channel
* 写入 preliminary_data,status=待处理,recorded_by=当前登录用户 * 写入 preliminary_data,recorded_by=当前登录用户(v1.0.50 起无 status 字段)
*/ */
require_once __DIR__ . '/../common/db.php'; require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php'; require_once __DIR__ . '/../common/response.php';
@@ -85,8 +85,8 @@ $pdo->prepare(
"INSERT INTO preliminary_data "INSERT INTO preliminary_data
(source_type, company_name, industry, registration_number, business_role, country, address, (source_type, company_name, industry, registration_number, business_role, country, address,
person_name, contact_phone, contact_email, work_location, id_number, platform, account_id, profile_url, person_name, contact_phone, contact_email, work_location, id_number, platform, account_id, profile_url,
source_channel, recorded_by, status) source_channel, recorded_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '待处理')" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
)->execute([ )->execute([
$sourceType, $sourceType,
$companyName !== '' ? $companyName : null, $companyName !== '' ? $companyName : null,
+11 -11
View File
@@ -31,8 +31,8 @@ $frag = $stmt->fetch();
if (!$frag) { if (!$frag) {
Response::error('碎片不存在'); Response::error('碎片不存在');
} }
if (in_array($frag['status'], ['已转换', '已废弃'], true)) { if ($frag['converted_id']) {
Response::error('该碎片已处理(' . $frag['status'] . ')'); Response::error('该碎片已处理(已转换)');
} }
$companyName = trim($_POST['company_name'] ?? ($frag['company_name'] ?? '')); $companyName = trim($_POST['company_name'] ?? ($frag['company_name'] ?? ''));
@@ -65,31 +65,31 @@ if ($profileUrl !== '' && !preg_match('/^https?:\/\//i', $profileUrl)) {
switch ($targetType) { switch ($targetType) {
case 'company': case 'company':
if ($companyName === '') { if ($companyName === '') {
Response::error('企业类型转软碎片:企业名称为必填项,请先「继续补全」', 400); Response::error('企业类型转软碎片:企业名称为必填项,请先「手动补全」', 400);
} }
if ($industry === '') { if ($industry === '') {
Response::error('企业类型转软碎片:行业为必填项,请先「继续补全」填写行业', 400); Response::error('企业类型转软碎片:行业为必填项,请先「手动补全」填写行业', 400);
} }
if ($registrationNum === '') { if ($registrationNum === '') {
Response::error('企业类型转软碎片:注册号为必填项,请先「继续补全」填写注册号', 400); Response::error('企业类型转软碎片:注册号为必填项,请先「手动补全」填写注册号', 400);
} }
break; break;
case 'person': case 'person':
if ($personName === '') { if ($personName === '') {
Response::error('人员类型转软碎片:姓名为必填项,请先「继续补全」', 400); Response::error('人员类型转软碎片:姓名为必填项,请先「手动补全」', 400);
} }
if ($phone === '' && $email === '') { if ($phone === '' && $email === '') {
Response::error('人员类型转软碎片:手机号码、邮箱至少填写一项,请先「继续补全」', 400); Response::error('人员类型转软碎片:手机号码、邮箱至少填写一项,请先「手动补全」', 400);
} }
break; break;
case 'product': case 'product':
if ($companyName === '') { if ($companyName === '') {
Response::error('产品类型转软碎片:公司名称为必填项,请先「继续补全」', 400); Response::error('产品类型转软碎片:公司名称为必填项,请先「手动补全」', 400);
} }
break; break;
case 'need': case 'need':
if ($companyName === '') { if ($companyName === '') {
Response::error('需求类型转软碎片:公司名称为必填项,请先「继续补全」', 400); Response::error('需求类型转软碎片:公司名称为必填项,请先「手动补全」', 400);
} }
break; break;
} }
@@ -215,9 +215,9 @@ switch ($targetType) {
break; break;
} }
// 标记碎片已转换 // 标记碎片已转换(v1.0.50 起不再写 status 字段,以 converted_id 非空标识已转换)
$pdo->prepare( $pdo->prepare(
"UPDATE preliminary_data SET status = '已转换', converted_type = ?, converted_id = ?, processed_at = NOW() WHERE id = ?" "UPDATE preliminary_data SET converted_type = ?, converted_id = ?, processed_at = NOW() WHERE id = ?"
)->execute([$targetType, $convertedId, $id]); )->execute([$targetType, $convertedId, $id]);
logCurrent('convert', 'fragment', 'preliminary_data', $id, [ logCurrent('convert', 'fragment', 'preliminary_data', $id, [
+7 -5
View File
@@ -2,6 +2,7 @@
/** /**
* 硬碎片废弃接口 POST /api/fragment/hard_discard.php * 硬碎片废弃接口 POST /api/fragment/hard_discard.php
* 入参:id * 入参:id
* v1.0.50:废弃改为从 preliminary_data 彻底硬删除(不再保留记录)
*/ */
require_once __DIR__ . '/../common/db.php'; require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php'; require_once __DIR__ . '/../common/response.php';
@@ -17,17 +18,18 @@ if ($id <= 0) {
} }
$pdo = DB::getInstance()->getPdo(); $pdo = DB::getInstance()->getPdo();
$stmt = $pdo->prepare("SELECT id, status FROM preliminary_data WHERE id = ? AND is_active = 1"); $stmt = $pdo->prepare("SELECT id, converted_id FROM preliminary_data WHERE id = ? AND is_active = 1");
$stmt->execute([$id]); $stmt->execute([$id]);
$frag = $stmt->fetch(); $frag = $stmt->fetch();
if (!$frag) { if (!$frag) {
Response::error('碎片不存在'); Response::error('碎片不存在');
} }
if (in_array($frag['status'], ['已转换', '已废弃'], true)) { if ($frag['converted_id']) {
Response::error('该碎片已处理(' . $frag['status'] . ')'); Response::error('该碎片已转换,不可废弃');
} }
$pdo->prepare("UPDATE preliminary_data SET status = '已废弃', processed_at = NOW() WHERE id = ?")->execute([$id]); // 彻底硬删除(v1.0.50 起废弃不再保留记录)
$pdo->prepare("DELETE FROM preliminary_data WHERE id = ?")->execute([$id]);
logCurrent('discard', 'fragment', 'preliminary_data', $id, ['status' => '已废弃']); logCurrent('discard', 'fragment', 'preliminary_data', $id, ['action' => 'hard_delete']);
Response::success(null, '已废弃'); Response::success(null, '已废弃');
+4 -5
View File
@@ -1,8 +1,9 @@
<?php <?php
/** /**
* 硬碎片列表接口 GET /api/fragment/hard_list.php * 硬碎片列表接口 GET /api/fragment/hard_list.php
* 参数:page / limit / source_type / status / source_channel / keyword * 参数:page / limit / source_type / source_channel / keyword
* 返回:{ list, total, page, limit },每行含完整度 score、缺失层级、内容摘要 * 返回:{ list, total, page, limit },每行含完整度 score、缺失层级、内容摘要
* 说明:v1.0.50 起移除 status 字段(查看弹窗/数据表均不再展示状态),列表仅返回未转换(converted_id IS NULL)记录
*/ */
require_once __DIR__ . '/../common/db.php'; require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php'; require_once __DIR__ . '/../common/response.php';
@@ -13,18 +14,16 @@ checkPermission('preliminary');
[$page, $limit] = pageParams(); [$page, $limit] = pageParams();
$keyword = trim($_REQUEST['keyword'] ?? ''); $keyword = trim($_REQUEST['keyword'] ?? '');
$status = trim($_REQUEST['status'] ?? '');
$sourceType = trim($_REQUEST['source_type'] ?? ''); $sourceType = trim($_REQUEST['source_type'] ?? '');
$sourceChannel = trim($_REQUEST['source_channel'] ?? ''); $sourceChannel = trim($_REQUEST['source_channel'] ?? '');
$where = ["is_active = 1", "status NOT IN ('已转换','已废弃')"]; $where = ["is_active = 1", "converted_id IS NULL"];
$params = []; $params = [];
if ($keyword !== '') { if ($keyword !== '') {
$where[] = '(company_name LIKE ? OR person_name LIKE ? OR contact_phone LIKE ? OR contact_email LIKE ? OR description LIKE ?)'; $where[] = '(company_name LIKE ? OR person_name LIKE ? OR contact_phone LIKE ? OR contact_email LIKE ? OR description LIKE ?)';
$like = "%$keyword%"; $like = "%$keyword%";
array_push($params, $like, $like, $like, $like, $like); array_push($params, $like, $like, $like, $like, $like);
} }
if ($status !== '') { $where[] = 'status = ?'; $params[] = $status; }
if ($sourceType !== '') { $where[] = 'source_type = ?'; $params[] = $sourceType; } if ($sourceType !== '') { $where[] = 'source_type = ?'; $params[] = $sourceType; }
if ($sourceChannel !== '') { $where[] = 'source_channel = ?'; $params[] = $sourceChannel; } if ($sourceChannel !== '') { $where[] = 'source_channel = ?'; $params[] = $sourceChannel; }
$whereSql = implode(' AND ', $where); $whereSql = implode(' AND ', $where);
@@ -39,7 +38,7 @@ $offset = ($page - 1) * $limit;
$stmt = $pdo->prepare( $stmt = $pdo->prepare(
"SELECT id, source_type, company_name, industry, registration_number, business_role, country, address, "SELECT id, source_type, company_name, industry, registration_number, business_role, country, address,
person_name, contact_phone, contact_email, work_location, id_number, platform, account_id, profile_url, person_name, contact_phone, contact_email, work_location, id_number, platform, account_id, profile_url,
status, source_channel, recorded_by, source_channel, recorded_by,
follow_person, converted_type, converted_id, created_at, updated_at follow_person, converted_type, converted_id, created_at, updated_at
FROM preliminary_data FROM preliminary_data
WHERE $whereSql WHERE $whereSql
+2 -2
View File
@@ -26,8 +26,8 @@ $frag = $stmt->fetch();
if (!$frag) { if (!$frag) {
Response::error('碎片不存在'); Response::error('碎片不存在');
} }
if (in_array($frag['status'], ['已转换', '已废弃'], true)) { if ($frag['converted_id']) {
Response::error('该碎片已处理(' . $frag['status'] . ')'); Response::error('该碎片已处理(已转换)');
} }
$sourceType = trim($_POST['source_type'] ?? ($frag['source_type'] ?? '')); $sourceType = trim($_POST['source_type'] ?? ($frag['source_type'] ?? ''));
+6 -5
View File
@@ -2,12 +2,13 @@
/** /**
* 碎片处理中心 - 顶部概览接口 GET /api/fragment/stats_overview.php * 碎片处理中心 - 顶部概览接口 GET /api/fragment/stats_overview.php
* 指标: * 指标:
* - hard_total 硬碎片待处理数(preliminary_data 未终结) * - hard_total 硬碎片待处理数(preliminary_data 未转换)
* - soft_total 软碎片待处理数(四主表 is_incomplete=1) * - soft_total 软碎片待处理数(四主表 is_incomplete=1)
* - month_new 本月新增(硬碎片本月新增 + 软碎片本月新增) * - month_new 本月新增(硬碎片本月新增 + 软碎片本月新增)
* - month_processed 本月处理(本月已转换硬碎片 + 本月已补全软碎片) * - month_processed 本月处理(本月已转换硬碎片 + 本月已补全软碎片)
* - cumulative 累计处理(历史已转换硬碎片 + 历史已补全软碎片) * - cumulative 累计处理(历史已转换硬碎片 + 历史已补全软碎片)
* - pending 待处理碎片(硬待处理 + 软待处理) * - pending 待处理碎片(硬待处理 + 软待处理)
* 说明:v1.0.50 起 preliminary_data 无 status 字段,已转换以 converted_id 非空标识
*/ */
require_once __DIR__ . '/../common/db.php'; require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php'; require_once __DIR__ . '/../common/response.php';
@@ -18,9 +19,9 @@ checkPermission('preliminary');
$pdo = DB::getInstance()->getPdo(); $pdo = DB::getInstance()->getPdo();
$monthStart = "DATE_FORMAT(CURDATE(), '%Y-%m-01')"; $monthStart = "DATE_FORMAT(CURDATE(), '%Y-%m-01')";
// 硬碎片待处理数 // 硬碎片待处理数(未转换)
$hardTotal = (int)$pdo->query( $hardTotal = (int)$pdo->query(
"SELECT COUNT(*) FROM preliminary_data WHERE is_active = 1 AND status NOT IN ('已转换','已废弃')" "SELECT COUNT(*) FROM preliminary_data WHERE is_active = 1 AND converted_id IS NULL"
)->fetchColumn(); )->fetchColumn();
// 软碎片待处理数(四主表 is_incomplete=1 之和) // 软碎片待处理数(四主表 is_incomplete=1 之和)
@@ -50,7 +51,7 @@ $softMonthNew += (int)$pdo->query(
// 本月已转换硬碎片 // 本月已转换硬碎片
$hardConvertedMonth = (int)$pdo->query( $hardConvertedMonth = (int)$pdo->query(
"SELECT COUNT(*) FROM preliminary_data "SELECT COUNT(*) FROM preliminary_data
WHERE is_active = 1 AND status = '已转换' AND processed_at >= $monthStart" WHERE is_active = 1 AND converted_id IS NOT NULL AND processed_at >= $monthStart"
)->fetchColumn(); )->fetchColumn();
// 本月已补全软碎片(fragment 模块 complete 日志) // 本月已补全软碎片(fragment 模块 complete 日志)
@@ -60,7 +61,7 @@ $softCompletedMonth = (int)$pdo->query(
// 历史累计已转换硬碎片 // 历史累计已转换硬碎片
$hardConvertedTotal = (int)$pdo->query( $hardConvertedTotal = (int)$pdo->query(
"SELECT COUNT(*) FROM preliminary_data WHERE is_active = 1 AND status = '已转换'" "SELECT COUNT(*) FROM preliminary_data WHERE is_active = 1 AND converted_id IS NOT NULL"
)->fetchColumn(); )->fetchColumn();
// 历史累计已补全软碎片 // 历史累计已补全软碎片
+1 -1
View File
@@ -61,7 +61,7 @@ while (($row = fgetcsv($handle)) !== false) {
continue; continue;
} }
$insPerson->execute([ $insPerson->execute([
$unionId, $rec['full_name'], $rec['gender'] ?? '保密', $rec['nationality'] ?? null, $unionId, $rec['full_name'], $rec['gender'] ?? '未知', $rec['nationality'] ?? null,
$rec['id_type'] ?? null, $rec['id_number'] ?? null, $rec['education'] ?? null, $rec['id_type'] ?? null, $rec['id_number'] ?? null, $rec['education'] ?? null,
$rec['graduated_from'] ?? null, $rec['hometown'] ?? null, $rec['work_location'] ?? null, $rec['graduated_from'] ?? null, $rec['hometown'] ?? null, $rec['work_location'] ?? null,
$rec['source_channel'] ?? null, $rec['source_detail'] ?? null, $rec['source_channel'] ?? null, $rec['source_detail'] ?? null,
+2 -4
View File
@@ -1,7 +1,7 @@
<?php <?php
/** /**
* 碎片处理列表接口(待定逻辑,暂做基础列表) GET/POST /api/preliminary/list.php * 碎片处理列表接口(待定逻辑,暂做基础列表) GET/POST /api/preliminary/list.php
* 参数:page / limit / keyword / status / source_type / start_date / end_date * 参数:page / limit / keyword / source_type / start_date / end_date
*/ */
require_once __DIR__ . '/../common/db.php'; require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php'; require_once __DIR__ . '/../common/response.php';
@@ -11,7 +11,6 @@ checkPermission('preliminary');
[$page, $limit] = pageParams(); [$page, $limit] = pageParams();
$keyword = trim($_REQUEST['keyword'] ?? ''); $keyword = trim($_REQUEST['keyword'] ?? '');
$status = trim($_REQUEST['status'] ?? '');
$sourceType = trim($_REQUEST['source_type'] ?? ''); $sourceType = trim($_REQUEST['source_type'] ?? '');
$startDate = trim($_REQUEST['start_date'] ?? ''); $startDate = trim($_REQUEST['start_date'] ?? '');
$endDate = trim($_REQUEST['end_date'] ?? ''); $endDate = trim($_REQUEST['end_date'] ?? '');
@@ -23,7 +22,6 @@ if ($keyword !== '') {
$like = "%$keyword%"; $like = "%$keyword%";
array_push($params, $like, $like, $like, $like, $like); array_push($params, $like, $like, $like, $like, $like);
} }
if ($status !== '') { $where[] = 'status = ?'; $params[] = $status; }
if ($sourceType !== '') { $where[] = 'source_type = ?'; $params[] = $sourceType; } if ($sourceType !== '') { $where[] = 'source_type = ?'; $params[] = $sourceType; }
if ($startDate !== '') { $where[] = 'DATE(created_at) >= ?'; $params[] = $startDate; } if ($startDate !== '') { $where[] = 'DATE(created_at) >= ?'; $params[] = $startDate; }
if ($endDate !== '') { $where[] = 'DATE(created_at) <= ?'; $params[] = $endDate; } if ($endDate !== '') { $where[] = 'DATE(created_at) <= ?'; $params[] = $endDate; }
@@ -38,7 +36,7 @@ $total = (int)$stmt->fetchColumn();
$offset = ($page - 1) * $limit; $offset = ($page - 1) * $limit;
$stmt = $pdo->prepare( $stmt = $pdo->prepare(
"SELECT id, source_type, company_name, person_name, contact_phone, contact_email, "SELECT id, source_type, company_name, person_name, contact_phone, contact_email,
target_product_category, description, status, source_channel, recorded_by, source_channel, recorded_by,
follow_person, converted_type, converted_id, created_at, updated_at follow_person, converted_type, converted_id, created_at, updated_at
FROM preliminary_data FROM preliminary_data
WHERE $whereSql WHERE $whereSql
+1 -11
View File
@@ -1,7 +1,7 @@
<?php <?php
/** /**
* 碎片处理统计接口 GET /api/preliminary/stats.php * 碎片处理统计接口 GET /api/preliminary/stats.php
* 返回:总数 / 今日新增 / 各状态数量 * 返回:总数 / 今日新增
*/ */
require_once __DIR__ . '/../common/db.php'; require_once __DIR__ . '/../common/db.php';
require_once __DIR__ . '/../common/response.php'; require_once __DIR__ . '/../common/response.php';
@@ -14,17 +14,7 @@ $pdo = DB::getInstance()->getPdo();
$total = (int)$pdo->query("SELECT COUNT(*) FROM preliminary_data WHERE is_active = 1")->fetchColumn(); $total = (int)$pdo->query("SELECT COUNT(*) FROM preliminary_data WHERE is_active = 1")->fetchColumn();
$today = (int)$pdo->query("SELECT COUNT(*) FROM preliminary_data WHERE is_active = 1 AND DATE(created_at) = CURDATE()")->fetchColumn(); $today = (int)$pdo->query("SELECT COUNT(*) FROM preliminary_data WHERE is_active = 1 AND DATE(created_at) = CURDATE()")->fetchColumn();
$statusRows = $pdo->query(
"SELECT status, COUNT(*) AS cnt FROM preliminary_data WHERE is_active = 1 GROUP BY status"
)->fetchAll();
$statusMap = [];
foreach ($statusRows as $r) {
$statusMap[$r['status']] = (int)$r['cnt'];
}
Response::success([ Response::success([
'total' => $total, 'total' => $total,
'today' => $today, 'today' => $today,
'status' => $statusMap,
'statuses' => ['待处理', '待分配', '处理中', '已转换', '已废弃', '已拒收'],
]); ]);
+20
View File
@@ -0,0 +1,20 @@
-- ============================================================
-- SuperLink Web - v1.0.50 数据库结构变更
-- 1) preliminary_data 删除 status 字段(硬数据板块不再有「状态」:
-- 查看弹窗不展示、数据表不存储;已转换以 converted_id 非空标识,
-- 废弃改为彻底硬删除,不再需要已废弃状态)
-- 2) persons.gender 枚举收敛为 男/女/未知(原 其他/保密 不再允许)
-- ============================================================
SET NAMES utf8mb4;
-- 1) 硬碎片数据表去掉 status 列(相关索引 idx_status 随列删除)
ALTER TABLE `preliminary_data` DROP COLUMN `status`;
-- 2) 人员性别:先扩枚举加入「未知」→ 归并旧值 → 再收紧枚举
ALTER TABLE `persons`
MODIFY COLUMN `gender` enum('男','女','未知','其他','保密') COLLATE utf8mb4_unicode_ci DEFAULT '未知' COMMENT '性别';
UPDATE `persons` SET `gender` = '未知' WHERE `gender` IN ('其他', '保密');
ALTER TABLE `persons`
MODIFY COLUMN `gender` enum('男','女','未知') COLLATE utf8mb4_unicode_ci DEFAULT '未知' COMMENT '性别';
+1 -1
View File
@@ -5,7 +5,7 @@ var BASE_URL = '/api/';
var PAGE_SIZE = 20; var PAGE_SIZE = 20;
/** 系统版本号(logo旁展示):修改代码后运行 tools/bump_version.php 自动递增 */ /** 系统版本号(logo旁展示):修改代码后运行 tools/bump_version.php 自动递增 */
var APP_VERSION = 'v1.0.49'; var APP_VERSION = 'v1.0.50';
/** 页脚版权/备案信息(在 config.js 中修改) */ /** 页脚版权/备案信息(在 config.js 中修改) */
var FOOTER_TEXT = '© 2026 SuperLink 管理系统 版权所有 | 备案号:请替换为真实备案号'; var FOOTER_TEXT = '© 2026 SuperLink 管理系统 版权所有 | 备案号:请替换为真实备案号';
+28 -15
View File
@@ -1,7 +1,7 @@
/** /**
* fragment.js - 碎片处理中心(v1.0.27) * fragment.js - 碎片处理中心(v1.0.50)
* 布局:顶部指标卡 + 【硬碎片】独立区块 + 【软碎片】独立区块(不用 Tab) * 布局:顶部指标卡 + 【硬碎片】独立区块 + 【软碎片】独立区块(不用 Tab)
* 硬碎片:preliminary_data 中未终结记录(完整度/类型/内容摘要/来源/录入人/时间/状态/操作) * 硬碎片:preliminary_data 中未转换记录(完整度/类型/内容摘要/来源/录入人/时间/操作;废弃即硬删除,无状态字段)
* 软碎片:四张主表 is_incomplete=1 记录(完整度/缺失层级标签/名称/缺失字段/所属表/时间/操作) * 软碎片:四张主表 is_incomplete=1 记录(完整度/缺失层级标签/名称/缺失字段/所属表/时间/操作)
*/ */
$(function () { $(function () {
@@ -42,8 +42,8 @@ $(function () {
// ================= 硬碎片 ================= // ================= 硬碎片 =================
'<div class="card" style="margin-top:14px;">' + '<div class="card" style="margin-top:14px;">' +
' <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">' + ' <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">' +
' <b style="font-size:15px;color:#1e2a3a;">硬数据新增与处理</b>' + ' <b style="font-size:15px;color:#1e2a3a;">硬数据碎片新增与处理</b>' +
' <span style="font-size:12px;color:#8a94a6;">关键字段缺失、未入主表的原始线索(不含已转换/已废弃)</span>' + ' <span style="font-size:12px;color:#8a94a6;">关键字段缺失、未入主表的原始线索(不含已转换)</span>' +
' </div>' + ' </div>' +
' <div class="toolbar">' + ' <div class="toolbar">' +
' <select id="hard-type"><option value="">全部类型</option>' + ' <select id="hard-type"><option value="">全部类型</option>' +
@@ -147,7 +147,8 @@ $(function () {
'<td>' + fmtDate(r.updated_at) + '</td>' + '<td>' + fmtDate(r.updated_at) + '</td>' +
'<td class="ops">' + '<td class="ops">' +
' <a onclick="viewHard(' + r.id + ')">查看</a>' + ' <a onclick="viewHard(' + r.id + ')">查看</a>' +
' <a onclick="convertHard(' + r.id + ')">继续补全</a>' + ' <a onclick="convertHard(' + r.id + ')">手动补全</a>' +
' <a onclick="aiCompleteHard(' + r.id + ')">AI补全</a>' +
' <a onclick="convertHardQuick(' + r.id + ')">转软碎片</a>' + ' <a onclick="convertHardQuick(' + r.id + ')">转软碎片</a>' +
' <a onclick="discardHard(' + r.id + ')">废弃</a>' + ' <a onclick="discardHard(' + r.id + ')">废弃</a>' +
'</td></tr>'; '</td></tr>';
@@ -184,7 +185,9 @@ $(function () {
'<td style="max-width:240px;">' + escHtml(missing) + '</td>' + '<td style="max-width:240px;">' + escHtml(missing) + '</td>' +
'<td>' + escHtml(r.table_label) + '</td>' + '<td>' + escHtml(r.table_label) + '</td>' +
'<td>' + fmtDate(r.updated_at) + '</td>' + '<td>' + fmtDate(r.updated_at) + '</td>' +
'<td class="ops"><a onclick="completeSoft(\'' + r.table + '\',' + r.id + ')">补全</a></td></tr>'; '<td class="ops">' +
' <a onclick="completeSoft(\'' + r.table + '\',' + r.id + ')">手动补全</a>' +
' <a onclick="aiCompleteSoft(\'' + r.table + '\',' + r.id + ')">AI补全</a></td></tr>';
}); });
if (!softRows.length) { if (!softRows.length) {
html = '<tr><td colspan="8" class="empty-tip" style="padding:30px 0;">暂无软碎片</td></tr>'; html = '<tr><td colspan="8" class="empty-tip" style="padding:30px 0;">暂无软碎片</td></tr>';
@@ -301,7 +304,7 @@ $(function () {
var fields = [ var fields = [
['类型', d.source_type_label], ['公司名称', d.company_name], ['联系人', d.person_name], ['类型', d.source_type_label], ['公司名称', d.company_name], ['联系人', d.person_name],
['联系电话', d.contact_phone], ['联系邮箱', d.contact_email], ['联系电话', d.contact_phone], ['联系邮箱', d.contact_email],
['来源渠道', d.source_channel], ['录入人', d.recorded_by], ['状态', d.status], ['来源渠道', d.source_channel], ['录入人', d.recorded_by],
['录入时间', d.created_at], ['处理时间', d.processed_at] ['录入时间', d.created_at], ['处理时间', d.processed_at]
]; ];
fields.forEach(function (f) { fields.forEach(function (f) {
@@ -354,7 +357,7 @@ $(function () {
'</form>'; '</form>';
var idx = Dialog.open({ var idx = Dialog.open({
title: '硬数据继续补全', title: '硬数据手动补全',
area: ['640px', 'auto'], area: ['640px', 'auto'],
content: content, content: content,
btn: false btn: false
@@ -429,24 +432,24 @@ $(function () {
target = d.company_name ? 'company' : (d.person_name ? 'person' : ''); target = d.company_name ? 'company' : (d.person_name ? 'person' : '');
} }
if (!target) { if (!target) {
Dialog.error('该碎片缺少可转换信息,请使用「继续补全」'); Dialog.error('该碎片缺少可转换信息,请使用「手动补全」');
return; return;
} }
// 各类型转软碎片基本要求预检 // 各类型转软碎片基本要求预检
if (target === 'company' && (!d.industry || !d.registration_number)) { if (target === 'company' && (!d.industry || !d.registration_number)) {
Dialog.error('企业类型转软碎片:请先「继续补全」填写行业和注册号'); Dialog.error('企业类型转软碎片:请先「手动补全」填写行业和注册号');
return; return;
} }
if (target === 'person' && !d.person_name) { if (target === 'person' && !d.person_name) {
Dialog.error('人员类型转软碎片:请先「继续补全」填写姓名'); Dialog.error('人员类型转软碎片:请先「手动补全」填写姓名');
return; return;
} }
if (target === 'product' && !d.company_name) { if (target === 'product' && !d.company_name) {
Dialog.error('产品类型转软碎片:请先「继续补全」填写公司名称'); Dialog.error('产品类型转软碎片:请先「手动补全」填写公司名称');
return; return;
} }
if (target === 'need' && !d.company_name) { if (target === 'need' && !d.company_name) {
Dialog.error('需求类型转软碎片:请先「继续补全」填写公司名称'); Dialog.error('需求类型转软碎片:请先「手动补全」填写公司名称');
return; return;
} }
Dialog.confirm('确定将该硬数据直接转换为软数据碎片吗?', function () { Dialog.confirm('确定将该硬数据直接转换为软数据碎片吗?', function () {
@@ -461,9 +464,14 @@ $(function () {
}); });
}; };
/* ================= 硬碎片:废弃 ================= */ /* ================= 硬碎片:AI补全(占位,后续开发) ================= */
window.aiCompleteHard = function (id) {
Dialog.alert('AI补全功能开发中,敬请期待');
};
/* ================= 硬碎片:废弃(彻底硬删除) ================= */
window.discardHard = function (id) { window.discardHard = function (id) {
Dialog.confirm('确定废弃该碎片吗?废弃后不可恢复。', function () { Dialog.confirm('确定废弃该碎片吗?将从数据表中彻底删除,不可恢复。', function () {
httpPost('fragment/hard_discard.php', { id: id }).then(function () { httpPost('fragment/hard_discard.php', { id: id }).then(function () {
Dialog.success('已废弃', function () { Dialog.success('已废弃', function () {
loadStats(); loadStats();
@@ -473,6 +481,11 @@ $(function () {
}); });
}; };
/* ================= 软碎片:AI补全(占位,后续开发) ================= */
window.aiCompleteSoft = function (table, id) {
Dialog.alert('AI补全功能开发中,敬请期待');
};
/* ================= 软碎片:补全 ================= */ /* ================= 软碎片:补全 ================= */
window.completeSoft = function (table, id) { window.completeSoft = function (table, id) {
httpGet('fragment/soft_detail.php', { table: table, id: id }).then(function (d) { httpGet('fragment/soft_detail.php', { table: table, id: id }).then(function (d) {
+2 -3
View File
@@ -27,7 +27,7 @@ $(function () {
' <input type="text" data-filter="keyword" placeholder="姓名/证件号/手机/邮箱关键词">' + ' <input type="text" data-filter="keyword" placeholder="姓名/证件号/手机/邮箱关键词">' +
' <select data-filter="gender"><option value="">全部性别</option>' + ' <select data-filter="gender"><option value="">全部性别</option>' +
' <option value="男">男</option><option value="女">女</option>' + ' <option value="男">男</option><option value="女">女</option>' +
' <option value="其他">其他</option><option value="保密">保密</option></select>' + ' <option value="未知">未知</option></select>' +
' <input type="text" data-filter="nationality" placeholder="国籍">' + ' <input type="text" data-filter="nationality" placeholder="国籍">' +
' <input type="text" data-filter="work_location" placeholder="工作所在地">' + ' <input type="text" data-filter="work_location" placeholder="工作所在地">' +
' <button class="btn btn-primary" id="btn-search">搜索</button>' + ' <button class="btn btn-primary" id="btn-search">搜索</button>' +
@@ -287,10 +287,9 @@ $(function () {
'<div class="form-grid">' + '<div class="form-grid">' +
' <div class="form-item"><label>姓名<span class="req">*</span></label><input type="text" name="full_name" value="' + escHtml(p.full_name) + '"></div>' + ' <div class="form-item"><label>姓名<span class="req">*</span></label><input type="text" name="full_name" value="' + escHtml(p.full_name) + '"></div>' +
' <div class="form-item"><label>性别</label><select name="gender">' + ' <div class="form-item"><label>性别</label><select name="gender">' +
' <option value="保密"' + (!p.gender || p.gender === '保密' ? ' selected' : '') + '>保密</option>' +
' <option value="男"' + (p.gender === '男' ? ' selected' : '') + '>男</option>' + ' <option value="男"' + (p.gender === '男' ? ' selected' : '') + '>男</option>' +
' <option value="女"' + (p.gender === '女' ? ' selected' : '') + '>女</option>' + ' <option value="女"' + (p.gender === '女' ? ' selected' : '') + '>女</option>' +
' <option value="其他"' + (p.gender === '其他' ? ' selected' : '') + '>其他</option></select></div>' + ' <option value="未知"' + (!p.gender || p.gender === '未知' ? ' selected' : '') + '>未知</option></select></div>' +
' <div class="form-item"><label>国籍</label><input type="text" name="nationality" id="person-nationality" value="' + escHtml(p.nationality) + '" list="person-country-list" placeholder="选择或输入">' + ' <div class="form-item"><label>国籍</label><input type="text" name="nationality" id="person-nationality" value="' + escHtml(p.nationality) + '" list="person-country-list" placeholder="选择或输入">' +
' <datalist id="person-country-list"></datalist></div>' + ' <datalist id="person-country-list"></datalist></div>' +
' <div class="form-item"><label>证件类型</label><input type="text" name="id_type" value="' + escHtml(p.id_type) + '"></div>' + ' <div class="form-item"><label>证件类型</label><input type="text" name="id_type" value="' + escHtml(p.id_type) + '"></div>' +
+3 -18
View File
@@ -19,10 +19,6 @@ $(function () {
'<div class="card">' + '<div class="card">' +
' <div class="toolbar">' + ' <div class="toolbar">' +
' <input type="text" data-filter="keyword" placeholder="公司/姓名/电话/邮箱关键词">' + ' <input type="text" data-filter="keyword" placeholder="公司/姓名/电话/邮箱关键词">' +
' <select data-filter="status"><option value="">全部状态</option>' +
' <option value="待处理">待处理</option><option value="待分配">待分配</option>' +
' <option value="处理中">处理中</option><option value="已转换">已转换</option>' +
' <option value="已废弃">已废弃</option><option value="已拒收">已拒收</option></select>' +
' <select data-filter="source_type"><option value="">全部类型</option>' + ' <select data-filter="source_type"><option value="">全部类型</option>' +
' <option value="company">公司</option><option value="person">人员</option>' + ' <option value="company">公司</option><option value="person">人员</option>' +
' <option value="product">产品</option><option value="need">需求</option><option value="mixed">混合</option></select>' + ' <option value="product">产品</option><option value="need">需求</option><option value="mixed">混合</option></select>' +
@@ -32,7 +28,7 @@ $(function () {
' <button class="btn" id="btn-reset">重置</button>' + ' <button class="btn" id="btn-reset">重置</button>' +
' </div>' + ' </div>' +
' <div class="table-wrap"><table class="grid">' + ' <div class="table-wrap"><table class="grid">' +
' <thead><tr><th>ID</th><th>类型</th><th>公司</th><th>姓名</th><th>电话</th><th>邮箱</th><th>意向品类</th><th>描述</th><th>状态</th><th>跟进人</th><th>录入时间</th></tr></thead>' + ' <thead><tr><th>ID</th><th>类型</th><th>公司</th><th>姓名</th><th>电话</th><th>邮箱</th><th>跟进人</th><th>录入时间</th></tr></thead>' +
' <tbody id="pre-tbody"></tbody>' + ' <tbody id="pre-tbody"></tbody>' +
' </table></div>' + ' </table></div>' +
' <div class="pagination" id="pagination"></div>' + ' <div class="pagination" id="pagination"></div>' +
@@ -42,16 +38,9 @@ $(function () {
function loadStats() { function loadStats() {
httpGet('preliminary/stats.php').then(function (d) { httpGet('preliminary/stats.php').then(function (d) {
var statusMap = d.status || {};
var html = '';
(d.statuses || []).forEach(function (s) {
var cnt = statusMap[s] || 0;
html += '<div class="stat-card c-' + ['blue', 'orange', 'teal', 'green', 'red', 'gray'][(d.statuses || []).indexOf(s) % 6] + '">' +
'<div class="num">' + cnt + '</div><div class="label">' + escHtml(s) + '</div></div>';
});
$('#stat-cards').html( $('#stat-cards').html(
'<div class="stat-card c-blue"><div class="num">' + d.total + '</div><div class="label">碎片总数</div></div>' + '<div class="stat-card c-blue"><div class="num">' + d.total + '</div><div class="label">碎片总数</div></div>' +
'<div class="stat-card c-green"><div class="num">' + d.today + '</div><div class="label">今日新增</div></div>' + html '<div class="stat-card c-green"><div class="num">' + d.today + '</div><div class="label">今日新增</div></div>'
); );
}); });
} }
@@ -66,7 +55,6 @@ $(function () {
var rows = data.list || []; var rows = data.list || [];
var html = ''; var html = '';
rows.forEach(function (r) { rows.forEach(function (r) {
var statusCls = { '待处理': 'tag-orange', '待分配': 'tag', '处理中': 'tag', '已转换': 'tag-green', '已废弃': 'tag-gray', '已拒收': 'tag-red' }[r.status] || 'tag';
html += '<tr>' + html += '<tr>' +
'<td>' + r.id + '</td>' + '<td>' + r.id + '</td>' +
'<td>' + escHtml(r.source_type || '-') + '</td>' + '<td>' + escHtml(r.source_type || '-') + '</td>' +
@@ -74,14 +62,11 @@ $(function () {
'<td>' + escHtml(r.person_name || '-') + '</td>' + '<td>' + escHtml(r.person_name || '-') + '</td>' +
'<td>' + escHtml(r.contact_phone || '-') + '</td>' + '<td>' + escHtml(r.contact_phone || '-') + '</td>' +
'<td>' + escHtml(r.contact_email || '-') + '</td>' + '<td>' + escHtml(r.contact_email || '-') + '</td>' +
'<td>' + escHtml(r.target_product_category || '-') + '</td>' +
'<td title="' + escHtml(r.description || '') + '">' + escHtml((r.description || '-').substring(0, 26)) + '</td>' +
'<td><span class="tag ' + statusCls + '">' + escHtml(r.status) + '</span></td>' +
'<td>' + escHtml(r.follow_person || '-') + '</td>' + '<td>' + escHtml(r.follow_person || '-') + '</td>' +
'<td>' + fmtDate(r.created_at) + '</td></tr>'; '<td>' + fmtDate(r.created_at) + '</td></tr>';
}); });
if (!rows.length) { if (!rows.length) {
html = '<tr><td colspan="11" class="empty-tip" style="padding:40px 0;">暂无数据碎片</td></tr>'; html = '<tr><td colspan="8" class="empty-tip" style="padding:40px 0;">暂无数据碎片</td></tr>';
} }
$('#pre-tbody').html(html); $('#pre-tbody').html(html);
renderPagination($('#pagination'), data, loadList); renderPagination($('#pagination'), data, loadList);