v1.0.19: 修复人员编辑入口缺失(person.js addPerson/editPerson) + 渠道明细数量字段名(analysis.php cnt) + CDP全量验证43项PASS

This commit is contained in:
nanguaboss
2026-08-08 19:29:14 +08:00
parent 2cb688ed46
commit 1c8b374a72
40 changed files with 2313 additions and 658 deletions
+275 -89
View File
@@ -1,173 +1,359 @@
/**
* channel.js - 渠道管理逻辑(渠道列表 + 年度统计 + 效率数据)
* channel.js - 渠道管理逻辑(v1.0.18 重构)
* 目标:记录企业/人员数据的来源渠道,并根据以往数据有计划地拓展高效渠道
* 两部分:
* 1) 渠道效能分析:企业(左)/人员(右)两个板块,右上角年份下拉(默认全部)
* - 按「渠道」字段饼状图按百分比排序;鼠标悬停饼图区域显示该渠道 source_detail TOP10 表格
* - 表格字段:来源(只显示前 8 字,超出用…)、数量;默认显示渠道第一名
* 2) 渠道新增计划:列表(渠道类别/来源详情/所属行业/时间窗口/剩余天数/备注说明/状态反馈)
*/
$(function () {
renderShell('渠道管理', '业务模块 / 渠道管理');
renderPage();
initPage('channel', function (user) {
if (!checkPagePermission('channel')) return;
loadList(1);
loadStats();
loadAnalysis();
loadPlans(1);
});
var currentPage = 1;
var pageRows = [];
var analysisData = null;
var charts = { company: null, person: null };
/** 渲染页面内容(统计卡+工具栏+表格+分页) */
/** 渲染页面内容:渠道效能分析(左右两板块)+ 渠道新增计划 */
function renderPage() {
$('#page-content').html(
'<div class="stat-cards" id="stat-summary"></div>' +
'<div class="card">' +
' <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">' +
' <b style="font-size:15px;color:#1e2a3a;">渠道效能分析</b>' +
' <span style="font-size:12px;color:#8a94a6;">按「渠道」字段统计,鼠标悬停饼图查看该渠道来源详情 TOP10</span>' +
' </div>' +
' <div style="display:flex;gap:14px;flex-wrap:wrap;">' +
' <div class="ana-panel">' +
' <div class="ana-head"><span>企业数据</span>' +
' <select id="ana-year-company" class="ana-year"><option value="">全部年份</option></select></div>' +
' <div class="ana-body">' +
' <div id="ana-chart-company" class="ana-chart"></div>' +
' <div class="ana-detail"><div class="ana-detail-title" id="ana-detail-title-c">渠道明细</div>' +
' <table class="grid" style="font-size:12px;"><thead><tr><th>来源</th><th>数量</th></tr></thead>' +
' <tbody id="ana-detail-c"></tbody></table></div>' +
' </div>' +
' </div>' +
' <div class="ana-panel">' +
' <div class="ana-head"><span>人员数据</span>' +
' <select id="ana-year-person" class="ana-year"><option value="">全部年份</option></select></div>' +
' <div class="ana-body">' +
' <div id="ana-chart-person" class="ana-chart"></div>' +
' <div class="ana-detail"><div class="ana-detail-title" id="ana-detail-title-p">渠道明细</div>' +
' <table class="grid" style="font-size:12px;"><thead><tr><th>来源</th><th>数量</th></tr></thead>' +
' <tbody id="ana-detail-p"></tbody></table></div>' +
' </div>' +
' </div>' +
' </div>' +
'</div>' +
'<div class="card" style="margin-top:14px;">' +
' <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">' +
' <b style="font-size:15px;color:#1e2a3a;">渠道新增计划</b>' +
' <span style="font-size:12px;color:#8a94a6;">根据历史数据有计划地拓展高效渠道</span>' +
' </div>' +
' <div class="toolbar">' +
' <input type="text" data-filter="keyword" placeholder="渠道名称/联系人关键词">' +
' <input type="text" data-filter="channel_type" placeholder="渠道类型">' +
' <select id="year-select" data-filter="year"><option value="">选择年度</option></select>' +
' <button class="btn btn-primary" id="btn-search">搜索</button>' +
' <button class="btn" id="btn-reset">重置</button>' +
' <input type="text" data-filter="keyword" placeholder="来源详情/行业/备注关键词">' +
' <select data-filter="channel_type"><option value="">全部渠道类别</option></select>' +
' <select data-filter="status"><option value="">全部状态</option>' +
' <option value="待启动">待启动</option><option value="已执行">已执行</option><option value="错过">错过</option></select>' +
' <button class="btn btn-primary" id="btn-plan-search">搜索</button>' +
' <button class="btn" id="btn-plan-reset">重置</button>' +
' <span class="spacer"></span>' +
' <button class="btn btn-success" id="btn-add">+ 新增</button>' +
' <button class="btn btn-success" id="btn-plan-add">+ 新增计划</button>' +
' </div>' +
' <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></tr></thead>' +
' <tbody id="channel-tbody"></tbody>' +
' <thead><tr><th>ID</th><th>渠道类别</th><th>来源详情</th><th>所属行业</th><th>时间窗口</th><th>剩余天数</th><th>备注说明</th><th>状态反馈</th><th>创建时间</th><th>操作</th></tr></thead>' +
' <tbody id="plan-tbody"></tbody>' +
' </table></div>' +
' <div class="pagination" id="pagination"></div>' +
' <div class="pagination" id="plan-pagination"></div>' +
'</div>'
);
}
/* ---------- 列表 ---------- */
function loadList(page) {
/* ============================================================
* 1) 渠道效能分析
* ============================================================ */
function loadAnalysis() {
var year = null;
var $c = $('#ana-year-company');
var $p = $('#ana-year-person');
if ($c.length && $p.length) {
var yc = $c.val();
var yp = $p.val();
year = (yc === yp) ? yc : ''; // 两板块年份不同时,分别请求
}
if (year !== null && year === '' && $c.length && $p.length && $c.val() !== $p.val()) {
// 两板块各自请求
httpGet('channel/analysis.php', { year: $c.val() || '' }).then(function (d) {
analysisData = d;
renderAnalysis(d, 'company');
fillYears(d, 'company');
});
httpGet('channel/analysis.php', { year: $p.val() || '' }).then(function (d) {
renderAnalysis(d, 'person');
fillYears(d, 'person');
});
return;
}
httpGet('channel/analysis.php', { year: year || '' }).then(function (d) {
analysisData = d;
fillYears(d, 'company');
fillYears(d, 'person');
renderAnalysis(d, 'company');
renderAnalysis(d, 'person');
});
}
function fillYears(d, side) {
var $sel = side === 'company' ? $('#ana-year-company') : $('#ana-year-person');
var cur = $sel.val();
var html = '<option value="">全部年份</option>';
(d.years || []).forEach(function (y) {
html += '<option value="' + y + '"' + (String(y) === String(cur) ? ' selected' : '') + '>' + y + '年</option>';
});
$sel.html(html);
}
function renderAnalysis(d, side) {
var rows = side === 'company' ? (d.company || []) : (d.person || []);
var chartId = side === 'company' ? 'ana-chart-company' : 'ana-chart-person';
var detailBodyId = side === 'company' ? 'ana-detail-c' : 'ana-detail-p';
var detailTitleId = side === 'company' ? 'ana-detail-title-c' : 'ana-detail-title-p';
// 饼图数据(按百分比排序)
var pieData = rows.map(function (r) {
return { name: r.channel, value: r.count, percent: r.percent };
}).sort(function (a, b) { return b.value - a.value; });
if (!charts[side]) {
charts[side] = echarts.init(document.getElementById(chartId));
}
var chart = charts[side];
chart.clear();
chart.setOption({
tooltip: {
trigger: 'item',
formatter: function (p) {
if (p.data && p.data.percent !== undefined) {
return p.data.name + '<br/>数量:' + p.data.value + '(' + p.data.percent + '%)';
}
return p.name + '<br/>数量:' + p.value;
}
},
legend: { bottom: 0, type: 'scroll', textStyle: { fontSize: 11 } },
series: [{
type: 'pie',
radius: ['38%', '62%'],
center: ['50%', '44%'],
data: pieData,
label: { show: true, formatter: '{b}\n{d}%', fontSize: 11 },
emphasis: { itemStyle: { shadowBlur: 8, shadowColor: 'rgba(0,0,0,0.2)' } }
}]
});
// 默认显示第一名渠道的 source_detail TOP10
function renderDetail(index) {
var r = rows[index];
if (!r) {
$('#' + detailBodyId).html('<tr><td colspan="2" style="color:#b6bfcc;text-align:center;padding:16px;">暂无数据</td></tr>');
return;
}
$('#' + detailTitleId).text(r.channel + ' 来源明细(TOP' + Math.min(10, (r.details || []).length) + ',共 ' + r.count + ' 条)');
var html = '';
(r.details || []).forEach(function (item) {
var src = item.source || '';
var short = src.length > 8 ? src.substring(0, 8) + '…' : src;
html += '<tr><td title="' + escHtml(src) + '">' + escHtml(short) + '</td><td>' + (item.count !== undefined ? item.count : item.cnt) + '</td></tr>';
});
if (!(r.details || []).length) {
html = '<tr><td colspan="2" style="color:#b6bfcc;text-align:center;padding:16px;">暂无明细</td></tr>';
}
$('#' + detailBodyId).html(html);
}
renderDetail(0);
// 悬停饼图区域 -> 显示该渠道 TOP10 表格
chart.off('mouseover');
chart.on('mouseover', function (p) {
if (p.dataIndex !== undefined) {
renderDetail(p.dataIndex);
}
});
chart.off('mouseout');
chart.on('mouseout', function () {
renderDetail(0);
});
// 空数据提示
if (!rows.length) {
$('#' + detailBodyId).html('<tr><td colspan="2" style="color:#b6bfcc;text-align:center;padding:16px;">暂无渠道数据(企业/人员需填写来源渠道)</td></tr>');
}
}
$('#ana-year-company, #ana-year-person').on('change', function () {
loadAnalysis();
});
/* ============================================================
* 2) 渠道新增计划
* ============================================================ */
function loadPlans(page) {
currentPage = page;
var params = buildFilterParam();
delete params.year; // 年度仅用于统计
params.page = page;
params.limit = PAGE_SIZE;
httpGet('channel/list.php', params).then(function (data) {
httpGet('channel/plan_list.php', params).then(function (data) {
var rows = data.list || [];
pageRows = rows;
var html = '';
rows.forEach(function (r) {
var win = r.start_date && r.end_date
? fmtWin(r.start_date) + ' 至 ' + fmtWin(r.end_date)
: (r.start_date || r.end_date || '-');
html += '<tr>' +
'<td>' + r.id + '</td>' +
'<td>' + escHtml(r.channel_name) + '</td>' +
'<td>' + escHtml(r.channel_type || '-') + '</td>' +
'<td>' + escHtml(r.contact_person || '-') + '</td>' +
'<td>' + escHtml(r.contact_phone || '-') + '</td>' +
'<td>' + escHtml(r.contact_email || '-') + '</td>' +
'<td>' + escHtml(r.efficiency_score) + '</td>' +
'<td>' + escHtml(r.channel_type) + '</td>' +
'<td title="' + escHtml(r.source_detail || '') + '">' + escHtml((r.source_detail || '-').substring(0, 16)) + '</td>' +
'<td>' + escHtml(r.industry || '-') + '</td>' +
'<td>' + escHtml(win) + '</td>' +
'<td>' + (r.remaining_days > 0 ? '<span class="tag' + (r.remaining_days <= 7 ? ' tag-red' : '') + '">' + r.remaining_days + ' 天</span>' : '<span class="tag tag-gray">已到期</span>') + '</td>' +
'<td title="' + escHtml(r.remark || '') + '">' + escHtml((r.remark || '-').substring(0, 20)) + '</td>' +
'<td>' + statusTag(r.status) + '</td>' +
'<td>' + fmtDate(r.created_at) + '</td>' +
'<td class="ops"><a onclick="editChannel(' + r.id + ')">编辑</a>' +
'<a class="danger" onclick="delChannel(' + r.id + ')">删除</a></td></tr>';
'<td class="ops"><a onclick="editPlan(' + r.id + ')">编辑</a>' +
'<a class="danger" onclick="delPlan(' + r.id + ')">删除</a></td></tr>';
});
if (!rows.length) {
html = '<tr><td colspan="10" class="empty-tip" style="padding:40px 0;">暂无渠道数据(渠道表为新增加的业务表,可在 sql/system_tables.sql 中创建)</td></tr>';
html = '<tr><td colspan="10" class="empty-tip" style="padding:40px 0;">暂无渠道新增计划</td></tr>';
}
$('#channel-tbody').html(html);
renderPagination($('#pagination'), data, loadList);
$('#plan-tbody').html(html);
renderPagination($('#plan-pagination'), data, loadPlans);
});
}
/* ---------- 年度统计 ---------- */
function loadStats() {
httpGet('channel/stats.php').then(function (data) {
var years = data.years || [];
var html = '<option value="">选择年度</option>';
years.forEach(function (y) {
html += '<option value="' + y + '"' + (y == data.year ? ' selected' : '') + '>' + y + '年</option>';
});
$('#year-select').html(html);
$('#stat-summary').html(
'<div class="stat-card c-blue"><div class="num">' + data.summary.channel_count + '</div><div class="label">' + data.year + '年渠道数</div></div>' +
'<div class="stat-card c-green"><div class="num">' + data.summary.avg_score + '</div><div class="label">平均效率评分</div></div>' +
'<div class="stat-card c-orange"><div class="num">' + data.summary.max_score + '</div><div class="label">最高评分</div></div>' +
'<div class="stat-card c-red"><div class="num">' + data.summary.min_score + '</div><div class="label">最低评分</div></div>'
);
window._channelStatData = data;
});
/** 时间窗口显示格式:2025-4-1 */
function fmtWin(d) {
if (!d) return '';
var parts = String(d).split('-');
if (parts.length === 3) {
return parseInt(parts[0], 10) + '-' + parseInt(parts[1], 10) + '-' + parseInt(parts[2], 10);
}
return d;
}
$('#year-select').on('change', function () {
var y = $(this).val();
if (!y) return;
httpGet('channel/stats.php', { year: y }).then(function (data) {
$('#stat-summary').html(
'<div class="stat-card c-blue"><div class="num">' + data.summary.channel_count + '</div><div class="label">' + data.year + '年渠道数</div></div>' +
'<div class="stat-card c-green"><div class="num">' + data.summary.avg_score + '</div><div class="label">平均效率评分</div></div>' +
'<div class="stat-card c-orange"><div class="num">' + data.summary.max_score + '</div><div class="label">最高评分</div></div>' +
'<div class="stat-card c-red"><div class="num">' + data.summary.min_score + '</div><div class="label">最低评分</div></div>'
);
window._channelStatData = data;
});
function statusTag(s) {
if (s === '已执行') return '<span class="tag tag-green">已执行</span>';
if (s === '错过') return '<span class="tag tag-red">错过</span>';
return '<span class="tag">待启动</span>';
}
$('#btn-plan-search').on('click', function () { loadPlans(1); });
$('#btn-plan-reset').on('click', function () {
$('.toolbar [data-filter]').val('');
loadPlans(1);
});
/* ---------- 新增/编辑 ---------- */
$('#btn-add').on('click', function () { openForm(null); });
window.editChannel = function (id) {
/* ---------- 新增/编辑计划 ---------- */
$('#btn-plan-add').on('click', function () { openPlanForm(null); });
window.editPlan = function (id) {
var row = null;
pageRows.forEach(function (r) { if (r.id == id) row = r; });
openForm(row);
openPlanForm(row);
};
function openForm(ch) {
var isEdit = !!ch;
var c = ch || {};
function openPlanForm(pl) {
var isEdit = !!pl;
var r = pl || {};
// 渠道类别下拉(来自 source_channels 字典)
var chHtml = '<option value="">请选择渠道类别</option>';
var hasCur = false;
if (window._channelTypes && window._channelTypes.length) {
window._channelTypes.forEach(function (c) {
var sel = (c === r.channel_type) ? ' selected' : '';
if (sel) hasCur = true;
chHtml += '<option value="' + escHtml(c) + '"' + sel + '>' + escHtml(c) + '</option>';
});
}
if (r.channel_type && !hasCur) {
chHtml += '<option value="' + escHtml(r.channel_type) + '" selected>' + escHtml(r.channel_type) + '</option>';
}
var content =
'<form id="channel-form" style="padding:18px 22px 4px;">' +
'<form id="plan-form" style="padding:18px 22px 4px;">' +
'<div class="form-grid">' +
' <div class="form-item"><label>渠道名称<span class="req">*</span></label><input type="text" name="channel_name" value="' + escHtml(c.channel_name) + '"></div>' +
' <div class="form-item"><label>渠道类型</label><input type="text" name="channel_type" value="' + escHtml(c.channel_type) + '" placeholder="线上/线下/展会/转介绍"></div>' +
' <div class="form-item"><label>联系人</label><input type="text" name="contact_person" value="' + escHtml(c.contact_person) + '"></div>' +
' <div class="form-item"><label>联系电话</label><input type="text" name="contact_phone" value="' + escHtml(c.contact_phone) + '"></div>' +
' <div class="form-item"><label>联系邮箱</label><input type="text" name="contact_email" value="' + escHtml(c.contact_email) + '"></div>' +
' <div class="form-item"><label>效率评分(0-100)</label><input type="number" name="efficiency_score" min="0" max="100" step="0.01" value="' + escHtml(c.efficiency_score) + '"></div>' +
' <div class="form-item full"><label>备注</label><textarea name="remark">' + escHtml(c.remark) + '</textarea></div>' +
' <div class="form-item"><label>渠道类别<span class="req">*</span></label><select name="channel_type">' + chHtml + '</select></div>' +
' <div class="form-item"><label>所属行业</label><input type="text" name="industry" value="' + escHtml(r.industry || '') + '"></div>' +
' <div class="form-item"><label>来源详情</label><input type="text" name="source_detail" value="' + escHtml(r.source_detail || '') + '" placeholder="如:华南工博会"></div>' +
' <div class="form-item"><label>状态反馈</label><select name="status">' +
' <option value="待启动"' + (!r.status || r.status === '待启动' ? ' selected' : '') + '>待启动</option>' +
' <option value="已执行"' + (r.status === '已执行' ? ' selected' : '') + '>已执行</option>' +
' <option value="错过"' + (r.status === '错过' ? ' selected' : '') + '>错过</option></select></div>' +
' <div class="form-item"><label>开始日期</label><input type="date" name="start_date" value="' + escHtml(r.start_date || '') + '"></div>' +
' <div class="form-item"><label>结束日期</label><input type="date" name="end_date" value="' + escHtml(r.end_date || '') + '"></div>' +
' <div class="form-item full"><label>备注说明</label><input type="text" name="remark" value="' + escHtml(r.remark || '') + '"></div>' +
'</div>' +
'<div class="dialog-footer"><button type="button" class="btn" id="form-cancel">取消</button>' +
'<div class="dialog-footer"><button type="button" class="btn" id="plan-form-cancel">取消</button>' +
'<button type="submit" class="btn btn-primary">保存</button></div>' +
'</form>';
var idx = Dialog.open({
title: isEdit ? '编辑渠道' : '新增渠道',
title: isEdit ? '编辑计划' : '新增计划',
area: ['680px', 'auto'],
content: content,
btn: false
});
$('#form-cancel').on('click', function () { Dialog.close(idx); });
$('#plan-form-cancel').on('click', function () { Dialog.close(idx); });
$('#channel-form').on('submit', function (e) {
$('#plan-form').on('submit', function (e) {
e.preventDefault();
var data = {};
$(this).find('input, textarea').each(function () {
$(this).find('input, select').each(function () {
var name = $(this).attr('name');
if (!name) return;
data[name] = $(this).val();
});
if (!data.channel_name) { Dialog.error('渠道名称为必填项'); return; }
if (isEdit) data.id = c.id;
if (!data.channel_type) { Dialog.error('渠道类别为必填项'); return; }
if (data.start_date && data.end_date && data.start_date > data.end_date) {
Dialog.error('开始日期不能晚于结束日期');
return;
}
if (isEdit) data.id = r.id;
httpPost(isEdit ? 'channel/save.php' : 'channel/save.php', data).then(function () {
httpPost('channel/plan_save.php', data).then(function () {
Dialog.success('保存成功', function () {
Dialog.close(idx);
loadList(currentPage);
loadStats();
loadPlans(currentPage);
});
});
});
}
window.delChannel = function (id) {
Dialog.confirm('确定删除该渠道吗?', function () {
httpPost('channel/delete.php', { id: id }).then(function () {
window.delPlan = function (id) {
Dialog.confirm('确定删除该计划吗?', function () {
httpPost('channel/plan_delete.php', { id: id }).then(function () {
Dialog.success('删除成功', function () {
loadList(currentPage);
loadStats();
loadPlans(currentPage);
});
});
});
};
/* ---------- 初始化渠道类别下拉(新增计划用 + 列表筛选) ---------- */
httpGet('common/dicts.php', { type: 'source_channel' }).then(function (d) {
var types = (d.source_channels || []).map(function (s) { return s.name; });
window._channelTypes = types;
var html = '<option value="">全部渠道类别</option>';
types.forEach(function (t) {
html += '<option value="' + escHtml(t) + '">' + escHtml(t) + '</option>';
});
$('.toolbar select[data-filter="channel_type"]').html(html);
}).catch(function () {});
});
+419 -66
View File
@@ -1,12 +1,24 @@
/**
* company.js - 企业数据管理逻辑(列表+筛选+分页 / 新增编辑弹窗 / 导入导出 / 详情)
* company.js - 企业数据管理逻辑(列表+筛选+分页 / 新增编辑弹窗 / 产品 / 官媒 / 导入导出 / 详情)
* v1.0.18:
* - 检索区:筛选字段下拉(全部字段/指定字段,与检索词联动)+ 行业下拉 + 企业类型下拉(去掉国家/地区框)
* - 新增/编辑:Tab 卡改为 基本信息|财务信息|资质认证;基本信息内左侧文字锚导航(工商信息/联系方式/行业归属/业务类型/来源)
* - 表单字段:公司名称→企业名称、法律形式→企业类型(字典下拉)、删除英文名称;资质认证增加 级别/颁证机构
* - 产品弹窗:展示 产品品类/基本定位/包含系列/状态/更新日期 + 新增产品按钮(EAV 参数)
* - 新增「官媒」入口:公司媒体清单 + 新增官媒按钮
*/
$(function () {
renderShell('企业数据', '数据管理 / 企业数据');
renderPage();
initPage('company', function (user) {
if (!checkPagePermission('company')) return;
loadList(1);
loadDicts().then(function (dicts) {
window._companyDicts = dicts;
fillSearchDicts(dicts);
loadList(1);
}).catch(function () {
loadList(1);
});
});
var currentPage = 1;
@@ -16,13 +28,16 @@ $(function () {
$('#page-content').html(
'<div class="card">' +
' <div class="toolbar">' +
' <input type="text" data-filter="keyword" placeholder="公司名/注册号关键词">' +
' <input type="text" data-filter="industry" placeholder="行业(精确匹配)">' +
' <select data-filter="business_role"><option value="">全部业务角色</option>' +
' <option value="manufacturer">生产型</option><option value="integrator">集成商</option>' +
' <option value="distributor">代理商</option><option value="brand_operator">品牌运营</option>' +
' <option value="others">其他</option></select>' +
' <input type="text" data-filter="country" placeholder="国家/地区">' +
' <select id="search-field"><option value="">全部字段</option>' +
' <option value="name_zh">企业名称</option><option value="name_en">英文名称</option>' +
' <option value="registration_number">注册号</option><option value="legal_representative">法定代表人</option>' +
' <option value="address">注册地址</option><option value="industry">行业</option>' +
' <option value="industry_subdivision">行业细分</option><option value="website">官网</option>' +
' <option value="source_channel">来源渠道</option><option value="source_detail">来源详情</option>' +
' <option value="business_scope">经营范围</option><option value="stock_code">股票代码</option></select>' +
' <input type="text" id="search-keyword" placeholder="输入要检索的内容">' +
' <select id="search-industry" data-filter="industry"><option value="">全部行业</option></select>' +
' <select id="search-company-type" data-filter="company_type"><option value="">全部企业类型</option></select>' +
' <button class="btn btn-primary" id="btn-search">搜索</button>' +
' <button class="btn" id="btn-reset">重置</button>' +
' <span class="spacer"></span>' +
@@ -31,7 +46,7 @@ $(function () {
' <button class="btn" id="btn-export">导出</button>' +
' </div>' +
' <div class="table-wrap"><table class="grid">' +
' <thead><tr><th><input type="checkbox" id="check-all"></th><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><input type="checkbox" id="check-all"></th><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>' +
' <tbody id="company-tbody"></tbody>' +
' </table></div>' +
' <div class="pagination" id="pagination"></div>' +
@@ -40,12 +55,36 @@ $(function () {
$('#check-all').on('change', function () { $('.row-check').prop('checked', this.checked); });
}
/** 填充检索区字典下拉(行业/企业类型) */
function fillSearchDicts(dicts) {
var indHtml = '<option value="">全部行业</option>';
(dicts.industries || []).forEach(function (i) {
indHtml += '<option value="' + escHtml(i.name) + '">' + escHtml(i.name) + '</option>';
});
$('#search-industry').html(indHtml);
var ctHtml = '<option value="">全部企业类型</option>';
(dicts.company_types || []).forEach(function (t) {
ctHtml += '<option value="' + escHtml(t.name) + '">' + escHtml(t.name) + '</option>';
});
$('#search-company-type').html(ctHtml);
}
/* ---------- 列表 ---------- */
function loadList(page) {
currentPage = page;
var params = buildFilterParam();
params.page = page;
params.limit = PAGE_SIZE;
var params = {
field: $('#search-field').val(),
keyword: $.trim($('#search-keyword').val()),
industry: $('#search-industry').val(),
company_type: $('#search-company-type').val(),
page: page,
limit: PAGE_SIZE
};
// 空值不传
Object.keys(params).forEach(function (k) {
if (params[k] === '') delete params[k];
});
httpGet('company/list.php', params).then(function (data) {
var rows = data.list || [];
@@ -67,6 +106,7 @@ $(function () {
' <a onclick="viewCompany(' + r.id + ')">详情</a>' +
' <a onclick="viewEmployees(' + r.id + ')">员工</a>' +
' <a onclick="viewProducts(' + r.id + ')">产品</a>' +
' <a onclick="viewOfficialMedia(' + r.id + ')">官媒</a>' +
' <a onclick="editCompany(' + r.id + ')">编辑</a>' +
'</td></tr>';
});
@@ -80,8 +120,12 @@ $(function () {
/* ---------- 筛选 ---------- */
$('#btn-search').on('click', function () { loadList(1); });
$('#search-keyword').on('keydown', function (e) { if (e.keyCode === 13) loadList(1); });
$('#btn-reset').on('click', function () {
$('.toolbar [data-filter]').val('');
$('#search-field').val('');
$('#search-keyword').val('');
$('#search-industry').val('');
$('#search-company-type').val('');
loadList(1);
});
@@ -89,22 +133,24 @@ $(function () {
$('#btn-add').on('click', function () {
httpGet('company/cert_types.php').then(function (d) {
loadDicts().then(function (dicts) {
openForm(null, [], [], d.types || [], dicts);
window._companyDicts = dicts;
openForm(null, [], [], [], d.types || [], dicts);
});
});
});
window.editCompany = function (id) {
httpGet('company/detail.php', { id: id }).then(function (d) {
loadDicts().then(function (dicts) {
openForm(d.company, d.financials, d.certifications, d.certification_types, dicts);
window._companyDicts = dicts;
openForm(d.company, d.financials, d.certifications, d.accounts || [], d.certification_types, dicts);
});
});
};
/** 拉取数据字典(国家/行业/来源渠道),供表单下拉使用 */
/** 拉取数据字典(国家/行业/来源渠道/企业类型),供表单与检索使用 */
function loadDicts() {
return httpGet('common/dicts.php', { type: 'all' }).catch(function () {
return { countries: [], industries: [], source_channels: [] };
return { countries: [], industries: [], source_channels: [], company_types: [] };
});
}
@@ -124,45 +170,91 @@ $(function () {
return html;
}
function openForm(company, financials, certifications, certTypes, dicts) {
/* ============================================================
* 新增/编辑弹窗:Tab = 基本信息 | 财务信息 | 资质认证
* 基本信息内左侧文字锚导航:工商信息 / 联系方式 / 行业归属 / 业务类型 / 来源
* ============================================================ */
function openForm(company, financials, certifications, accounts, certTypes, dicts) {
var isEdit = !!company;
var c = company || {};
financials = financials || [];
certifications = certifications || [];
accounts = accounts || [];
certTypes = certTypes || [];
dicts = dicts || {};
// Tab1 工商信息
// ---- 基本信息 Tab(含左侧锚导航 + 5 个分区) ----
var secBiz =
'<div class="form-sec" id="sec-biz">' +
' <div class="form-sec-title">工商信息</div>' +
' <div class="form-grid">' +
' <div class="form-item"><label>企业名称<span class="req">*</span></label>' +
' <div style="display:flex;gap:8px;align-items:center;">' +
' <input type="text" name="name_zh" value="' + escHtml(c.name_zh) + '" style="width:60%;flex:0 0 auto;">' +
' <button type="button" class="btn btn-sm" id="btn-gongshang-query" title="预留:后续接入工商查询API,自动填写企业名称/注册号/注册地址等">工商查询</button>' +
' </div></div>' +
' <div class="form-item"><label>国家/地区</label><select name="country">' + dictOptions(dicts.countries, c.country) + '</select></div>' +
' <div class="form-item"><label>注册号</label><input type="text" name="registration_number" value="' + escHtml(c.registration_number) + '"></div>' +
' <div class="form-item"><label>注册地址</label><input type="text" name="address" value="' + escHtml(c.address) + '"></div>' +
' <div class="form-item"><label>企业类型</label><select name="legal_form">' + dictOptions(dicts.company_types, c.legal_form) + '</select></div>' +
' <div class="form-item"><label>法定代表人</label><input type="text" name="legal_representative" value="' + escHtml(c.legal_representative) + '"></div>' +
' <div class="form-item"><label>成立日期</label><input type="date" name="established_date" value="' + escHtml(c.established_date) + '"></div>' +
' <div class="form-item"><label>注册资本(万元)</label><input type="text" name="registered_capital" value="' + escHtml(c.registered_capital) + '"></div>' +
' <div class="form-item full"><label>经营范围</label><textarea name="business_scope">' + escHtml(c.business_scope) + '</textarea></div>' +
' </div></div>';
// 联系方式(social_accounts,整表替换)
var accountRowsHtml = '';
if (!accounts.length) {
accountRowsHtml = accountRowHtml(null);
} else {
accounts.forEach(function (a) { accountRowsHtml += accountRowHtml(a); });
}
var secContact =
'<div class="form-sec" id="sec-contact">' +
' <div class="form-sec-title">联系方式' +
' <button type="button" class="btn btn-sm" id="add-account-row" style="margin-left:10px;">+ 添加</button></div>' +
' <div id="account-rows">' + accountRowsHtml + '</div>' +
' <div style="font-size:12px;color:#8a94a6;margin-top:6px;">如:官网、公众号、微博、抖音、领英等平台账号</div>' +
'</div>';
var secIndustry =
'<div class="form-sec" id="sec-industry">' +
' <div class="form-sec-title">行业归属</div>' +
' <div class="form-grid">' +
' <div class="form-item"><label>行业</label><select name="industry">' + dictOptions(dicts.industries, c.industry) + '</select></div>' +
' <div class="form-item"><label>行业细分</label><input type="text" name="industry_subdivision" value="' + escHtml(c.industry_subdivision) + '"></div>' +
' <div class="form-item"><label>官网</label><input type="text" name="website" value="' + escHtml(c.website) + '"></div>' +
' </div></div>';
var secBizType =
'<div class="form-sec" id="sec-biztype">' +
' <div class="form-sec-title">业务类型</div>' +
' <div class="form-grid">' +
' <div class="form-item"><label>业务角色</label><select name="business_role">' + roleOptions(c.business_role) + '</select></div>' +
' </div></div>';
var secSource =
'<div class="form-sec" id="sec-source">' +
' <div class="form-sec-title">来源</div>' +
' <div class="form-grid">' +
' <div class="form-item"><label>来源渠道</label><select name="source_channel">' + dictOptions(dicts.source_channels, c.source_channel) + '</select></div>' +
' <div class="form-item"><label>来源详情</label><input type="text" name="source_detail" value="' + escHtml(c.source_detail) + '" placeholder="如:华南工博会"></div>' +
' </div></div>';
var bizHtml =
'<div class="form-grid">' +
' <div class="form-item"><label>公司名称<span class="req">*</span></label>' +
' <div style="display:flex;gap:8px;align-items:center;">' +
' <input type="text" name="name_zh" value="' + escHtml(c.name_zh) + '" style="width:60%;flex:0 0 auto;">' +
' <button type="button" class="btn btn-sm" id="btn-gongshang-query" title="预留:后续接入工商查询API,自动填写公司名称/注册号/注册地址等">工商查询</button>' +
' </div></div>' +
' <div class="form-item"><label>英文名称</label><input type="text" name="name_en" value="' + escHtml(c.name_en) + '"></div>' +
' <div class="form-item"><label>国家/地区</label><select name="country">' + dictOptions(dicts.countries, c.country) + '</select></div>' +
' <div class="form-item"><label>注册号</label><input type="text" name="registration_number" value="' + escHtml(c.registration_number) + '"></div>' +
' <div class="form-item"><label>注册地址</label><input type="text" name="address" value="' + escHtml(c.address) + '"></div>' +
' <div class="form-item"><label>法律形式</label><input type="text" name="legal_form" value="' + escHtml(c.legal_form) + '"></div>' +
' <div class="form-item"><label>法定代表人</label><input type="text" name="legal_representative" value="' + escHtml(c.legal_representative) + '"></div>' +
' <div class="form-item"><label>成立日期</label><input type="date" name="established_date" value="' + escHtml(c.established_date) + '"></div>' +
' <div class="form-item"><label>注册资本(万元)</label><input type="text" name="registered_capital" value="' + escHtml(c.registered_capital) + '"></div>' +
' <div class="form-item full"><label>经营范围</label><textarea name="business_scope">' + escHtml(c.business_scope) + '</textarea></div>' +
'<div class="form-anchor-layout">' +
' <div class="form-anchor-nav">' +
' <a href="#sec-biz" class="anchor-link active">工商信息</a>' +
' <a href="#sec-contact" class="anchor-link">联系方式</a>' +
' <a href="#sec-industry" class="anchor-link">行业归属</a>' +
' <a href="#sec-biztype" class="anchor-link">业务类型</a>' +
' <a href="#sec-source" class="anchor-link">来源</a>' +
' </div>' +
' <div class="form-anchor-body">' + secBiz + secContact + secIndustry + secBizType + secSource + '</div>' +
'</div>';
// Tab2 行业信息(业务角色/行业/行业细分/官网)
var indHtml =
'<div class="form-grid">' +
' <div class="form-item"><label>业务角色</label><select name="business_role">' + roleOptions(c.business_role) + '</select></div>' +
' <div class="form-item"><label>行业</label><select name="industry">' + dictOptions(dicts.industries, c.industry) + '</select></div>' +
' <div class="form-item"><label>行业细分</label><input type="text" name="industry_subdivision" value="' + escHtml(c.industry_subdivision) + '"></div>' +
' <div class="form-item"><label>官网</label><input type="text" name="website" value="' + escHtml(c.website) + '"></div>' +
' <div class="form-item"><label>来源渠道</label><select name="source_channel">' + dictOptions(dicts.source_channels, c.source_channel) + '</select></div>' +
' <div class="form-item"><label>来源详情</label><input type="text" name="source_detail" value="' + escHtml(c.source_detail) + '" placeholder="如:华南工博会"></div>' +
'</div>';
// Tab3 财务信息:顶部独立字段(是否上市/股票代码/最新员工/最新营收)+ 年度财务明细列表
// ---- 财务信息 Tab(保持不变) ----
var finRowsHtml = '';
if (!financials.length) {
finRowsHtml = finRowHtml(null);
@@ -187,7 +279,7 @@ $(function () {
' <tbody id="fin-tbody">' + finRowsHtml + '</tbody>' +
'</table></div>';
// Tab3 资质认证:列表形式(瞪羚企业等)
// ---- 资质认证 Tab(含级别/颁证机构) ----
var certRowsHtml = '';
if (!certifications.length) {
certRowsHtml = certRowHtml(null, certTypes);
@@ -206,14 +298,12 @@ $(function () {
'<form id="company-form" style="padding:14px 22px 4px;">' +
' <div class="edit-tabs">' +
' <div class="edit-tabs-head">' +
' <span class="edit-tab active" data-tab="biz" onclick="switchEditTab(this)">工商信息</span>' +
' <span class="edit-tab" data-tab="ind" onclick="switchEditTab(this)">行业信息</span>' +
' <span class="edit-tab active" data-tab="biz" onclick="switchEditTab(this)">基本信息</span>' +
' <span class="edit-tab" data-tab="fin" onclick="switchEditTab(this)">财务信息</span>' +
' <span class="edit-tab" data-tab="cert" onclick="switchEditTab(this)">资质认证</span>' +
' </div>' +
' <div class="edit-tabs-body">' +
' <div class="edit-pane" id="edit-pane-biz">' + bizHtml + '</div>' +
' <div class="edit-pane" id="edit-pane-ind" style="display:none;">' + indHtml + '</div>' +
' <div class="edit-pane" id="edit-pane-fin" style="display:none;">' + finHtml + '</div>' +
' <div class="edit-pane" id="edit-pane-cert" style="display:none;">' + certHtml + '</div>' +
' </div>' +
@@ -224,27 +314,29 @@ $(function () {
var idx = Dialog.open({
title: isEdit ? '编辑企业' : '新增企业',
area: ['820px', 'auto'],
area: ['860px', 'auto'],
content: content,
btn: false
});
bindAnchorNav();
$('#form-cancel').on('click', function () { Dialog.close(idx); });
$('#add-fin-row').on('click', function () { $('#fin-tbody').append(finRowHtml(null)); });
$('#add-cert-row').on('click', function () { $('#cert-rows').append(certRowHtml(null, certTypes)); });
$('#add-account-row').on('click', function () { $('#account-rows').append(accountRowHtml(null)); });
// 工商查询按钮(预留:后续接入工商查询API自动填写)
$('#btn-gongshang-query').on('click', function () {
Dialog.alert('工商查询API预留中,接入后可通过企业名称自动查询并填写公司名称、注册号、注册地址等信息');
Dialog.alert('工商查询API预留中,接入后可通过企业名称自动查询并填写企业名称、注册号、注册地址等信息');
});
$('#company-form').on('submit', function (e) {
e.preventDefault();
var fd = new FormData(this);
var nameZh = fd.get('name_zh');
var nameEn = fd.get('name_en');
if (isEdit) fd.delete('display_name'); // 编辑不允许改显示名称
if (!isEdit && !nameZh && !nameEn) {
Dialog.error('公司名称或英文名称至少填写一项');
if (!nameZh) {
Dialog.error('企业名称为必填项');
return;
}
@@ -271,7 +363,7 @@ $(function () {
});
fd.append('financials', JSON.stringify(financials));
// 资质认证(整表替换)
// 资质认证(整表替换,含级别/颁证机构)
var certs = [];
$('#cert-rows .cert-row').each(function () {
var $r = $(this);
@@ -280,12 +372,32 @@ $(function () {
certs.push({
certification_type_id: tid,
certificate_number: $r.find('[name="cert-number"]').val(),
level: $r.find('[name="cert-level"]').val(),
issuing_authority: $r.find('[name="cert-issuer"]').val(),
issue_date: $r.find('[name="cert-issue"]').val(),
expiry_date: $r.find('[name="cert-expiry"]').val()
});
});
fd.append('certifications', JSON.stringify(certs));
// 联系方式(整表替换)
var accounts = [];
$('#account-rows .account-row').each(function () {
var $r = $(this);
var platform = $.trim($r.find('[name="acc-platform"]').val());
var accountId = $.trim($r.find('[name="acc-account"]').val());
if (!platform || !accountId) return;
accounts.push({
platform: platform,
account_id: accountId,
profile_url: $r.find('[name="acc-url"]').val(),
remark: $r.find('[name="acc-remark"]').val(),
is_active: $r.find('[name="acc-active"]').val(),
is_defult: $r.find('[name="acc-defult"]').prop('checked') ? 1 : 0
});
});
fd.append('accounts', JSON.stringify(accounts));
httpPost(isEdit ? 'company/update.php' : 'company/add.php', fd, true).then(function () {
Dialog.success('保存成功', function () {
Dialog.close(idx);
@@ -304,6 +416,40 @@ $(function () {
$('#edit-pane-' + $(el).data('tab')).show();
};
/** 基本信息内左侧锚点导航:点击平滑滚动 + 高亮 */
function bindAnchorNav() {
$('.form-anchor-nav .anchor-link').off('click').on('click', function (e) {
e.preventDefault();
var $target = $($(this).attr('href'));
var $body = $target.closest('.edit-pane');
// 若锚点所在分区不在当前可见 Tab,先切到对应 Tab
if (!$body.is(':visible')) {
var tab = $body.attr('id').replace('edit-pane-', '');
var $tabEl = $('.edit-tabs-head .edit-tab[data-tab="' + tab + '"]');
if ($tabEl.length) switchEditTab($tabEl[0]);
}
var offset = $target.offset();
if (offset) {
$('.edit-tabs-body').animate({ scrollTop: $target.offset().top - $('.edit-tabs-body').offset().top + $('.edit-tabs-body').scrollTop() }, 200);
}
$('.form-anchor-nav .anchor-link').removeClass('active');
$(this).addClass('active');
});
// 滚动时高亮当前分区
var $secs = $('.form-anchor-body .form-sec');
$('.edit-tabs-body').off('scroll.anchor').on('scroll.anchor', function () {
var top = $(this).scrollTop() + 60;
var currentId = null;
$secs.each(function () {
if ($(this).position().top <= top) currentId = $(this).attr('id');
});
if (currentId) {
$('.form-anchor-nav .anchor-link').removeClass('active');
$('.form-anchor-nav .anchor-link[href="#' + currentId + '"]').addClass('active');
}
});
}
/** 年度财务明细行模板(列表形式,一行一个财年) */
function finRowHtml(f) {
f = f || {};
@@ -325,7 +471,7 @@ $(function () {
'</tr>';
}
/** 资质认证行模板 */
/** 资质认证行模板(含级别/颁证机构) */
function certRowHtml(ct, certTypes) {
ct = ct || {};
var opts = '<option value="">请选择认证类型</option>';
@@ -335,12 +481,35 @@ $(function () {
return '<div class="cert-row">' +
'<select name="cert-type">' + opts + '</select>' +
'<input type="text" name="cert-number" placeholder="证书编号" value="' + escHtml(ct.certificate_number || '') + '">' +
'<input type="text" name="cert-level" placeholder="级别" value="' + escHtml(ct.level || '') + '">' +
'<input type="text" name="cert-issuer" placeholder="颁证机构" value="' + escHtml(ct.issuing_authority || '') + '">' +
'<input type="date" name="cert-issue" value="' + escHtml(ct.issue_date || '') + '">' +
'<input type="date" name="cert-expiry" value="' + escHtml(ct.expiry_date || '') + '">' +
'<button type="button" class="btn btn-sm btn-danger" onclick="$(this).closest(\'.cert-row\').remove()">删除</button>' +
'</div>';
}
/** 联系方式行模板(企业 social_accounts) */
function accountRowHtml(a) {
a = a || {};
var empty = function (v) { return (v === null || v === undefined) ? '' : v; };
var platOpts = ['', '官网', '微信公众号', '微信', '微博', '抖音', '快手', 'B站', '小红书', '领英', 'Facebook', 'Twitter/X', 'YouTube', 'Instagram', '其他'];
var platHtml = '';
platOpts.forEach(function (p) {
platHtml += '<option value="' + escHtml(p) + '"' + (String(p) === String(a.platform || '') ? ' selected' : '') + '>' + (p || '平台') + '</option>';
});
return '<div class="account-row">' +
'<select name="acc-platform">' + platHtml + '</select>' +
'<input type="text" name="acc-account" placeholder="账号名称/ID" value="' + escHtml(empty(a.account_id)) + '">' +
'<input type="text" name="acc-url" placeholder="主页链接" value="' + escHtml(empty(a.profile_url)) + '">' +
'<input type="text" name="acc-remark" placeholder="备注" value="' + escHtml(empty(a.remark)) + '">' +
'<select name="acc-active" title="状态"><option value="1"' + (a.is_active !== 0 ? ' selected' : '') + '>启用</option>' +
'<option value="0"' + (a.is_active === 0 ? ' selected' : '') + '>停用</option></select>' +
'<label title="常用" style="font-size:12px;color:#5a6472;white-space:nowrap;"><input type="checkbox" name="acc-defult"' + (a.is_defult ? ' checked' : '') + '> 常用</label>' +
'<button type="button" class="btn btn-sm btn-danger" onclick="$(this).closest(\'.account-row\').remove()">删除</button>' +
'</div>';
}
function roleOptions(sel) {
var opts = ['', 'manufacturer', 'integrator', 'distributor', 'brand_operator', 'others'];
var labels = { 'manufacturer': '生产型', 'integrator': '集成商', 'distributor': '代理商', 'brand_operator': '品牌运营', 'others': '其他' };
@@ -402,7 +571,7 @@ $(function () {
'<div style="padding:18px 22px;">' +
' <h3 style="margin-bottom:14px;">' + escHtml(c.name_zh || c.name_en || '企业 #' + c.id) + '</h3>' +
' <div class="detail-grid" style="display:grid;grid-template-columns:1fr 1fr;gap:8px 24px;font-size:13px;">' +
detailRow('公司名称', c.name_zh) + detailRow('英文名称', c.name_en) +
detailRow('企业名称', c.name_zh) + detailRow('企业类型', c.legal_form) +
detailRow('业务角色', c.business_role) + detailRow('国家/地区', c.country) +
detailRow('注册号', c.registration_number) + detailRow('地址', c.address) +
detailRow('法定代表人', c.legal_representative) + detailRow('行业', c.industry) +
@@ -509,40 +678,224 @@ $(function () {
});
}
/* ---------- 产品品类弹窗(按企业查看产品品类,分页) ---------- */
/* ---------- 产品弹窗(产品品类/基本定位/包含系列/状态/更新日期 + 新增产品) ---------- */
var prodCompanyId = 0;
window.viewProducts = function (companyId) {
prodCompanyId = companyId;
var content =
'<div style="padding:14px 18px 6px;overflow:hidden;height:330px;">' +
'<div style="padding:14px 18px 6px;overflow:hidden;height:360px;">' +
' <div class="toolbar" style="margin-bottom:10px;">' +
' <span style="font-size:13px;color:#5a6472;">共 <b id="prod-total">0</b> 条产品记录</span>' +
' <span class="spacer"></span>' +
' <button class="btn btn-success btn-sm" id="btn-add-product">+ 新增产品</button>' +
' </div>' +
' <div class="table-wrap"><table class="grid" style="font-size:13px;">' +
' <thead><tr><th>品类名称</th><th>品类描述</th><th>是否核心</th><th>状态</th></tr></thead>' +
' <thead><tr><th>产品品类</th><th>基本定位</th><th>包含系列</th><th>状态</th><th>更新日期</th><th>参数</th></tr></thead>' +
' <tbody id="prod-tbody"></tbody>' +
' </table></div>' +
' <div class="pagination" id="prod-pagination"></div>' +
'</div>';
Dialog.open({ title: '产品品类', area: ['720px', 'auto'], content: content, btn: ['关闭'] });
Dialog.open({ title: '产品列表', area: ['860px', 'auto'], content: content, btn: ['关闭'] });
loadProducts(1);
$('#btn-add-product').on('click', function () { openProductForm(null); });
};
function loadProducts(page) {
httpGet('company/products.php', { company_id: prodCompanyId, page: page, limit: 5 }).then(function (data) {
$('#prod-total').text(data.total);
var rows = data.list || [];
var html = '';
rows.forEach(function (r) {
var attrHtml = (r.attrs && r.attrs.length)
? r.attrs.map(function (a) { return '<div>' + escHtml(a.attr_name) + (a.unit ? '(' + escHtml(a.unit) + ')' : '') + ':' + escHtml(a.value) + '</div>'; }).join('')
: '<span style="color:#b6bfcc;">-</span>';
html += '<tr>' +
'<td>' + escHtml(r.category_name) + '</td>' +
'<td>' + escHtml(r.category_description || '-') + '</td>' +
'<td>' + (r.is_core ? '<span class="tag">核心</span>' : '-') + '</td>' +
'<td title="' + escHtml(r.positioning || '') + '">' + escHtml((r.positioning || '-').substring(0, 24)) + '</td>' +
'<td>' + escHtml(r.series || '-') + '</td>' +
'<td>' + (r.is_active ? '<span class="tag tag-green">在产</span>' : '<span class="tag tag-gray">停产</span>') + '</td>' +
'<td>' + fmtDate(r.updated_at || r.created_at) + '</td>' +
'<td style="font-size:12px;">' + attrHtml + '</td>' +
'</tr>';
});
if (!rows.length) {
html = '<tr><td colspan="4" class="empty-tip" style="padding:30px 0;">暂无产品品类</td></tr>';
html = '<tr><td colspan="6" class="empty-tip" style="padding:30px 0;">暂无产品,点击右上角「+ 新增产品」添加</td></tr>';
}
$('#prod-tbody').html(html);
renderPagination($('#prod-pagination'), data, loadProducts);
});
}
/** 新增产品弹窗(含 EAV 参数,按企业所属行业加载参数属性定义) */
function openProductForm() {
// 获取企业行业
httpGet('company/detail.php', { id: prodCompanyId }).then(function (d) {
var industry = (d.company && d.company.industry) || '';
var content =
'<form id="product-form" style="padding:18px 22px 4px;">' +
'<div class="form-grid">' +
' <div class="form-item"><label>产品品类<span class="req">*</span></label><input type="text" name="category_name" placeholder="如:工业机器人、手机"></div>' +
' <div class="form-item"><label>包含系列</label><input type="text" name="series" placeholder="如:SUV、Gen 13"></div>' +
' <div class="form-item"><label>状态</label><select name="is_active"><option value="1" selected>在产</option><option value="0">停产</option></select></div>' +
' <div class="form-item"><label>是否核心</label><select name="is_core"><option value="0" selected>否</option><option value="1">是</option></select></div>' +
' <div class="form-item full"><label>产品基本定位</label><textarea name="positioning" placeholder="介绍这一款产品的商业定位"></textarea></div>' +
' <div class="form-item full"><label>品类描述</label><textarea name="category_description" placeholder="选填"></textarea></div>' +
'</div>' +
'<div id="product-attr-box" style="margin-top:6px;' + (industry ? '' : 'display:none;') + '">' +
' <b style="font-size:13px;color:#1e2a3a;">产品参数' + (industry ? '(' + escHtml(industry) + ')' : '') + '</b>' +
' <div id="product-attr-rows" style="margin-top:8px;"></div>' +
'</div>' +
'<div class="dialog-footer"><button type="button" class="btn" id="prod-form-cancel">取消</button>' +
'<button type="submit" class="btn btn-primary">保存</button></div>' +
'</form>';
var idx = Dialog.open({
title: '新增产品',
area: ['640px', 'auto'],
content: content,
btn: false
});
$('#prod-form-cancel').on('click', function () { Dialog.close(idx); });
// 加载该行业参数属性定义(EAV)
if (industry) {
httpGet('company/product_attrs.php', { industry: industry }).then(function (rd) {
var attrs = rd.attrs || [];
var html = '';
attrs.forEach(function (a) {
html += '<div class="form-item" style="width:calc(50% - 10px);display:inline-block;margin-right:10px;">' +
'<label>' + escHtml(a.attr_name) + (a.unit ? '(' + escHtml(a.unit) + ')' : '') + '</label>' +
'<input type="text" data-attr-id="' + a.id + '" data-attr-type="' + escHtml(a.attr_type) + '" placeholder="' +
(a.attr_type === 'boolean' ? '是/否' : a.attr_type === 'date' ? 'YYYY-MM-DD' : '') + '"></div>';
});
if (!attrs.length) {
html = '<div style="font-size:12px;color:#b6bfcc;">该行业暂无参数属性定义(可在 company_products_attr 表中维护)</div>';
}
$('#product-attr-rows').html(html);
}).catch(function () {});
}
$('#product-form').on('submit', function (e) {
e.preventDefault();
var data = {};
$(this).find('input, select, textarea').each(function () {
var name = $(this).attr('name');
if (name) data[name] = $(this).val();
});
if (!data.category_name) { Dialog.error('产品品类为必填项'); return; }
// 收集 EAV 参数值
var attrs = [];
$('#product-attr-rows input[data-attr-id]').each(function () {
var $this = $(this);
var val = $.trim($this.val());
if (!val) return;
attrs.push({ attr_id: $this.data('attr-id'), value: val });
});
data.company_id = prodCompanyId;
data.attrs = JSON.stringify(attrs);
httpPost('company/product_add.php', data).then(function () {
Dialog.success('新增成功', function () {
Dialog.close(idx);
loadProducts(1);
});
});
});
});
}
/* ---------- 官媒弹窗(该公司媒体清单 + 新增官媒) ---------- */
var mediaCompanyId = 0;
window.viewOfficialMedia = function (companyId) {
mediaCompanyId = companyId;
var content =
'<div style="padding:14px 18px 6px;overflow:hidden;height:360px;">' +
' <div class="toolbar" style="margin-bottom:10px;">' +
' <span style="font-size:13px;color:#5a6472;">共 <b id="media-total">0</b> 条官媒记录</span>' +
' <span class="spacer"></span>' +
' <button class="btn btn-success btn-sm" id="btn-add-media">+ 新增官媒</button>' +
' </div>' +
' <div class="table-wrap"><table class="grid" style="font-size:13px;">' +
' <thead><tr><th>平台</th><th>账号名称</th><th>ID</th><th>主页链接</th><th>状态</th><th>更新日期</th></tr></thead>' +
' <tbody id="media-tbody"></tbody>' +
' </table></div>' +
' <div class="pagination" id="media-pagination"></div>' +
'</div>';
Dialog.open({ title: '官媒列表', area: ['820px', 'auto'], content: content, btn: ['关闭'] });
loadOfficialMedia(1);
$('#btn-add-media').on('click', function () { openOfficialMediaForm(); });
};
function loadOfficialMedia(page) {
httpGet('company/official_media_list.php', { company_id: mediaCompanyId, page: page, limit: 5 }).then(function (data) {
$('#media-total').text(data.total);
var rows = data.list || [];
var html = '';
rows.forEach(function (r) {
html += '<tr>' +
'<td>' + escHtml(r.platform || '-') + '</td>' +
'<td>' + escHtml(r.account_id || '-') + '</td>' +
'<td>' + r.id + '</td>' +
'<td>' + (r.profile_url ? '<a href="' + escHtml(r.profile_url) + '" target="_blank">打开</a>' : '-') + '</td>' +
'<td>' + (r.is_active ? '<span class="tag tag-green">启用</span>' : '<span class="tag tag-gray">停用</span>') + '</td>' +
'<td>' + fmtDate(r.updated_at || r.created_at) + '</td>' +
'</tr>';
});
if (!rows.length) {
html = '<tr><td colspan="6" class="empty-tip" style="padding:30px 0;">暂无官媒,点击右上角「+ 新增官媒」添加</td></tr>';
}
$('#media-tbody').html(html);
renderPagination($('#media-pagination'), data, loadOfficialMedia);
});
}
/** 新增官媒弹窗 */
function openOfficialMediaForm() {
var content =
'<form id="media-form" style="padding:18px 22px 4px;">' +
'<div class="form-grid">' +
' <div class="form-item"><label>平台<span class="req">*</span></label><input type="text" name="platform" placeholder="如:微博、微信公众号、抖音"></div>' +
' <div class="form-item"><label>账号名称<span class="req">*</span></label><input type="text" name="account_id" placeholder="账号名称/ID"></div>' +
' <div class="form-item full"><label>主页链接</label><input type="text" name="profile_url" placeholder="https://..."></div>' +
' <div class="form-item full"><label>备注</label><input type="text" name="remark"></div>' +
' <div class="form-item"><label>状态</label><select name="is_active"><option value="1" selected>启用</option><option value="0">停用</option></select></div>' +
' <div class="form-item"><label>是否常用</label><select name="is_defult"><option value="0" selected>否</option><option value="1">是</option></select></div>' +
'</div>' +
'<div class="dialog-footer"><button type="button" class="btn" id="media-form-cancel">取消</button>' +
'<button type="submit" class="btn btn-primary">保存</button></div>' +
'</form>';
var idx = Dialog.open({
title: '新增官媒',
area: ['620px', 'auto'],
content: content,
btn: false
});
$('#media-form-cancel').on('click', function () { Dialog.close(idx); });
$('#media-form').on('submit', function (e) {
e.preventDefault();
var data = {};
$(this).find('input, select').each(function () {
var name = $(this).attr('name');
if (name) data[name] = $(this).val();
});
if (!data.platform || !data.account_id) { Dialog.error('平台与账号名称为必填项'); return; }
data.company_id = mediaCompanyId;
httpPost('company/official_media_add.php', data).then(function () {
Dialog.success('新增成功', function () {
Dialog.close(idx);
loadOfficialMedia(1);
});
});
});
}
});
+29 -10
View File
@@ -5,7 +5,7 @@ var BASE_URL = '/api/';
var PAGE_SIZE = 20;
/** 系统版本号(logo旁展示):修改代码后运行 tools/bump_version.php 自动递增 */
var APP_VERSION = 'v1.0.17';
var APP_VERSION = 'v1.0.19';
/** 页脚版权/备案信息(在 config.js 中修改) */
var FOOTER_TEXT = '© 2026 SuperLink 管理系统 版权所有 | 备案号:请替换为真实备案号';
@@ -38,9 +38,13 @@ var MENU_MAP = {
'preliminary': { name: '碎片处理', icon: 'preliminary', url: 'preliminary.html' },
'company': { name: '企业数据', icon: 'company', url: 'company.html' },
'person': { name: '人员数据', icon: 'person', url: 'person.html' },
'media': { name: '媒体数据', icon: 'media', url: 'media.html' },
'need': { name: '需求转盘', icon: 'need', url: 'need.html' },
'marketing': { name: '营销服务', icon: 'marketing', url: 'edm.html' },
'marketing': { name: '精准营销', icon: 'marketing', url: 'edm.html' },
'media_resources': { name: '媒体资源', icon: 'media', url: 'media_resources.html' },
'media_opinion': { name: '舆情监测', icon: 'media', url: 'media_opinion.html' },
'media_promo': { name: '推广服务', icon: 'media', url: 'media_promo.html' },
'competitor_analysis': { name: '竞品分析', icon: 'role', url: 'competitor_analysis.html' },
'competitor_data': { name: '竞品资料', icon: 'role', url: 'competitor_data.html' },
'channel': { name: '渠道管理', icon: 'channel', url: 'channel.html' },
'document': { name: '文件管理', icon: 'document', url: 'document.html' },
'system': { name: '权限管理', icon: 'system', url: 'user.html' },
@@ -58,16 +62,27 @@ var MENU_GROUPS = [
{
key: 'data', name: '数据管理', icon: 'data', children: [
{ key: 'company', name: '企业数据', icon: 'company', url: 'company.html' },
{ key: 'person', name: '人员数据', icon: 'person', url: 'person.html' },
{ key: 'media', name: '媒体数据', icon: 'media', url: 'media.html' }
{ key: 'person', name: '人员数据', icon: 'person', url: 'person.html' }
]
},
{ key: 'need', name: '需求转盘', icon: 'need', url: 'need.html' },
{
key: 'marketing', name: '营销服务', icon: 'marketing', children: [
key: 'marketing', name: '精准营销', icon: 'marketing', children: [
{ key: 'marketing', name: 'EDM', icon: 'marketing', url: 'edm.html' },
{ key: 'marketing', name: '数据定制', icon: 'marketing', url: 'data_custom.html' },
{ key: 'marketing', name: '媒体批发', icon: 'marketing', url: 'media_wholesale.html' }
{ key: 'need', name: '需求转盘', icon: 'need', url: 'need.html' }
]
},
{
key: 'media_mgmt', name: '媒体管理', icon: 'media', children: [
{ key: 'media_resources', name: '媒体资源', icon: 'media', url: 'media_resources.html' },
{ key: 'media_opinion', name: '舆情监测', icon: 'media', url: 'media_opinion.html' },
{ key: 'media_promo', name: '推广服务', icon: 'media', url: 'media_promo.html' }
]
},
{
key: 'competitor', name: '竞品管理', icon: 'role', children: [
{ key: 'competitor_analysis', name: '竞品分析', icon: 'role', url: 'competitor_analysis.html' },
{ key: 'competitor_data', name: '竞品资料', icon: 'role', url: 'competitor_data.html' }
]
},
{ key: 'channel', name: '渠道管理', icon: 'channel', url: 'channel.html' },
@@ -113,9 +128,13 @@ var PERMISSION_OPTIONS = [
{ key: 'preliminary', name: '碎片处理' },
{ key: 'company', name: '企业数据' },
{ key: 'person', name: '人员数据' },
{ key: 'media', name: '媒体数据' },
{ key: 'need', name: '需求转盘' },
{ key: 'marketing', name: '营销服务' },
{ key: 'marketing', name: '精准营销' },
{ key: 'media_resources', name: '媒体资源' },
{ key: 'media_opinion', name: '舆情监测' },
{ key: 'media_promo', name: '推广服务' },
{ key: 'competitor_analysis', name: '竞品分析' },
{ key: 'competitor_data', name: '竞品资料' },
{ key: 'channel', name: '渠道管理' },
{ key: 'document', name: '文件管理' },
{ key: 'system', name: '权限管理' },
+266 -71
View File
@@ -1,5 +1,10 @@
/**
* person.js - 人员数据管理逻辑(列表+筛选+分页 / 新增编辑弹窗 / 导入导出 / 详情)
* person.js - 人员数据管理逻辑(列表+筛选+分页 / 新增编辑弹窗 / 人脉 / 导入导出 / 详情)
* v1.0.18:
* - 新增「人脉」入口:与本人有交集(同事/老乡/校友)的人员列表,含亲密度百分比;点姓名反查详情
* - 新增/编辑表单分两个 Tab:基本信息 | 工作履历
* - 家乡/工作所在地:省-市两级联动(不细化区县),存储为「省-市」短结构(如 湖南衡阳 / 新疆乌鲁木齐)
* - 国籍为中国/中国香港/中国澳门/中国台湾时,家乡置灰不可填写
*/
$(function () {
renderShell('人员数据', '数据管理 / 人员数据');
@@ -64,6 +69,7 @@ $(function () {
'<td class="ops">' +
' <a onclick="viewPerson(' + r.id + ')">详情</a>' +
' <a onclick="viewResume(' + r.id + ')">履历</a>' +
' <a onclick="viewConnections(' + r.id + ')">人脉</a>' +
' <a onclick="editPerson(' + r.id + ')">编辑</a>' +
'</td></tr>';
});
@@ -81,14 +87,20 @@ $(function () {
loadList(1);
});
$('#btn-add').on('click', function () { openForm(null, null, null, null, null); });
window.editPerson = function (id) {
httpGet('person/detail.php', { id: id }).then(function (d) {
openForm(d.person, d.accounts, d.phone, d.email, d.experiences);
});
};
/* ============================================================
* 新增/编辑弹窗:Tab = 基本信息 | 工作履历
* ============================================================ */
var companyOptions = []; // 任职公司下拉选项
/** 手机号输入后自动识别归属地(录入手机号码时自动识别:国内-国家省-市;港澳台-中国香港/澳门/台湾;海外-国籍) */
function loadCompanyOptions() {
if (companyOptions.length) return $.Deferred().resolve(companyOptions).promise();
return httpGet('person/company_options.php', { limit: 500 }).then(function (d) {
companyOptions = d.list || [];
return companyOptions;
}).catch(function () { companyOptions = []; return companyOptions; });
}
/** 手机号输入后自动识别归属地(录入手机号码时自动识别:国内-省-市;港澳台-中国香港/澳门/台湾;海外-国籍) */
function bindPhoneGeo($phoneInput, $geoHint) {
var timer = null;
$phoneInput.on('blur', function () {
@@ -107,18 +119,25 @@ $(function () {
});
}
/** 省市区三级联动下拉(数据来自 date_dict_area 字典)
* value 格式:"省-市-区县"(直辖市省略市,如 "北京市-东城区") */
/** 名称去行政区后缀:湖南省->湖南,新疆维吾尔自治区->新疆,北京市->北京,石家庄市->石家庄 */
function stripAreaSuffix(name) {
return String(name || '')
.replace(/(维吾尔|壮族|回族)?自治区$/, '')
.replace(/省$/, '')
.replace(/特别行政区$/, '')
.replace(/市$/, '');
}
/** 省-市两级联动下拉(不细化区县;值存储为短结构,如 湖南衡阳 / 新疆乌鲁木齐 / 北京) */
function areaSelectsHtml(target, value) {
return '<div class="area-cascade" data-target="' + target + '" style="display:flex;gap:6px;">' +
'<select class="area-prov" style="flex:1;min-width:0;"><option value="">省</option></select>' +
'<select class="area-city" style="flex:1;min-width:0;"><option value="">市</option></select>' +
'<select class="area-dist" style="flex:1;min-width:0;"><option value="">区/县</option></select>' +
'</div>' +
'<input type="hidden" name="' + target + '" value="' + escHtml(value || '') + '">';
}
/** 构建省/市/区三级联动:填充选项并按已有值回选 */
/** 构建省/市两级联动:填充选项并按已有值回选(兼容旧格式:湖南衡阳 / 湖南省衡阳市 / 北京市-东城区) */
function initAreaCascade(areas) {
var provMap = {}; // code -> name
var childMap = {}; // parent_code -> [{code,name}]
@@ -144,69 +163,77 @@ $(function () {
html += '<option value="' + c.code + '">' + escHtml(c.name) + '</option>';
});
$box.find('.area-city').html(html);
$box.find('.area-dist').html('<option value="">区/县</option>');
}
function fillDist($box, cityCode) {
var html = '<option value="">区/县</option>';
(childMap[cityCode] || []).forEach(function (c) {
html += '<option value="' + c.code + '">' + escHtml(c.name) + '</option>';
});
$box.find('.area-dist').html(html);
}
function combine($box) {
var target = $box.data('target');
var provName = $box.find('.area-prov option:selected').text();
var cityName = $box.find('.area-city option:selected').text();
var distName = $box.find('.area-dist option:selected').text();
var parts = [];
if ($box.find('.area-prov').val()) parts.push(provName);
if ($box.find('.area-city').val() && cityName !== provName) parts.push(cityName);
if ($box.find('.area-dist').val()) parts.push(distName);
$box.closest('.form-item').find('input[name="' + target + '"]').val(parts.join('-'));
var provShort = provName ? stripAreaSuffix(provName) : '';
var cityShort = cityName ? stripAreaSuffix(cityName) : '';
var val = provShort;
// 直辖市(省/市同名)只存省
if (cityShort && cityShort !== provShort) val += cityShort;
$box.closest('.form-item').find('input[name="' + target + '"]').val(val);
}
/** 按短名匹配省(如 湖南 / 新疆 / 北京) */
function matchProv(shortName) {
var sorted = provCodes.slice().sort(function (a, b) { return b.length - a.length; });
for (var i = 0; i < sorted.length; i++) {
if (stripAreaSuffix(provMap[sorted[i]]) === shortName) return sorted[i];
}
return '';
}
/** 按短名匹配市 */
function matchCity(provCode, shortName) {
var list = childMap[provCode] || [];
for (var i = 0; i < list.length; i++) {
if (stripAreaSuffix(list[i].name) === shortName) return list[i].code;
}
return '';
}
/** 解析已有值 -> [省短名, 市短名](兼容 湖南衡阳 / 湖南省衡阳市 / 北京市-东城区 等格式) */
function parseValue(value) {
var v = String(value || '').trim();
if (!v) return ['', ''];
if (v.indexOf('-') >= 0) {
var parts = v.split('-');
return [stripAreaSuffix(parts[0]), parts[1] ? stripAreaSuffix(parts[1]) : ''];
}
// 无分隔符:最长省名前缀匹配
var sorted = provCodes.slice().sort(function (a, b) { return b.length - a.length; });
for (var i = 0; i < sorted.length; i++) {
var pShort = stripAreaSuffix(provMap[sorted[i]]);
if (pShort && v.indexOf(pShort) === 0) {
var rest = v.substring(pShort.length);
return [pShort, rest];
}
}
return ['', v];
}
$('.area-cascade').each(function () {
var $box = $(this);
var target = $box.data('target');
var current = $box.closest('.form-item').find('input[name="' + target + '"]').val() || '';
var parts = current.split('-');
var provName = parts[0] || '';
var cityName = parts[1] || '';
var distName = parts[2] || '';
var parsed = parseValue(current);
var provShort = parsed[0];
var cityShort = parsed[1];
fillProv($box);
// 回选省
var provCode = '';
provCodes.forEach(function (code) {
if (provMap[code] === provName) {
provCode = code;
$box.find('.area-prov').val(code);
}
});
var provCode = provShort ? matchProv(provShort) : '';
if (!provCode) return;
$box.find('.area-prov').val(provCode);
fillCity($box, provCode);
var cityList = childMap[provCode] || [];
var cityCode = '';
// 直接按市名匹配;直辖市(省/市同名,如"北京市-东城区")时 parts[1] 实为区县
var direct = cityList.filter(function (c) { return c.name === cityName; });
if (direct.length) {
cityCode = direct[0].code;
$box.find('.area-city').val(cityCode);
} else if (cityList.length === 1 && cityList[0].name === provName) {
cityCode = cityList[0].code;
$box.find('.area-city').val(cityCode);
if (parts[1] && !distName) {
distName = parts[1]; // 直辖市:第二段实为区县
}
}
if (!cityCode) return;
fillDist($box, cityCode);
(childMap[cityCode] || []).forEach(function (c) {
if (c.name === distName) $box.find('.area-dist').val(c.code);
});
// 直辖市:市=省本身
if (cityShort === provShort) {
var muniCode = matchCity(provCode, cityShort);
if (muniCode) $box.find('.area-city').val(muniCode);
return;
}
var cityCode = cityShort ? matchCity(provCode, cityShort) : '';
if (cityCode) $box.find('.area-city').val(cityCode);
});
// 联动事件
@@ -216,22 +243,44 @@ $(function () {
combine($box);
});
$('.area-cascade').off('change.areaCity').on('change.areaCity', '.area-city', function () {
var $box = $(this).closest('.area-cascade');
fillDist($box, $(this).val());
combine($box);
});
$('.area-cascade').off('change.areaDist').on('change.areaDist', '.area-dist', function () {
combine($(this).closest('.area-cascade'));
});
}
/** 国籍是否为中国大陆/港澳台(此时家乡置灰不可填) */
function isCnNationality(n) {
var v = String(n || '').trim();
return v === '中国' || v === '中国香港' || v === '中国澳门' || v === '中国台湾';
}
/** 工作履历行模板 */
function expRowHtml(e) {
e = e || {};
var empty = function (v) { return (v === null || v === undefined) ? '' : v; };
var opts = '<option value="">请选择公司</option>';
companyOptions.forEach(function (c) {
opts += '<option value="' + c.id + '"' + (String(c.id) === String(e.company_id) ? ' selected' : '') + '>' + escHtml(c.name) + '</option>';
});
return '<div class="exp-row">' +
'<select name="exp-company" style="width:180px;">' + opts + '</select>' +
'<input type="text" name="exp-position" placeholder="职位" value="' + escHtml(empty(e.position)) + '">' +
'<input type="text" name="exp-department" placeholder="部门" value="' + escHtml(empty(e.department)) + '">' +
'<input type="text" name="exp-level" placeholder="职级" value="' + escHtml(empty(e.job_level)) + '">' +
'<input type="date" name="exp-start" title="入职日期" value="' + escHtml(empty(e.start_date)) + '">' +
'<input type="date" name="exp-end" title="离职日期" value="' + escHtml(empty(e.end_date)) + '">' +
'<label title="当前在职" style="font-size:12px;color:#5a6472;white-space:nowrap;"><input type="checkbox" name="exp-current"' + (e.is_current ? ' checked' : '') + '> 在职</label>' +
'<button type="button" class="btn btn-sm btn-danger" onclick="$(this).closest(\'.exp-row\').remove()">删除</button>' +
'</div>';
}
function openForm(person, accounts, phone, email, experiences) {
var isEdit = !!person;
var p = person || {};
var pPhone = phone || '';
var pEmail = email || '';
var content =
'<form id="person-form" style="padding:18px 22px 4px;">' +
// Tab1 基本信息
var baseHtml =
'<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>性别</label><select name="gender">' +
@@ -239,28 +288,55 @@ $(function () {
' <option value="男"' + (p.gender === '男' ? ' selected' : '') + '>男</option>' +
' <option value="女"' + (p.gender === '女' ? ' selected' : '') + '>女</option>' +
' <option value="其他"' + (p.gender === '其他' ? ' selected' : '') + '>其他</option></select></div>' +
' <div class="form-item"><label>国籍</label><input type="text" name="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>' +
' <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_number" value="' + escHtml(p.id_number) + '"></div>' +
' <div class="form-item"><label>学历</label><input type="text" name="education" value="' + escHtml(p.education) + '"></div>' +
' <div class="form-item"><label>毕业院校</label><input type="text" name="graduated_from" value="' + escHtml(p.graduated_from) + '"></div>' +
' <div class="form-item"><label>家乡</label>' + areaSelectsHtml('hometown', p.hometown) + '</div>' +
' <div class="form-item"><label>工作所在地</label>' + areaSelectsHtml('work_location', p.work_location) + '</div>' +
' <div class="form-item"><label>家乡</label><div class="hometown-box">' + areaSelectsHtml('hometown', p.hometown) + '</div></div>' +
' <div class="form-item"><label>工作所在地</label><div class="workloc-box">' + areaSelectsHtml('work_location', p.work_location) + '</div></div>' +
' <div class="form-item"><label>手机</label>' +
' <div><input type="text" name="phone" id="person-phone" value="' + escHtml(pPhone) + '" placeholder="常用手机号">' +
' <div class="geo-hint" id="person-phone-geo" style="font-size:12px;color:#2f8f46;margin-top:4px;display:none;"></div></div></div>' +
' <div class="form-item"><label>邮箱</label><input type="text" name="email" value="' + escHtml(pEmail) + '" placeholder="常用邮箱"></div>' +
' <div class="form-item"><label>来源渠道</label><select name="source_channel" id="person-source-channel"><option value="">-</option></select></div>' +
' <div class="form-item"><label>来源详情</label><input type="text" name="source_detail" value="' + escHtml(p.source_detail) + '" placeholder="如:华南工博会"></div>' +
'</div>';
// Tab2 工作履历
var expRowsHtml = '';
if (!experiences || !experiences.length) {
expRowsHtml = expRowHtml(null);
} else {
experiences.forEach(function (e) { expRowsHtml += expRowHtml(e); });
}
var expHtml =
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">' +
' <b style="font-size:13px;color:#1e2a3a;">工作履历</b>' +
' <button type="button" class="btn btn-sm" id="add-exp-row">+ 添加履历</button>' +
'</div>' +
'<div id="exp-rows">' + expRowsHtml + '</div>';
var content =
'<form id="person-form" style="padding:14px 22px 4px;">' +
' <div class="edit-tabs">' +
' <div class="edit-tabs-head">' +
' <span class="edit-tab active" data-tab="base" onclick="switchPersonTab(this)">基本信息</span>' +
' <span class="edit-tab" data-tab="exp" onclick="switchPersonTab(this)">工作履历</span>' +
' </div>' +
' <div class="edit-tabs-body">' +
' <div class="edit-pane" id="edit-pane-base">' + baseHtml + '</div>' +
' <div class="edit-pane" id="edit-pane-exp" style="display:none;">' + expHtml + '</div>' +
' </div>' +
' </div>' +
'<div class="dialog-footer"><button type="button" class="btn" id="form-cancel">取消</button>' +
'<button type="submit" class="btn btn-primary">保存</button></div>' +
'</form>';
var idx = Dialog.open({
title: isEdit ? '编辑人员' : '新增人员',
area: ['720px', 'auto'],
area: ['760px', 'auto'],
content: content,
btn: false
});
@@ -268,13 +344,13 @@ $(function () {
// 手机号归属地自动识别
bindPhoneGeo($('#person-phone'), $('#person-phone-geo'));
// 加载国籍字典 + 行政区划字典(三级联动) + 来源渠道字典
// 加载国籍字典 + 行政区划字典(省-市两级) + 来源渠道字典 + 任职公司
httpGet('common/dicts.php', { type: 'all' }).then(function (d) {
var $dl = $('#person-country-list');
var html = '';
(d.countries || []).forEach(function (c) { html += '<option value="' + escHtml(c.name) + '"></option>'; });
$dl.html(html);
// 省市区三级联动
// 省-市两级联动
initAreaCascade(d.areas || []);
var $sc = $('#person-source-channel');
var scHtml = '<option value="">-</option>';
@@ -288,8 +364,39 @@ $(function () {
scHtml += '<option value="' + escHtml(p.source_channel) + '" selected>' + escHtml(p.source_channel) + '(自定义)</option>';
}
$sc.html(scHtml);
// 国籍变化 -> 家乡置灰/可用
function syncHometownDisabled() {
var n = $('#person-nationality').val();
var disabled = isCnNationality(n);
var $box = $('.hometown-box');
$box.find('select, input').prop('disabled', disabled);
$box.toggleClass('area-disabled', disabled);
if (disabled) {
$box.closest('.form-item').find('input[name="hometown"]').val('');
}
}
$('#person-nationality').off('input.home').on('input.home', syncHometownDisabled);
syncHometownDisabled();
}).catch(function () {});
// 任职公司选项
loadCompanyOptions().then(function () {
// 已有行重填公司下拉(若首次加载后追加的选项)
$('#exp-rows .exp-row select[name="exp-company"]').each(function () {
if ($(this).find('option').length <= 1) {
var cur = $(this).data('cur') || '';
var opts = '<option value="">请选择公司</option>';
companyOptions.forEach(function (c) {
opts += '<option value="' + c.id + '"' + (String(c.id) === String(cur) ? ' selected' : '') + '>' + escHtml(c.name) + '</option>';
});
$(this).html(opts);
}
});
});
$('#add-exp-row').on('click', function () { $('#exp-rows').append(expRowHtml(null)); });
$('#form-cancel').on('click', function () { Dialog.close(idx); });
$('#person-form').on('submit', function (e) {
@@ -313,6 +420,25 @@ $(function () {
data.contacts = JSON.stringify(contacts);
delete data.phone;
delete data.email;
// 工作履历(整表替换)
var exps = [];
$('#exp-rows .exp-row').each(function () {
var $r = $(this);
var companyId = $r.find('[name="exp-company"]').val();
if (!companyId) return;
exps.push({
company_id: companyId,
position: $r.find('[name="exp-position"]').val(),
department: $r.find('[name="exp-department"]').val(),
job_level: $r.find('[name="exp-level"]').val(),
start_date: $r.find('[name="exp-start"]').val(),
end_date: $r.find('[name="exp-end"]').val(),
is_current: $r.find('[name="exp-current"]').prop('checked') ? 1 : 0
});
});
data.experiences = JSON.stringify(exps);
if (isEdit) data.id = p.id;
httpPost(isEdit ? 'person/update.php' : 'person/add.php', data).then(function () {
@@ -324,6 +450,30 @@ $(function () {
});
}
/* ---------- 新增 / 编辑入口 ---------- */
window.addPerson = function () {
openForm(null, [], '', '', []);
};
window.editPerson = function (id) {
httpGet('person/detail.php', { id: id }).then(function (d) {
openForm(d.person, d.accounts || [], d.phone || '', d.email || '', d.experiences || []);
}).catch(function () {
Dialog.alert('加载人员详情失败');
});
};
$('#btn-add').on('click', function () { addPerson(); });
/* ---------- 人员弹窗 Tab 切换 ---------- */
window.switchPersonTab = function (el) {
var $tabs = $(el).closest('.edit-tabs');
$tabs.find('.edit-tab').removeClass('active');
$(el).addClass('active');
$tabs.find('.edit-pane').hide();
$('#edit-pane-' + $(el).data('tab')).show();
};
/* ---------- 导入 ---------- */
$('#btn-import').on('click', function () {
triggerFileInput('.csv', function (file) {
@@ -383,6 +533,51 @@ $(function () {
});
}
/* ---------- 人脉弹窗(同事/老乡/校友 + 亲密度) ---------- */
var connPersonId = 0;
window.viewConnections = function (personId) {
connPersonId = personId;
var content =
'<div style="padding:14px 18px 6px;overflow:hidden;height:420px;">' +
' <div style="margin-bottom:10px;font-size:13px;color:#5a6472;">共 <b id="conn-total">0</b> 条人脉(交集:工作履历/家乡/毕业院校)</div>' +
' <div class="table-wrap"><table class="grid" style="font-size:13px;">' +
' <thead><tr><th>姓名</th><th>联系方式</th><th>交集</th><th>亲密度</th></tr></thead>' +
' <tbody id="conn-tbody"></tbody>' +
' </table></div>' +
'</div>';
Dialog.open({ title: '人脉', area: ['760px', 'auto'], content: content, btn: ['关闭'] });
loadConnections();
};
function loadConnections() {
httpGet('person/connections.php', { person_id: connPersonId, limit: 50 }).then(function (data) {
var rows = data.list || [];
$('#conn-total').text(rows.length);
var html = '';
rows.forEach(function (r) {
var tagHtml = (r.tags || []).map(function (t) {
return '<span class="tag">' + escHtml(t) + '</span>';
}).join(' ');
html += '<tr>' +
'<td><a href="javascript:void(0)" class="link" onclick="viewPerson(' + r.id + ')">' + escHtml(r.full_name) + '</a></td>' +
'<td>' + escHtml(r.phone || '-') + '</td>' +
'<td>' + (tagHtml || '-') + '</td>' +
'<td style="white-space:nowrap;">' +
' <span style="color:#2a5298;font-weight:bold;">' + r.intimacy + '%</span>' +
' <span style="display:inline-block;width:70px;height:6px;background:#eef0f4;border-radius:3px;vertical-align:middle;margin-left:6px;overflow:hidden;">' +
' <span style="display:block;height:100%;width:' + r.intimacy + '%;background:#4fa3ff;border-radius:3px;"></span>' +
' </span>' +
'</td>' +
'</tr>';
});
if (!rows.length) {
html = '<tr><td colspan="4" class="empty-tip" style="padding:40px 0;">暂无相关人脉</td></tr>';
}
$('#conn-tbody').html(html);
});
}
/* ---------- 任职履历弹窗(按人员查看任职公司/履历,分页) ---------- */
var compPersonId = 0;
+20
View File
@@ -0,0 +1,20 @@
/**
* placeholder.js - 占位页面通用逻辑(媒体管理/竞品管理等未开发模块)
* 用法:renderPlaceholder('media_resources'); —— key 需在 MENU_MAP 中存在
*/
function renderPlaceholder(key) {
var m = MENU_MAP[key] || { name: key, icon: 'document' };
renderShell(m.name, '业务模块 / ' + m.name);
var html =
'<div class="card" style="padding:60px 20px;text-align:center;">' +
' <div style="width:64px;height:64px;margin:0 auto 16px;color:#c3cbd8;">' + (ICONS[m.icon] || '') + '</div>' +
' <div style="font-size:18px;color:#1e2a3a;font-weight:600;">' + escHtml(m.name) + '</div>' +
' <div style="font-size:13px;color:#8a94a6;margin-top:8px;">该模块正在建设中,敬请期待</div>' +
'</div>';
$('#page-content').html(html);
initPage(key, function (user) {
if (!checkPagePermission(key)) return;
});
}