v1.0.19: 修复人员编辑入口缺失(person.js addPerson/editPerson) + 渠道明细数量字段名(analysis.php cnt) + CDP全量验证43项PASS
This commit is contained in:
@@ -519,6 +519,141 @@ table.grid .ops a:hover { text-decoration: underline; }
|
||||
.cert-row input[type="text"] { width: 170px; }
|
||||
.cert-row input[type="date"] { width: 140px; }
|
||||
|
||||
/* ---------- 基本信息锚点导航布局(左侧文字锚 + 右侧分区) ---------- */
|
||||
.form-anchor-layout {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.form-anchor-nav {
|
||||
flex: 0 0 86px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
border-right: 1px solid #eef0f4;
|
||||
padding-right: 10px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
.form-anchor-nav .anchor-link {
|
||||
display: block;
|
||||
padding: 7px 10px;
|
||||
font-size: 13px;
|
||||
color: #5a6472;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.form-anchor-nav .anchor-link:hover { color: #2a5298; background: #f2f6fd; }
|
||||
.form-anchor-nav .anchor-link.active {
|
||||
color: #2a5298;
|
||||
background: #e8f0fe;
|
||||
font-weight: bold;
|
||||
}
|
||||
.form-anchor-body { flex: 1; min-width: 0; }
|
||||
.form-sec { padding: 4px 2px 14px; border-bottom: 1px dashed #eef0f4; }
|
||||
.form-sec:last-child { border-bottom: none; }
|
||||
.form-sec-title {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: #2a5298;
|
||||
margin-bottom: 10px;
|
||||
padding-left: 8px;
|
||||
border-left: 3px solid #2a5298;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 联系方式行(企业 social_accounts) */
|
||||
.account-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.account-row select, .account-row input {
|
||||
border: 1px solid #d9dee8;
|
||||
border-radius: 4px;
|
||||
padding: 7px 8px;
|
||||
font-size: 13px;
|
||||
background: #fff;
|
||||
}
|
||||
.account-row select[name="acc-platform"] { width: 110px; }
|
||||
.account-row input[name="acc-account"] { width: 150px; }
|
||||
.account-row input[name="acc-url"] { width: 210px; }
|
||||
.account-row input[name="acc-remark"] { width: 130px; }
|
||||
.account-row select[name="acc-active"] { width: 76px; }
|
||||
|
||||
/* ---------- 人员工作履历行 ---------- */
|
||||
.exp-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.exp-row select, .exp-row input {
|
||||
border: 1px solid #d9dee8;
|
||||
border-radius: 4px;
|
||||
padding: 7px 8px;
|
||||
font-size: 13px;
|
||||
background: #fff;
|
||||
}
|
||||
.exp-row input[type="text"] { width: 110px; }
|
||||
.exp-row input[type="date"] { width: 130px; }
|
||||
|
||||
/* 家乡置灰(中国/港澳台国籍时不可填) */
|
||||
.area-disabled select, .area-disabled input {
|
||||
background: #f2f3f5 !important;
|
||||
color: #b6bfcc !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 人脉弹窗姓名链接 */
|
||||
.link { color: #2a5298; text-decoration: underline; cursor: pointer; }
|
||||
.link:hover { color: #4fa3ff; }
|
||||
|
||||
/* ---------- 渠道管理:效能分析面板 ---------- */
|
||||
.ana-panel {
|
||||
flex: 1 1 46%;
|
||||
min-width: 340px;
|
||||
border: 1px solid #e5e9f0;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ana-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 14px;
|
||||
background: #f7f9fc;
|
||||
border-bottom: 1px solid #e5e9f0;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: #1e2a3a;
|
||||
}
|
||||
.ana-year {
|
||||
border: 1px solid #d9dee8;
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
background: #fff;
|
||||
}
|
||||
.ana-body { padding: 12px 14px; }
|
||||
.ana-chart { width: 100%; height: 260px; }
|
||||
.ana-detail {
|
||||
margin-top: 10px;
|
||||
border-top: 1px dashed #eef0f4;
|
||||
padding-top: 10px;
|
||||
}
|
||||
.ana-detail-title {
|
||||
font-size: 12px;
|
||||
color: #2a5298;
|
||||
font-weight: bold;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* ---------- 页脚(版权/备案) ---------- */
|
||||
.footer {
|
||||
padding: 14px 22px;
|
||||
|
||||
+275
-89
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user