v1.0.27: 碎片处理中心(完整方案) - 完整度计算引擎(四表分层/阈值)+is_incomplete标记(写操作挂钩)+api/fragment八接口(统计/硬碎片列表详情转换废弃/软碎片列表详情补全)+fragment.html前端(指标卡+硬软碎片双区块)

This commit is contained in:
nanguaboss
2026-08-09 15:47:05 +08:00
parent b68232bff3
commit 76431c20ac
25 changed files with 1342 additions and 3 deletions
+1
View File
@@ -737,6 +737,7 @@ table.grid .ops a:hover { text-decoration: underline; }
.tag-green { background: #eafaf1; color: #27ae60; }
.tag-red { background: #fdecea; color: #e74c3c; }
.tag-orange { background: #fef5e7; color: #f39c12; }
.tag-blue { background: #eaf2fd; color: #2a5298; }
.tag-gray { background: #f0f2f6; color: #8a94a6; }
/* ---------- 空状态 ---------- */
+3 -3
View File
@@ -5,7 +5,7 @@ var BASE_URL = '/api/';
var PAGE_SIZE = 20;
/** 系统版本号(logo旁展示):修改代码后运行 tools/bump_version.php 自动递增 */
var APP_VERSION = 'v1.0.26';
var APP_VERSION = 'v1.0.27';
/** 页脚版权/备案信息(在 config.js 中修改) */
var FOOTER_TEXT = '© 2026 SuperLink 管理系统 版权所有 | 备案号:请替换为真实备案号';
@@ -35,7 +35,7 @@ var ICONS = {
/** 菜单标识 -> 权限显示信息(角色权限标签等用) */
var MENU_MAP = {
'dashboard': { name: '综合概览', icon: 'dashboard', url: 'dashboard.html' },
'preliminary': { name: '碎片处理', icon: 'preliminary', url: 'preliminary.html' },
'preliminary': { name: '碎片处理', icon: 'preliminary', url: 'fragment.html' },
'company': { name: '企业数据', icon: 'company', url: 'company.html' },
'person': { name: '人员数据', icon: 'person', url: 'person.html' },
'need': { name: '需求转盘', icon: 'need', url: 'need.html' },
@@ -58,7 +58,7 @@ var MENU_MAP = {
*/
var MENU_GROUPS = [
{ key: 'dashboard', name: '综合概览', icon: 'dashboard', url: 'dashboard.html' },
{ key: 'preliminary', name: '碎片处理', icon: 'preliminary', url: 'preliminary.html' },
{ key: 'preliminary', name: '碎片处理', icon: 'preliminary', url: 'fragment.html' },
{
key: 'data', name: '数据管理', icon: 'data', children: [
{ key: 'company', name: '企业数据', icon: 'company', url: 'company.html' },
+346
View File
@@ -0,0 +1,346 @@
/**
* fragment.js - 碎片处理中心(v1.0.27)
* 布局:顶部指标卡 + 【硬碎片】独立区块 + 【软碎片】独立区块(不用 Tab)
* 硬碎片:preliminary_data 中未终结记录(完整度/类型/内容摘要/来源/录入人/时间/状态/操作)
* 软碎片:四张主表 is_incomplete=1 记录(完整度/缺失层级标签/名称/缺失字段/所属表/时间/操作)
*/
$(function () {
renderShell('碎片处理中心', '数据管理 / 碎片处理');
renderPage();
initPage('preliminary', function (user) {
if (!checkPagePermission('preliminary')) return;
loadStats();
loadHardList(1);
loadSoftList(1);
loadDicts();
});
var hardPage = 1, softPage = 1;
var hardRows = [], softRows = [];
/** 缺失层级标签样式 */
function levelTagHtml(level) {
if (level === 'core') return '<span class="tag tag-red">缺核心</span>';
if (level === 'important') return '<span class="tag tag-orange">缺重要</span>';
if (level === 'supplementary') return '<span class="tag tag-blue">待补充</span>';
return '<span class="tag tag-green">完整</span>';
}
/** 完整度进度条 + 百分比 */
function completenessHtml(score) {
var color = score >= 80 ? '#2f8f46' : (score >= 65 ? '#e6a23c' : '#d9534f');
return '<div style="display:flex;align-items:center;gap:6px;min-width:90px;">' +
'<div style="flex:1;height:6px;background:#eef0f4;border-radius:3px;overflow:hidden;">' +
'<div style="height:100%;width:' + score + '%;background:' + color + ';border-radius:3px;"></div></div>' +
'<span style="font-size:12px;color:' + color + ';white-space:nowrap;">' + score + '%</span></div>';
}
/** 渲染页面内容:指标卡 + 硬碎片 + 软碎片 两个独立区块 */
function renderPage() {
$('#page-content').html(
'<div class="stat-cards" id="stat-cards"></div>' +
// ================= 硬碎片 =================
'<div class="card" style="margin-top:14px;">' +
' <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">' +
' <b style="font-size:15px;color:#1e2a3a;">【硬碎片】</b>' +
' <span style="font-size:12px;color:#8a94a6;">关键字段缺失、未入主表的原始线索(不含已转换/已废弃)</span>' +
' </div>' +
' <div class="toolbar">' +
' <select id="hard-type"><option value="">全部类型</option>' +
' <option value="company">企业</option><option value="person">人员</option>' +
' <option value="product">产品</option><option value="need">需求</option><option value="mixed">混合</option></select>' +
' <select id="hard-source"><option value="">全部来源</option></select>' +
' <select id="hard-status"><option value="">全部状态</option>' +
' <option value="待处理">待处理</option><option value="待分配">待分配</option>' +
' <option value="处理中">处理中</option><option value="已拒收">已拒收</option></select>' +
' <input type="text" id="hard-keyword" placeholder="公司/姓名/电话/邮箱/描述关键词">' +
' <button class="btn btn-primary" id="hard-search">搜索</button>' +
' <button class="btn" id="hard-reset">重置</button>' +
' </div>' +
' <div class="table-wrap"><table class="grid">' +
' <thead><tr><th>完整度</th><th>类型</th><th>内容摘要</th><th>来源</th><th>录入人</th><th>录入时间</th><th>状态</th><th>操作</th></tr></thead>' +
' <tbody id="hard-tbody"></tbody>' +
' </table></div>' +
' <div class="pagination" id="hard-pagination"></div>' +
'</div>' +
// ================= 软碎片 =================
'<div class="card" style="margin-top:14px;">' +
' <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">' +
' <b style="font-size:15px;color:#1e2a3a;">【软碎片】</b>' +
' <span style="font-size:12px;color:#8a94a6;">已入主表但完整度低于阈值的记录(is_incomplete=1)</span>' +
' </div>' +
' <div class="toolbar">' +
' <select id="soft-table"><option value="">全部所属表</option>' +
' <option value="persons">人员</option><option value="companies">企业</option>' +
' <option value="social_accounts">联系方式</option><option value="media">媒体</option></select>' +
' <select id="soft-level"><option value="">全部缺失层级</option>' +
' <option value="core">缺核心</option><option value="important">缺重要</option>' +
' <option value="supplementary">待补充</option></select>' +
' <input type="text" id="soft-keyword" placeholder="名称关键词">' +
' <button class="btn btn-primary" id="soft-search">搜索</button>' +
' <button class="btn" id="soft-reset">重置</button>' +
' </div>' +
' <div class="table-wrap"><table class="grid">' +
' <thead><tr><th>完整度</th><th>缺失层级</th><th>名称</th><th>缺失字段</th><th>所属表</th><th>录入时间</th><th>操作</th></tr></thead>' +
' <tbody id="soft-tbody"></tbody>' +
' </table></div>' +
' <div class="pagination" id="soft-pagination"></div>' +
'</div>'
);
$('#hard-search').on('click', function () { loadHardList(1); });
$('#hard-keyword').on('keydown', function (e) { if (e.keyCode === 13) loadHardList(1); });
$('#hard-reset').on('click', function () {
$('#hard-type').val(''); $('#hard-source').val(''); $('#hard-status').val(''); $('#hard-keyword').val('');
loadHardList(1);
});
$('#soft-search').on('click', function () { loadSoftList(1); });
$('#soft-keyword').on('keydown', function (e) { if (e.keyCode === 13) loadSoftList(1); });
$('#soft-reset').on('click', function () {
$('#soft-table').val(''); $('#soft-level').val(''); $('#soft-keyword').val('');
loadSoftList(1);
});
}
/** 来源渠道下拉选项 */
function loadDicts() {
httpGet('common/dicts.php', { type: 'all' }).then(function (d) {
var html = '<option value="">全部来源</option>';
(d.source_channels || []).forEach(function (c) {
html += '<option value="' + escHtml(c.name) + '">' + escHtml(c.name) + '</option>';
});
$('#hard-source').html(html);
}).catch(function () {});
}
/* ================= 顶部指标卡 ================= */
function loadStats() {
httpGet('fragment/stats_overview.php').then(function (d) {
$('#stat-cards').html(
'<div class="stat-card c-blue"><div class="num">' + d.hard_total + '</div><div class="label">硬碎片总数</div></div>' +
'<div class="stat-card c-teal"><div class="num">' + d.soft_total + '</div><div class="label">软碎片总数</div></div>' +
'<div class="stat-card c-green"><div class="num">' + d.month_new + '</div><div class="label">本月新增</div></div>' +
'<div class="stat-card c-orange"><div class="num">' + d.month_processed + '</div><div class="label">本月处理</div></div>' +
'<div class="stat-card c-red"><div class="num">' + d.pending + '</div><div class="label">待处理碎片</div></div>'
);
});
}
/* ================= 硬碎片列表 ================= */
function loadHardList(page) {
hardPage = page;
var params = {
page: page,
source_type: $('#hard-type').val(),
source_channel: $('#hard-source').val(),
status: $('#hard-status').val(),
keyword: $.trim($('#hard-keyword').val())
};
httpGet('fragment/hard_list.php', params).then(function (d) {
hardRows = d.list || [];
var html = '';
hardRows.forEach(function (r) {
html += '<tr>' +
'<td>' + completenessHtml(r.completeness) + '</td>' +
'<td>' + escHtml(r.source_type_label) + '</td>' +
'<td title="' + escHtml(r.summary) + '" style="max-width:280px;">' + escHtml(r.summary || '-') + '</td>' +
'<td>' + escHtml(r.source_channel || '-') + '</td>' +
'<td>' + escHtml(r.recorded_by || '-') + '</td>' +
'<td>' + fmtDate(r.created_at) + '</td>' +
'<td>' + escHtml(r.status || '-') + '</td>' +
'<td class="ops">' +
' <a onclick="viewHard(' + r.id + ')">查看</a>' +
' <a onclick="convertHard(' + r.id + ')">补全录入</a>' +
' <a onclick="discardHard(' + r.id + ')">废弃</a>' +
'</td></tr>';
});
if (!hardRows.length) {
html = '<tr><td colspan="8" class="empty-tip" style="padding:30px 0;">暂无硬碎片</td></tr>';
}
$('#hard-tbody').html(html);
renderPagination($('#hard-pagination'), d, loadHardList);
});
}
/* ================= 软碎片列表 ================= */
function loadSoftList(page) {
softPage = page;
var params = {
page: page,
table: $('#soft-table').val(),
level: $('#soft-level').val(),
keyword: $.trim($('#soft-keyword').val())
};
httpGet('fragment/soft_list.php', params).then(function (d) {
softRows = d.list || [];
var html = '';
softRows.forEach(function (r) {
var missing = (r.missing_fields || []).join('、') || '-';
html += '<tr>' +
'<td>' + completenessHtml(r.score) + '</td>' +
'<td>' + levelTagHtml(r.level) + '</td>' +
'<td>' + escHtml(r.name) + '</td>' +
'<td style="max-width:260px;">' + escHtml(missing) + '</td>' +
'<td>' + escHtml(r.table_label) + '</td>' +
'<td>' + fmtDate(r.created_at) + '</td>' +
'<td class="ops"><a onclick="completeSoft(\'' + r.table + '\',' + r.id + ')">补全</a></td></tr>';
});
if (!softRows.length) {
html = '<tr><td colspan="7" class="empty-tip" style="padding:30px 0;">暂无软碎片</td></tr>';
}
$('#soft-tbody').html(html);
renderPagination($('#soft-pagination'), d, loadSoftList);
});
}
/* ================= 硬碎片:查看 ================= */
window.viewHard = function (id) {
httpGet('fragment/hard_detail.php', { id: id }).then(function (d) {
var missing = [].concat(d.missing.core || [], d.missing.important || [], d.missing.supplementary || []);
var rowHtml = '';
var fields = [
['类型', d.source_type_label], ['公司名称', d.company_name], ['联系人', d.person_name],
['联系电话', d.contact_phone], ['联系邮箱', d.contact_email], ['意向品类', d.target_product_category],
['来源渠道', d.source_channel], ['录入人', d.recorded_by], ['状态', d.status],
['录入时间', d.created_at], ['处理时间', d.processed_at]
];
fields.forEach(function (f) {
rowHtml += '<div style="display:flex;margin:6px 0;"><span style="width:80px;color:#8a94a6;">' + f[0] + '</span>' +
'<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({
title: '硬碎片详情(完整度 ' + d.completeness + '%)',
area: ['560px', 'auto'],
content: '<div style="padding:16px 22px;">' + rowHtml + '</div>',
btn: ['关闭']
});
});
};
/* ================= 硬碎片:补全录入(转换) ================= */
window.convertHard = function (id) {
httpGet('fragment/hard_detail.php', { id: id }).then(function (d) {
var typeSel = '<select id="cv-type">' +
'<option value="company"' + (d.source_type === 'company' || d.source_type === 'mixed' ? ' selected' : '') + '>企业</option>' +
'<option value="person"' + (d.source_type === 'person' ? ' selected' : '') + '>人员</option>' +
'<option value="product"' + (d.source_type === 'product' ? ' selected' : '') + '>产品</option>' +
'<option value="need"' + (d.source_type === 'need' ? ' selected' : '') + '>需求</option></select>';
var content =
'<form id="cv-form" style="padding:16px 22px 4px;">' +
'<div class="form-grid">' +
' <div class="form-item"><label>转换目标<span class="req">*</span></label>' + typeSel + '</div>' +
' <div class="form-item" id="cv-f-company_name"><label>公司名称</label><input type="text" name="company_name" value="' + escHtml(d.company_name || '') + '"></div>' +
' <div class="form-item" id="cv-f-person_name"><label>联系人</label><input type="text" name="person_name" value="' + escHtml(d.person_name || '') + '"></div>' +
' <div class="form-item"><label>联系电话</label><input type="text" name="contact_phone" value="' + escHtml(d.contact_phone || '') + '"></div>' +
' <div class="form-item"><label>联系邮箱</label><input type="text" name="contact_email" value="' + escHtml(d.contact_email || '') + '"></div>' +
' <div class="form-item" id="cv-f-category"><label>产品品类</label><input type="text" name="target_product_category" value="' + escHtml(d.target_product_category || '') + '"></div>' +
' <div class="form-item" id="cv-f-industry"><label>行业</label><input type="text" name="industry" placeholder="转换为企业时填写"></div>' +
' <div class="form-item" id="cv-f-needcat"><label>需求类别</label><input type="text" name="need_category" placeholder="如:设备采购"></div>' +
' <div class="form-item" id="cv-f-scenario"><label>应用场景</label><input type="text" name="application_scenario" placeholder="如:码垛"></div>' +
' <div class="form-item full"><label>描述/补充说明</label><textarea name="description" style="height:60px;">' + escHtml(d.description || '') + '</textarea></div>' +
'</div>' +
'<div class="dialog-footer"><button type="button" class="btn" id="cv-cancel">取消</button>' +
'<button type="submit" class="btn btn-primary">确认转换</button></div>' +
'</form>';
var idx = Dialog.open({
title: '硬碎片补全录入',
area: ['640px', 'auto'],
content: content,
btn: false
});
function syncFields() {
var t = $('#cv-type').val();
$('#cv-f-company_name').toggle(t !== 'person');
$('#cv-f-person_name').toggle(t === 'person');
$('#cv-f-category').toggle(t === 'product' || t === 'need');
$('#cv-f-industry').toggle(t === 'company');
$('#cv-f-needcat').toggle(t === 'need');
$('#cv-f-scenario').toggle(t === 'need');
}
$('#cv-type').on('change', syncFields);
syncFields();
$('#cv-cancel').on('click', function () { Dialog.close(idx); });
$('#cv-form').on('submit', function (e) {
e.preventDefault();
var fd = {};
$(this).find('input, select, textarea').each(function () {
var name = $(this).attr('name');
if (name) fd[name] = $(this).val();
});
fd.id = id;
fd.target_type = $('#cv-type').val();
httpPost('fragment/hard_convert.php', fd).then(function () {
Dialog.success('转换成功', function () {
Dialog.close(idx);
loadStats();
loadHardList(hardPage);
loadSoftList(1);
});
});
});
});
};
/* ================= 硬碎片:废弃 ================= */
window.discardHard = function (id) {
Dialog.confirm('确定废弃该碎片吗?废弃后不可恢复。', function () {
httpPost('fragment/hard_discard.php', { id: id }).then(function () {
Dialog.success('已废弃', function () {
loadStats();
loadHardList(hardPage);
});
});
});
};
/* ================= 软碎片:补全 ================= */
window.completeSoft = function (table, id) {
httpGet('fragment/soft_detail.php', { table: table, id: id }).then(function (d) {
var allMissing = [].concat(d.missing_core || [], d.missing_important || [], d.missing_supp || []);
var inputs = '';
allMissing.forEach(function (m) {
var isDate = (m.key === 'established_date');
inputs += '<div class="form-item"><label>' + escHtml(m.label) + (m.key === 'full_name' || m.key === 'display_name' ? '<span class="req">*</span>' : '') + '</label>' +
'<input type="' + (isDate ? 'date' : 'text') + '" name="' + m.key + '"></div>';
});
var content =
'<form id="soft-form" style="padding:16px 22px 4px;">' +
'<div style="font-size:12px;color:#8a94a6;margin-bottom:10px;">当前完整度 <b style="color:#d9534f;">' + d.score + '%</b>(阈值 ' + d.threshold + '%),补全后自动重新计算。</div>' +
'<div class="form-grid">' + inputs + '</div>' +
'<div class="dialog-footer"><button type="button" class="btn" id="soft-cancel">取消</button>' +
'<button type="submit" class="btn btn-primary">保存补全</button></div>' +
'</form>';
var idx = Dialog.open({
title: '软碎片补全 - ' + escHtml(d.name) + '(' + d.table_label + ')',
area: ['640px', 'auto'],
content: content,
btn: false
});
$('#soft-cancel').on('click', function () { Dialog.close(idx); });
$('#soft-form').on('submit', function (e) {
e.preventDefault();
var fd = { table: table, id: id };
$(this).find('input').each(function () {
var name = $(this).attr('name');
if (name) fd[name] = $(this).val();
});
httpPost('fragment/soft_update.php', fd).then(function (res) {
Dialog.success(res && res.is_complete ? '补全完成' : '已保存(完整度 ' + (res && res.score) + '%)', function () {
Dialog.close(idx);
loadStats();
loadSoftList(softPage);
loadHardList(hardPage);
});
});
});
});
};
});