v1.0.41: 硬数据流程调整 - 区块标题去【】+preliminary_data瘦身(删非必要字段品类/描述,备份表已建)+硬数据标准(人员需手机/邮箱至少一,企业需名称必填,前后端校验)+完整度字段改4项

This commit is contained in:
nanguaboss
2026-08-10 00:10:53 +08:00
parent df8439fcb5
commit 7fc0546a1e
7 changed files with 41 additions and 26 deletions
+12 -7
View File
@@ -29,13 +29,20 @@ $idNumber = trim($_POST['id_number'] ?? '');
$platform = trim($_POST['platform'] ?? ''); $platform = trim($_POST['platform'] ?? '');
$accountId = trim($_POST['account_id'] ?? ''); $accountId = trim($_POST['account_id'] ?? '');
$profileUrl = trim($_POST['profile_url'] ?? ''); $profileUrl = trim($_POST['profile_url'] ?? '');
$category = trim($_POST['target_product_category'] ?? '');
$description = trim($_POST['description'] ?? '');
$sourceChannel = trim($_POST['source_channel'] ?? ''); $sourceChannel = trim($_POST['source_channel'] ?? '');
if ($sourceChannel === '') { if ($sourceChannel === '') {
$sourceChannel = '手动录入'; $sourceChannel = '手动录入';
} }
// 硬数据标准校验:
// 企业/产品/需求 → 企业名称必填;人员 → 手机、邮箱至少填一个;混合 → 至少有一项内容
if (in_array($sourceType, ['company', 'product', 'need'], true) && $companyName === '') {
Response::error('企业类型硬数据:企业名称为必填项', 400);
}
if ($sourceType === 'person' && $phone === '' && $email === '') {
Response::error('人员类型硬数据:手机号码、邮箱至少填写一项', 400);
}
// 格式校验:手机 / 邮箱 / 身份证 / 主页链接 // 格式校验:手机 / 邮箱 / 身份证 / 主页链接
checkFieldFormat('phone', $phone); checkFieldFormat('phone', $phone);
checkFieldFormat('email', $email); checkFieldFormat('email', $email);
@@ -74,23 +81,21 @@ if ($accountId !== '') $extra['account_id'] = $accountId;
if ($profileUrl !== '') $extra['profile_url'] = $profileUrl; if ($profileUrl !== '') $extra['profile_url'] = $profileUrl;
// 至少有一项内容才能录为碎片 // 至少有一项内容才能录为碎片
if ($companyName === '' && $personName === '' && $phone === '' && $email === '' && $category === '' && $description === '' && !$extra) { if ($companyName === '' && $personName === '' && $phone === '' && $email === '' && !$extra) {
Response::error('请至少填写一项内容', 400); Response::error('请至少填写一项内容', 400);
} }
$pdo->prepare( $pdo->prepare(
"INSERT INTO preliminary_data "INSERT INTO preliminary_data
(source_type, company_name, person_name, contact_phone, contact_email, (source_type, company_name, person_name, contact_phone, contact_email,
target_product_category, description, source_channel, recorded_by, status, extra_info) source_channel, recorded_by, status, extra_info)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, '待处理', ?)" VALUES (?, ?, ?, ?, ?, ?, ?, '待处理', ?)"
)->execute([ )->execute([
$sourceType, $sourceType,
$companyName !== '' ? $companyName : null, $companyName !== '' ? $companyName : null,
$personName !== '' ? $personName : null, $personName !== '' ? $personName : null,
$phone !== '' ? $phone : null, $phone !== '' ? $phone : null,
$email !== '' ? $email : null, $email !== '' ? $email : null,
$category !== '' ? $category : null,
$description !== '' ? $description : null,
$sourceChannel, $sourceChannel,
$_SESSION['username'] ?? null, $_SESSION['username'] ?? null,
$extra ? json_encode($extra, JSON_UNESCAPED_UNICODE) : null, $extra ? json_encode($extra, JSON_UNESCAPED_UNICODE) : null,
+3 -5
View File
@@ -4,11 +4,11 @@
* 被 hard_list.php 与 hard_detail.php 共用 * 被 hard_list.php 与 hard_detail.php 共用
*/ */
/** 硬碎片可统计字段(公司名/联系人/电话/邮箱/品类/描述) */ /** 硬碎片可统计字段(核心+重要:企业名/联系人/手机/邮箱;品类与描述已从表中移除) */
const HARD_FIELDS = ['company_name', 'person_name', 'contact_phone', 'contact_email', 'target_product_category', 'description']; const HARD_FIELDS = ['company_name', 'person_name', 'contact_phone', 'contact_email'];
const HARD_LABELS = [ const HARD_LABELS = [
'company_name' => '公司名称', 'person_name' => '联系人', 'contact_phone' => '电话', 'company_name' => '公司名称', 'person_name' => '联系人', 'contact_phone' => '电话',
'contact_email' => '邮箱', 'target_product_category' => '产品品类', 'description' => '描述', 'contact_email' => '邮箱',
]; ];
/** 计算硬碎片完整度:已填字段数 ÷ 总可填字段数 × 100% */ /** 计算硬碎片完整度:已填字段数 ÷ 总可填字段数 × 100% */
@@ -36,7 +36,5 @@ function hardSummary($row)
if (!empty($row['person_name'])) $parts[] = '联系人:' . $row['person_name']; if (!empty($row['person_name'])) $parts[] = '联系人:' . $row['person_name'];
if (!empty($row['contact_phone'])) $parts[] = '手机:' . $row['contact_phone']; if (!empty($row['contact_phone'])) $parts[] = '手机:' . $row['contact_phone'];
if (!empty($row['contact_email'])) $parts[] = '邮箱:' . $row['contact_email']; if (!empty($row['contact_email'])) $parts[] = '邮箱:' . $row['contact_email'];
if (!empty($row['target_product_category'])) $parts[] = '品类:' . $row['target_product_category'];
if (empty($parts) && !empty($row['description'])) $parts[] = '描述:' . mb_substr($row['description'], 0, 20);
return implode(' ', $parts); return implode(' ', $parts);
} }
+4 -4
View File
@@ -39,8 +39,8 @@ $companyName = trim($_POST['company_name'] ?? ($frag['company_name'] ?? ''));
$personName = trim($_POST['person_name'] ?? ($frag['person_name'] ?? '')); $personName = trim($_POST['person_name'] ?? ($frag['person_name'] ?? ''));
$phone = trim($_POST['contact_phone'] ?? ($frag['contact_phone'] ?? '')); $phone = trim($_POST['contact_phone'] ?? ($frag['contact_phone'] ?? ''));
$email = trim($_POST['contact_email'] ?? ($frag['contact_email'] ?? '')); $email = trim($_POST['contact_email'] ?? ($frag['contact_email'] ?? ''));
$category = trim($_POST['target_product_category'] ?? ($frag['target_product_category'] ?? '')); $category = trim($_POST['target_product_category'] ?? ''); // 仅补全录入表单提供(表中已无该字段)
$description = trim($_POST['description'] ?? ($frag['description'] ?? '')); $description = trim($_POST['description'] ?? ''); // 仅补全录入表单提供
$industry = trim($_POST['industry'] ?? ''); $industry = trim($_POST['industry'] ?? '');
$sourceChannel = $frag['source_channel'] ?? null; $sourceChannel = $frag['source_channel'] ?? null;
@@ -105,7 +105,7 @@ switch ($targetType) {
$industry !== '' ? $industry : null, $industry !== '' ? $industry : null,
$description !== '' ? $description : null, $description !== '' ? $description : null,
$sourceChannel, $sourceChannel,
$frag['description'] ?? null, $description !== '' ? $description : null,
]); ]);
$convertedId = (int)$pdo->lastInsertId(); $convertedId = (int)$pdo->lastInsertId();
$saveContacts('company', $convertedId); $saveContacts('company', $convertedId);
@@ -120,7 +120,7 @@ switch ($targetType) {
$unionId = 'P' . date('YmdHis') . substr(uniqid(), -6); $unionId = 'P' . date('YmdHis') . substr(uniqid(), -6);
$pdo->prepare( $pdo->prepare(
"INSERT INTO persons (union_id, full_name, source_channel, source_detail) VALUES (?, ?, ?, ?)" "INSERT INTO persons (union_id, full_name, source_channel, source_detail) VALUES (?, ?, ?, ?)"
)->execute([$unionId, $personName, $sourceChannel, $frag['description'] ?? null]); )->execute([$unionId, $personName, $sourceChannel, $description !== '' ? $description : null]);
$convertedId = (int)$pdo->lastInsertId(); $convertedId = (int)$pdo->lastInsertId();
$saveContacts('person', $convertedId); $saveContacts('person', $convertedId);
updateIncomplete($pdo, 'persons', $convertedId); updateIncomplete($pdo, 'persons', $convertedId);
+1 -1
View File
@@ -38,7 +38,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, status, 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
+14
View File
@@ -0,0 +1,14 @@
-- ============================================================
-- SuperLink Web - v1.0.41 数据库结构变更
-- preliminary_data 瘦身:只保留核心/重要字段,删除非必要字段
-- 保留:company_name(核心) / contact_phone(核心) / contact_email(核心) / person_name(重要)
-- 删除:target_product_category(非必要) / description(非必要)
-- 说明:删除前先备份整表到 preliminary_data_bak_20260810
-- ============================================================
SET NAMES utf8mb4;
CREATE TABLE `preliminary_data_bak_20260810` AS SELECT * FROM `preliminary_data`;
ALTER TABLE `preliminary_data`
DROP COLUMN `target_product_category`,
DROP COLUMN `description`;
+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.40'; var APP_VERSION = 'v1.0.41';
/** 页脚版权/备案信息(在 config.js 中修改) */ /** 页脚版权/备案信息(在 config.js 中修改) */
var FOOTER_TEXT = '© 2026 SuperLink 管理系统 版权所有 | 备案号:请替换为真实备案号'; var FOOTER_TEXT = '© 2026 SuperLink 管理系统 版权所有 | 备案号:请替换为真实备案号';
+6 -8
View File
@@ -42,7 +42,7 @@ $(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">' +
@@ -68,7 +68,7 @@ $(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;">已入主表但完整度低于阈值的记录(is_incomplete=1)</span>' + ' <span style="font-size:12px;color:#8a94a6;">已入主表但完整度低于阈值的记录(is_incomplete=1)</span>' +
' </div>' + ' </div>' +
' <div class="toolbar">' + ' <div class="toolbar">' +
@@ -212,7 +212,6 @@ $(function () {
' <div class="form-item"><label>联系电话</label><input type="text" name="contact_phone" placeholder="如:13812345678"></div>' + ' <div class="form-item"><label>联系电话</label><input type="text" name="contact_phone" placeholder="如:13812345678"></div>' +
' <div class="form-item"><label>联系邮箱</label><input type="text" name="contact_email" placeholder="如:a@b.com"></div>' + ' <div class="form-item"><label>联系邮箱</label><input type="text" name="contact_email" placeholder="如:a@b.com"></div>' +
' <div class="form-item" id="ha-f-idnumber"><label>身份证号码</label><input type="text" name="id_number" placeholder="15 或 18 位"></div>' + ' <div class="form-item" id="ha-f-idnumber"><label>身份证号码</label><input type="text" name="id_number" placeholder="15 或 18 位"></div>' +
' <div class="form-item" id="ha-f-category"><label>意向产品品类</label><input type="text" name="target_product_category" placeholder="如:工业机器人"></div>' +
' <div class="form-item"><label>来源渠道</label><select name="source_channel" id="ha-source"><option value="手动录入">手动录入</option></select></div>' + ' <div class="form-item"><label>来源渠道</label><select name="source_channel" id="ha-source"><option value="手动录入">手动录入</option></select></div>' +
' <div class="form-item" id="ha-f-platform"><label>社媒平台</label><select name="platform" id="ha-platform"><option value="">-</option>' + ' <div class="form-item" id="ha-f-platform"><label>社媒平台</label><select name="platform" id="ha-platform"><option value="">-</option>' +
' <option value="wechat">微信</option><option value="weibo">微博</option><option value="douyin">抖音</option>' + ' <option value="wechat">微信</option><option value="weibo">微博</option><option value="douyin">抖音</option>' +
@@ -246,7 +245,6 @@ $(function () {
var t = $('#ha-type').val(); var t = $('#ha-type').val();
$('#ha-f-company_name').toggle(t !== 'person'); $('#ha-f-company_name').toggle(t !== 'person');
$('#ha-f-person_name').toggle(t === 'person' || t === 'mixed'); $('#ha-f-person_name').toggle(t === 'person' || t === 'mixed');
$('#ha-f-category').toggle(t === 'product' || t === 'need');
} }
$('#ha-type').on('change', syncFields); $('#ha-type').on('change', syncFields);
syncFields(); syncFields();
@@ -260,6 +258,9 @@ $(function () {
if (name) fd[name] = $(this).val(); if (name) fd[name] = $(this).val();
}); });
fd.source_type = $('#ha-type').val(); fd.source_type = $('#ha-type').val();
// 硬数据标准校验:企业/产品/需求 → 企业名称必填;人员 → 手机、邮箱至少一项
if (fd.source_type !== 'person' && !$.trim(fd.company_name)) { Dialog.error('企业类型硬数据:企业名称为必填项'); return; }
if (fd.source_type === 'person' && !$.trim(fd.contact_phone) && !$.trim(fd.contact_email)) { Dialog.error('人员类型硬数据:手机号码、邮箱至少填写一项'); return; }
// 格式校验:手机 / 邮箱 / 身份证 // 格式校验:手机 / 邮箱 / 身份证
if (fd.contact_phone && !validatePhoneCN(fd.contact_phone)) { Dialog.error('手机号码格式不正确(应为国内 11 位手机号,如 13812345678)'); return; } if (fd.contact_phone && !validatePhoneCN(fd.contact_phone)) { Dialog.error('手机号码格式不正确(应为国内 11 位手机号,如 13812345678)'); return; }
if (fd.contact_email && !validateEmail(fd.contact_email)) { Dialog.error('邮箱格式不正确'); return; } if (fd.contact_email && !validateEmail(fd.contact_email)) { Dialog.error('邮箱格式不正确'); return; }
@@ -281,7 +282,7 @@ $(function () {
var rowHtml = ''; var rowHtml = '';
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.target_product_category], ['联系电话', d.contact_phone], ['联系邮箱', d.contact_email],
['来源渠道', d.source_channel], ['录入人', d.recorded_by], ['状态', d.status], ['来源渠道', d.source_channel], ['录入人', d.recorded_by], ['状态', d.status],
['录入时间', d.created_at], ['处理时间', d.processed_at] ['录入时间', d.created_at], ['处理时间', d.processed_at]
]; ];
@@ -289,9 +290,6 @@ $(function () {
rowHtml += '<div style="display:flex;margin:6px 0;"><span style="width:80px;color:#8a94a6;">' + f[0] + '</span>' + rowHtml += '<div style="display:flex;margin:6px 0;"><span style="width:80px;color:#8a94a6;">' + f[0] + '</span>' +
'<span>' + escHtml(f[1] || '-') + '</span></div>'; '<span>' + escHtml(f[1] || '-') + '</span></div>';
}); });
if (d.description) {
rowHtml += '<div style="margin:6px 0;"><span style="color:#8a94a6;">原始描述</span><div style="margin-top:4px;padding:8px;background:#f7f8fa;border-radius:4px;font-size:12px;max-height:120px;overflow:auto;">' + escHtml(d.description) + '</div></div>';
}
Dialog.open({ Dialog.open({
title: '硬碎片详情(完整度 ' + d.completeness + '%)', title: '硬碎片详情(完整度 ' + d.completeness + '%)',
area: ['560px', 'auto'], area: ['560px', 'auto'],