Files
superlink/static/js/fragment.js
T

429 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 === 'optional') 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>' +
' <span class="spacer"></span>' +
' <button class="btn btn-success" id="hard-add">+ 新增碎片</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="optional">待补充</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><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);
});
$('#hard-add').on('click', function () { openHardAdd(); });
$('#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 + '/' + 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-teal"><div class="num">' + d.cumulative + '</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('、') || '-';
var layers = r.layer_rates || {};
var layerHtml = '核心' + (layers.core !== undefined ? layers.core : '-') + '% / 重要' + (layers.important !== undefined ? layers.important : '-') + '% / 非必要' + (layers.optional !== undefined ? layers.optional : '-') + '%';
html += '<tr>' +
'<td>' + completenessHtml(r.overall) + '</td>' +
'<td>' + levelTagHtml(r.level) + '</td>' +
'<td>' + escHtml(r.name) + '</td>' +
'<td style="font-size:12px;color:#5a6472;white-space:nowrap;">' + layerHtml + '</td>' +
'<td style="max-width:240px;">' + 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="8" class="empty-tip" style="padding:30px 0;">暂无软碎片</td></tr>';
}
$('#soft-tbody').html(html);
renderPagination($('#soft-pagination'), d, loadSoftList);
});
}
/* ================= 硬碎片:新增 ================= */
function openHardAdd() {
var typeSel = '<select id="ha-type">' +
'<option value="company">企业</option><option value="person">人员</option>' +
'<option value="product">产品</option><option value="need">需求</option><option value="mixed">混合</option></select>';
var content =
'<form id="ha-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="ha-f-company_name"><label>公司名称</label><input type="text" name="company_name" placeholder="如:深圳XX科技"></div>' +
' <div class="form-item" id="ha-f-person_name"><label>联系人</label><input type="text" name="person_name" placeholder="如:张三"></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" 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 full"><label>原始描述</label><textarea name="description" style="height:70px;" placeholder="记录原始线索信息"></textarea></div>' +
'</div>' +
'<div class="dialog-footer"><button type="button" class="btn" id="ha-cancel">取消</button>' +
'<button type="submit" class="btn btn-primary">保存碎片</button></div>' +
'</form>';
var idx = Dialog.open({
title: '新增硬碎片',
area: ['640px', 'auto'],
content: content,
btn: false
});
// 来源渠道选项
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>';
});
$('#ha-source').html(html);
}).catch(function () {});
function syncFields() {
var t = $('#ha-type').val();
$('#ha-f-company_name').toggle(t !== 'person');
$('#ha-f-person_name').toggle(t === 'person' || t === 'mixed');
$('#ha-f-category').toggle(t === 'product' || t === 'need');
}
$('#ha-type').on('change', syncFields);
syncFields();
$('#ha-cancel').on('click', function () { Dialog.close(idx); });
$('#ha-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.source_type = $('#ha-type').val();
// 格式校验:手机 / 邮箱
if (fd.contact_phone && !validatePhoneCN(fd.contact_phone)) { Dialog.error('手机号码格式不正确(应为国内 11 位手机号,如 13812345678)'); return; }
if (fd.contact_email && !validateEmail(fd.contact_email)) { Dialog.error('邮箱格式不正确'); return; }
httpPost('fragment/hard_add.php', fd).then(function () {
Dialog.success('新增成功', function () {
Dialog.close(idx);
loadStats();
loadHardList(1);
});
});
});
}
/* ================= 硬碎片:查看 ================= */
window.viewHard = function (id) {
httpGet('fragment/hard_detail.php', { id: id }).then(function (d) {
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();
// 格式校验:手机 / 邮箱
if (fd.contact_phone && !validatePhoneCN(fd.contact_phone)) { Dialog.error('手机号码格式不正确(应为国内 11 位手机号,如 13812345678)'); return; }
if (fd.contact_email && !validateEmail(fd.contact_email)) { Dialog.error('邮箱格式不正确'); return; }
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_optional || []);
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 layers = d.layers || {};
var layerDesc = '核心' + (layers.core ? layers.core.rate : '-') + '% / 重要' + (layers.important ? layers.important.rate : '-') + '% / 非必要' + (layers.optional ? layers.optional.rate : '-') + '%';
var content =
'<form id="soft-form" style="padding:16px 22px 4px;">' +
'<div style="font-size:12px;color:#8a94a6;margin-bottom:4px;">整体完整度 <b style="color:#d9534f;">' + d.overall + '%</b>,各层:' + layerDesc + '</div>' +
'<div style="font-size:12px;color:#8a94a6;margin-bottom:10px;">核心层须 100%、重要层 ≥60%、非必要层 ≥40%,补全后自动重新计算。</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();
});
// 格式校验:身份证 / 手机 / 邮箱
if (fd.id_number && !validateIdCard(fd.id_number)) { Dialog.error('身份证号码格式不正确(应为 15 或 18 位)'); return; }
if (fd.phone && !validatePhoneCN(fd.phone)) { Dialog.error('手机号码格式不正确(应为国内 11 位手机号,如 13812345678)'); return; }
if (fd.email && !validateEmail(fd.email)) { Dialog.error('邮箱格式不正确'); return; }
httpPost('fragment/soft_update.php', fd).then(function (res) {
Dialog.success(res && res.is_complete ? '补全完成' : '已保存(整体完整度 ' + (res && res.overall) + '%)', function () {
Dialog.close(idx);
loadStats();
loadSoftList(softPage);
loadHardList(hardPage);
});
});
});
});
};
});