';
}
el.innerHTML = html;
}
function renderCrmFollowsTimeline(follows) {
var el = document.getElementById('crmFollowsTimeline');
if (!el) return;
if (!follows.length) { el.innerHTML = '
暂无跟进记录
'; return; }
// 按日期分组
var groups = {};
follows.forEach(function(f) {
var day = f.created_at ? f.created_at.slice(0,10) : '';
if (!groups[day]) groups[day] = [];
groups[day].push(f);
});
var html = '';
Object.keys(groups).sort().reverse().forEach(function(day) {
html += '
';
html += '
📅 ' + day + '
';
groups[day].forEach(function(f) {
var imgs = (f.follow_images||[]).map(function(img){
return '';
}).join('');
var comments = (f.comments||[]).map(function(cm){
return '
';
if (t === 'today' || t === 'upcoming') {
html += '';
}
html += '';
html += '
';
});
html += '
';
content.innerHTML = html;
}
});
}
function switchWorkbenchTab(tab, el) { loadCrmWorkbench(tab); }
// ---- 数据统计 ----
function loadCrmStatistics() {
api('GET', '/crm/statistics').then(function(res) {
if (res.code !== 0) return;
var s = res.data || {};
document.getElementById('statCrmTotal').textContent = s.total_customers || 0;
document.getElementById('statCrmNew').textContent = s.new_today || 0;
document.getElementById('statCrmPending').textContent = s.pending_follow || 0;
document.getElementById('statCrmDeal').textContent = s.deal_done || 0;
document.getElementById('statFollowToday').textContent = s.today_follows || 0;
document.getElementById('statPublicSea').textContent = s.in_public_sea || 0;
document.getElementById('statOverdue').textContent = s.overdue_count || 0;
});
api('GET', '/crm/statistics/funnel').then(function(res) {
if (res.code === 0) renderCrmFunnelChart(res.data || {});
});
api('GET', '/crm/statistics/sources').then(function(res) {
if (res.code === 0) renderCrmSourceChart(res.data || []);
});
}
function renderCrmFunnelChart(funnel) {
var el = document.getElementById('crmFunnelChart');
if (!el) return;
var steps = ['线索','待跟进','跟进中','已约访','已看房','价格谈判','已成交'];
var colors = ['#d9d9d9','#fa8c16','#1890ff','#722ed1','#13c2c2','#faad14','#52c41a'];
var max = Math.max.apply(null, steps.map(function(s){return funnel[s]||0;})) || 1;
var html = '
';
steps.forEach(function(step, i) {
var count = funnel[step] || 0;
var pct = Math.round(count / max * 100);
html += '
';
html += '' + step + '';
html += '
';
html += '';
html += '
';
html += '' + count + '';
html += '
';
});
html += '
';
el.innerHTML = html;
}
function renderCrmSourceChart(sources) {
var el = document.getElementById('crmSourceChart');
if (!el) return;
if (!sources.length) { el.innerHTML = '
暂无数据
'; return; }
var total = sources.reduce(function(s,item){return s + item.count;},0) || 1;
var colors = ['#1677ff','#52c41a','#fa8c16','#722ed1','#13c2c2','#faad14','#eb2f96','#fa541c'];
var html = '
';
sources.slice(0,8).forEach(function(item, i) {
var pct = Math.round(item.count / total * 100);
html += '
';
html += '' + escHtml(item.source) + '';
html += '
';
html += '';
html += '
';
html += '' + item.count + ' (' + pct + '%)';
html += '
';
});
html += '
';
el.innerHTML = html;
}
// ---- 权限加载 ----
function loadCrmPermission() {
api('GET', '/crm/sales/permission').then(function(res) {
if (res.code === 0) {
crmPermission = res.data;
// 填充负责人/协作人下拉
var ownerSel = document.getElementById('crmFilterOwner');
var collabSel = document.getElementById('crmFilterCollab');
var opts = (crmPermission.subordinates||[]).map(function(s){return '';}).join('');
if (ownerSel) ownerSel.innerHTML = '' + opts;
if (collabSel) collabSel.innerHTML = '' + opts;
}
});
}
// ---- 工具函数 ----
function fmtDate(iso) {
if (!iso) return '-';
return iso.slice(0,10);
}
function fmtDateTime(iso) {
if (!iso) return '-';
return iso.slice(0,16).replace('T',' ');
}
// ---- switchTab 扩展:加载CRM权限和数据 ----
var _origSwitchTab = typeof switchTab === 'function' ? switchTab : null;
// 如果已有 switchTab,覆盖它来注入 CRM tab 处理
var _origSwitchTab_backup = window.switchTab;
window.switchTab = function(tab, el) {
// 调用原有逻辑(如果存在)
if (_origSwitchTab_backup && _origSwitchTab_backup !== window.switchTab) {
_origSwitchTab_backup(tab, el);
}
// CRM tab 专属处理
if (tab === 'crmCustomers') { loadCrmPermission(); loadCrmCustomers(1); }
else if (tab === 'crmLeads') { loadCrmLeads(1); }
else if (tab === 'crmPublicSea') { loadCrmPublicSea(1); }
else if (tab === 'crmWorkbench') { loadCrmWorkbench('today'); }
else if (tab === 'crmStatistics') { loadCrmStatistics(); }
};
// ════════════════════════════════════════════════════════════════
// CRM Phase 2 JS 函数包(追加到 <\/script> 之前)
// ════════════════════════════════════════════════════════════════
// ── 权限Tab显隐 ──────────────────────────────────────────────
function initCrmPermissionTabs() {
var role = CUR_USER && CUR_USER.role;
var bar = document.getElementById('crmViewTabBar');
if (!bar) return;
// 隐藏下属相关Tab(普通销售角色)
if (role !== 'superadmin' && role !== 'manager') {
bar.querySelectorAll('.crm-sub-tab').forEach(function(el){ el.style.display = 'none'; });
}
// manager 可看下属Tab,但看不到「全部」
// superadmin 全部展示
}
// ── 7个权限视图Tab切换(增强版)─────────────────────────────
function switchCrmTab(tab, el) {
currentCrmTab = tab;
// Tab active 样式
var bar = el && el.parentElement;
if (bar) {
bar.querySelectorAll('.crm-tab').forEach(function(a){ a.classList.remove('active'); });
if (el) el.classList.add('active');
}
// 清空选中
crmSelectedIds = [];
document.getElementById('crmBatchBar').style.display = 'none';
document.getElementById('crmSelectAll') && (document.getElementById('crmSelectAll').checked = false);
// 权限差异化批量工具栏
var isFullView = ['all','mine','subordinate'].indexOf(tab) !== -1;
var isCollabView = ['collaborate','sub-collab'].indexOf(tab) !== -1;
var fullEl = document.getElementById('crmFullActions');
var collabEl = document.getElementById('crmCollabActions');
if (fullEl) fullEl.style.display = isFullView ? '' : 'none';
if (collabEl) collabEl.style.display = isCollabView ? '' : 'none';
// 保存到 localStorage
localStorage.setItem('crm_last_tab', tab);
loadCrmCustomers(1);
}
// 页面加载时恢复上次Tab
function restoreCrmLastTab() {
var last = localStorage.getItem('crm_last_tab');
if (last && document.querySelector('#crmViewTabBar .crm-tab[data-tab="'+last+'"]')) {
var el = document.querySelector('#crmViewTabBar .crm-tab[data-tab="'+last+'"]');
switchCrmTab(last, el);
}
}
// ── 快捷日期标签 ──────────────────────────────────────────────
function setQuickDate(preset) {
var now = new Date();
var fmt = function(d) { return d.toISOString().slice(0,10); };
var from, to = fmt(now);
if (preset === 'today') { from = to; }
else if (preset === 'yesterday') {
var y = new Date(now); y.setDate(y.getDate()-1);
from = fmt(y); to = from;
}
else if (preset === 'week') {
var d = new Date(now); d.setDate(d.getDate() - d.getDay());
from = fmt(d);
}
else if (preset === 'lastweek') {
var d = new Date(now); d.setDate(d.getDate() - d.getDay() - 7);
var d2 = new Date(d); d2.setDate(d2.getDate()+6);
from = fmt(d); to = fmt(d2);
}
else if (preset === 'month') {
var d = new Date(now.getFullYear(), now.getMonth(), 1);
from = fmt(d);
}
else if (preset === 'lastmonth') {
var d = new Date(now.getFullYear(), now.getMonth()-1, 1);
var d2 = new Date(now.getFullYear(), now.getMonth(), 0);
from = fmt(d); to = fmt(d2);
}
document.getElementById('crmFilterDateFrom').value = from;
document.getElementById('crmFilterDateTo').value = to;
// 高亮当前选中
document.querySelectorAll('#crmQuickDate span').forEach(function(el){ el.style.background = '#fff'; el.style.color = '#333'; });
var active = document.getElementById('qd-'+preset);
if (active) { active.style.background = '#1677ff'; active.style.color = '#fff'; active.style.borderColor = '#1677ff'; }
loadCrmCustomers(1);
}
// ── 批量选中管理 ──────────────────────────────────────────────
var crmSelectedIds = [];
function onCrmRowCheck(chk) {
var id = parseInt(chk.value);
if (chk.checked) { if (crmSelectedIds.indexOf(id) === -1) crmSelectedIds.push(id); }
else { crmSelectedIds = crmSelectedIds.filter(function(x){ return x !== id; }); }
updateCrmBatchBar();
}
function toggleCrmSelectAll(checked) {
var checks = document.querySelectorAll('#crmCustomerList input[type=checkbox]');
crmSelectedIds = [];
checks.forEach(function(chk) {
chk.checked = checked;
if (checked) crmSelectedIds.push(parseInt(chk.value));
});
updateCrmBatchBar();
}
function updateCrmBatchBar() {
var bar = document.getElementById('crmBatchBar');
if (!bar) return;
var cnt = crmSelectedIds.length;
if (cnt === 0) { bar.style.display = 'none'; return; }
bar.style.display = 'flex';
var countEl = document.getElementById('crmSelectedCount');
if (countEl) countEl.textContent = cnt;
}
// ── 子菜单切换 ──────────────────────────────────────────────
function toggleCrmSubmenu(menuId) {
var menu = document.getElementById(menuId);
if (!menu) return;
// 关闭其他菜单
['crmTransferMenu','crmExportMenu','crmMoreMenu'].forEach(function(id) {
var m = document.getElementById(id);
if (m && id !== menuId) m.style.display = 'none';
});
menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
}
// 点击其他地方关闭子菜单
document.addEventListener('click', function(e) {
if (!e.target.closest('#crmTransferDropdown') && !e.target.closest('#crmExportDropdown') && !e.target.closest('#crmMoreDropdown')) {
['crmTransferMenu','crmExportMenu','crmMoreMenu'].forEach(function(id) {
var m = document.getElementById(id);
if (m) m.style.display = 'none';
});
}
});
// ── 批量转移 ────────────────────────────────────────────────
function batchTransferCrm(mode) {
if (crmSelectedIds.length === 0) { toast('请先选择客户'); return; }
closeAllCrmModals();
var toSeaArea = document.getElementById('crmBatchTransferToSeaArea');
var toUserArea = document.getElementById('crmBatchTransferToUserArea');
var title = document.getElementById('crmBatchTransferTitle');
var summary = document.getElementById('crmBatchTransferSummary');
if (mode === 'sea') {
title.textContent = '转移至客户公海';
toSeaArea.style.display = 'block';
toUserArea.style.display = 'none';
document.getElementById('crmBatchTransferConfirmBtn').onclick = confirmCrmBatchTransferToSea;
} else {
title.textContent = '转移客户给他人';
toSeaArea.style.display = 'none';
toUserArea.style.display = 'block';
document.getElementById('crmBatchTransferConfirmBtn').onclick = confirmCrmBatchTransferToUser;
loadCrmTransferUsers();
}
summary.innerHTML = '已选 ' + crmSelectedIds.length + ' 个客户将被转移';
openModal('crmBatchTransferModal');
}
var _crmTransferUsers = [];
function loadCrmTransferUsers() {
api('GET', '/crm/sales').then(function(res) {
if (res.code !== 0) return;
_crmTransferUsers = (res.data || []).filter(function(u){ return u.user_id != CUR_USER.userId; });
renderCrmTransferUserList('');
});
}
function filterCrmTransferUsers(kw) {
renderCrmTransferUserList(kw);
}
function renderCrmTransferUserList(kw) {
var el = document.getElementById('crmBatchTransferUserList');
if (!el) return;
var filtered = _crmTransferUsers.filter(function(u) {
return !kw || (u.username||'').toLowerCase().includes(kw.toLowerCase());
});
if (!filtered.length) { el.innerHTML = '
无匹配销售
'; return; }
var html = '';
filtered.forEach(function(u) {
var dept = u.department || '';
html += '
'; return; }
var html = '';
options.forEach(function(opt) {
var checked = _crmMultiSelectValues.indexOf(opt) !== -1 ? 'checked' : '';
html += '';
});
el.innerHTML = html;
}
function filterCrmMultiSelectOptions(kw) { renderCrmMultiSelectOptions(kw); }
function onCrmMultiSelectItemChange(chk) {
var val = chk.value;
if (chk.checked) { if (_crmMultiSelectValues.indexOf(val) === -1) _crmMultiSelectValues.push(val); }
else { _crmMultiSelectValues = _crmMultiSelectValues.filter(function(v){ return v !== val; }); }
}
function onCrmMultiSelectBlankChange() {
_crmMultiSelectBlank = document.getElementById('crmMultiSelectBlank').checked;
}
function confirmCrmMultiSelect() {
var type = _crmMultiSelectType;
var btn = document.getElementById('crmFilter'+type.charAt(0).toUpperCase()+type.slice(1)+'Btn');
var vals = _crmMultiSelectValues.slice();
if (_crmMultiSelectBlank) vals.push('');
// 更新按钮文字
if (btn) {
var label = {tag:'标签',status:'跟进状态',type:'客户类型',source:'客户来源'}[type] || type;
btn.textContent = label + ' ' + (_crmMultiSelectValues.length > 0 ? '('+_crmMultiSelectValues.length+')' : '') + ' ▼';
btn.setAttribute('data-values', _crmMultiSelectValues.join(','));
}
// 同步到 API 参数(改用逗号分隔传给后端)
closeCrmMultiSelectModal();
loadCrmCustomers(1);
}
function closeCrmMultiSelectModal() { closeModal('crmMultiSelectModal'); }
// ── 协作人多选弹窗 ──────────────────────────────────────────
var _crmCollabSelected = [];
function openCrmCollabModal() {
closeAllCrmModals();
_crmCollabSelected = (document.getElementById('crmFilterCollabBtn').getAttribute('data-values') || '').split(',').filter(Boolean);
document.getElementById('crmCollabSearch').value = '';
// 加载部门和人员数据
api('GET', '/crm/sales').then(function(res) {
if (res.code !== 0) return;
renderCrmCollabDeptTree(res.data || []);
renderCrmCollabUserList(res.data || [], '');
});
openModal('crmCollabModal');
}
function renderCrmCollabDeptTree(users) {
var el = document.getElementById('crmCollabDeptTree');
if (!el) return;
var depts = {};
users.forEach(function(u) {
var d = u.department || '未分配';
if (!depts[d]) depts[d] = [];
depts[d].push(u);
});
var html = '';
Object.keys(depts).sort().forEach(function(dept) {
html += '
' + escHtml(dept) + ' ▶
';
html += '
';
depts[dept].forEach(function(u) {
var checked = _crmCollabSelected.indexOf(String(u.user_id)) !== -1 ? 'checked' : '';
html += '';
});
html += '
';
});
el.innerHTML = html || '
暂无部门
';
}
function toggleCrmDept(dept) {
var el = document.getElementById('crmCollabDept_' + dept.replace(/\s/g,'_'));
if (el) el.style.display = el.style.display === 'none' ? 'block' : 'none';
}
function renderCrmCollabUserList(users, kw) {
var el = document.getElementById('crmCollabUserList');
if (!el) return;
var filtered = users.filter(function(u) { return !kw || (u.username||'').toLowerCase().includes(kw.toLowerCase()); });
if (!filtered.length) { el.innerHTML = '
无匹配人员
'; return; }
var html = '';
filtered.forEach(function(u) {
var checked = _crmCollabSelected.indexOf(String(u.user_id)) !== -1 ? 'checked' : '';
html += '';
});
el.innerHTML = html;
}
function filterCrmCollabUsers(kw) {
api('GET', '/crm/sales').then(function(res) {
if (res.code !== 0) return;
renderCrmCollabUserList(res.data || [], kw);
});
}
function onCrmCollabUserChange(chk) {
var id = chk.value;
if (chk.checked) { if (_crmCollabSelected.indexOf(id) === -1) _crmCollabSelected.push(id); }
else { _crmCollabSelected = _crmCollabSelected.filter(function(v){ return v !== id; }); }
}
function confirmCrmCollab() {
var btn = document.getElementById('crmFilterCollabBtn');
var cnt = _crmCollabSelected.length;
btn.textContent = '协作人 ' + (cnt > 0 ? '('+cnt+')' : '') + ' ▼';
btn.setAttribute('data-values', _crmCollabSelected.join(','));
closeCrmCollabModal();
loadCrmCustomers(1);
}
function closeCrmCollabModal() { closeModal('crmCollabModal'); }
// ── 部门多选弹窗 ───────────────────────────────────────────
var _crmDeptSelected = [];
function openCrmDeptModal() {
closeAllCrmModals();
_crmDeptSelected = (document.getElementById('crmFilterDeptBtn').getAttribute('data-values') || '').split(',').filter(Boolean);
api('GET', '/crm/sales').then(function(res) {
if (res.code !== 0) return;
var depts = {};
(res.data || []).forEach(function(u) {
var d = u.department || '未分配';
if (!depts[d]) depts[d] = true;
});
var el = document.getElementById('crmDeptOptions');
if (!el) return;
var html = '';
Object.keys(depts).sort().forEach(function(d) {
var checked = _crmDeptSelected.indexOf(d) !== -1 ? 'checked' : '';
html += '';
});
el.innerHTML = html || '
暂无部门数据
';
});
openModal('crmDeptModal');
}
function onCrmDeptChange(chk) {
var val = chk.value;
if (chk.checked) { if (_crmDeptSelected.indexOf(val) === -1) _crmDeptSelected.push(val); }
else { _crmDeptSelected = _crmDeptSelected.filter(function(v){ return v !== val; }); }
}
function confirmCrmDept() {
var btn = document.getElementById('crmFilterDeptBtn');
var cnt = _crmDeptSelected.length;
btn.textContent = '所属部门 ' + (cnt > 0 ? '('+cnt+')' : '') + ' ▼';
btn.setAttribute('data-values', _crmDeptSelected.join(','));
closeCrmDeptModal();
loadCrmCustomers(1);
}
function closeCrmDeptModal() { closeModal('crmDeptModal'); }
// ── 负责人多选弹窗 ───────────────────────────────────────────
var _crmOwnerSelected = [];
function openCrmOwnerModal() {
closeAllCrmModals();
_crmOwnerSelected = (document.getElementById('crmFilterOwnerBtn').getAttribute('data-values') || '').split(',').filter(Boolean);
document.getElementById('crmOwnerSearch').value = '';
api('GET', '/crm/sales').then(function(res) {
if (res.code !== 0) return;
renderCrmOwnerOptions(res.data || [], '');
});
openModal('crmOwnerModal');
}
function renderCrmOwnerOptions(users, kw) {
var el = document.getElementById('crmOwnerOptions');
if (!el) return;
var filtered = users.filter(function(u) { return !kw || (u.username||'').toLowerCase().includes(kw.toLowerCase()); });
if (!filtered.length) { el.innerHTML = '
无匹配人员
'; return; }
var html = '';
filtered.forEach(function(u) {
var checked = _crmOwnerSelected.indexOf(String(u.user_id)) !== -1 ? 'checked' : '';
html += '';
});
el.innerHTML = html;
}
function filterCrmOwnerUsers(kw) {
api('GET', '/crm/sales').then(function(res) {
if (res.code !== 0) return;
renderCrmOwnerOptions(res.data || [], kw);
});
}
function onCrmOwnerChange(chk) {
var id = chk.value;
if (chk.checked) { if (_crmOwnerSelected.indexOf(id) === -1) _crmOwnerSelected.push(id); }
else { _crmOwnerSelected = _crmOwnerSelected.filter(function(v){ return v !== id; }); }
}
function confirmCrmOwner() {
var btn = document.getElementById('crmFilterOwnerBtn');
var cnt = _crmOwnerSelected.length;
btn.textContent = '负责人 ' + (cnt > 0 ? '('+cnt+')' : '') + ' ▼';
btn.setAttribute('data-values', _crmOwnerSelected.join(','));
closeCrmOwnerModal();
loadCrmCustomers(1);
}
function closeCrmOwnerModal() { closeModal('crmOwnerModal'); }
// ── 自定义显示列弹窗 ─────────────────────────────────────────
var _crmActiveColumns = [];
var _crmAllColumns = [];
var _crmDraggingCol = null;
var CRM_ALL_COLUMNS = [
{key:'name', label:'客户名称', default:true},
{key:'type', label:'客户类型', default:true},
{key:'status', label:'跟进状态', default:true},
{key:'priority', label:'优先级', default:false},
{key:'tags', label:'标签', default:false},
{key:'collaborators_display', label:'协作人', default:true},
{key:'phone', label:'电话', default:true},
{key:'wechat', label:'微信号', default:false},
{key:'created_at', label:'创建时间', default:true},
{key:'assigned_name', label:'负责人', default:true},
{key:'department', label:'所属部门', default:false},
{key:'last_follow_time', label:'实际跟进时间', default:true},
{key:'last_follow_content', label:'最新跟进记录', default:true},
{key:'next_follow_time', label:'下次跟进时间', default:false},
{key:'no_follow_days', label:'未跟进天数', default:false},
{key:'source', label:'来源', default:false},
{key:'budget', label:'预算', default:false},
{key:'intention_building', label:'意向楼盘', default:false},
{key:'last_contact_time', label:'最近联系时间', default:false},
];
function openCrmColumnModal() {
closeAllCrmModals();
// 读取本地配置
var saved = localStorage.getItem('crm_columns');
if (saved) {
try { _crmActiveColumns = JSON.parse(saved); } catch(e) {}
}
if (!_crmActiveColumns.length) {
_crmActiveColumns = CRM_ALL_COLUMNS.filter(function(c){ return c.default; }).map(function(c){ return c.key; });
}
_crmAllColumns = CRM_ALL_COLUMNS.map(function(c){ return c.key; });
// 其他设置
var fm = localStorage.getItem('crm_follow_mode') === '1';
var ar = localStorage.getItem('crm_auto_refresh') === '1';
var sn = localStorage.getItem('crm_sound_notify') === '1';
if (document.getElementById('crmCfgFollowMode')) document.getElementById('crmCfgFollowMode').checked = fm;
if (document.getElementById('crmCfgAutoRefresh')) document.getElementById('crmCfgAutoRefresh').checked = ar;
if (document.getElementById('crmCfgSoundNotify')) document.getElementById('crmCfgSoundNotify').checked = sn;
switchCrmColTab('field');
renderCrmActiveColumns();
renderCrmAllColumns();
openModal('crmColumnModal');
}
function switchCrmColTab(tab) {
document.getElementById('crmColFieldPanel').style.display = tab === 'field' ? '' : 'none';
document.getElementById('crmColOtherPanel').style.display = tab === 'other' ? '' : 'none';
document.getElementById('crmColTab1').classList.toggle('active', tab === 'field');
document.getElementById('crmColTab2').classList.toggle('active', tab === 'other');
document.getElementById('crmColTab1').style.borderBottomColor = tab === 'field' ? '#1677ff' : 'transparent';
document.getElementById('crmColTab1').style.color = tab === 'field' ? '#1677ff' : '#666';
document.getElementById('crmColTab2').style.borderBottomColor = tab === 'other' ? '#1677ff' : 'transparent';
document.getElementById('crmColTab2').style.color = tab === 'other' ? '#1677ff' : '#666';
}
function renderCrmActiveColumns() {
var el = document.getElementById('crmActiveColumns');
if (!el) return;
var html = '';
_crmActiveColumns.forEach(function(key, i) {
var col = CRM_ALL_COLUMNS.find(function(c){ return c.key === key; });
if (!col) return;
html += '
';
html += '☰';
html += '' + escHtml(col.label) + '';
html += '×
';
});
el.innerHTML = html || '暂无展示字段';
}
function renderCrmAllColumns() {
var el = document.getElementById('crmAllColumns');
if (!el) return;
var html = '';
CRM_ALL_COLUMNS.forEach(function(col) {
if (_crmActiveColumns.indexOf(col.key) !== -1) return; // 已添加的跳过
html += '';
html += '+';
html += '' + escHtml(col.label) + '';
});
el.innerHTML = html || '已添加全部字段';
}
function addCrmActiveColumn(key) {
if (_crmActiveColumns.indexOf(key) === -1) _crmActiveColumns.push(key);
renderCrmActiveColumns();
renderCrmAllColumns();
}
function removeCrmActiveColumn(key) {
_crmActiveColumns = _crmActiveColumns.filter(function(k){ return k !== key; });
renderCrmActiveColumns();
renderCrmAllColumns();
}
function onCrmColDragStart(e) {
_crmDraggingCol = e.target.closest('[data-key]').dataset.key;
e.dataTransfer.effectAllowed = 'move';
}
function onCrmColDragOver(e) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; }
function onCrmColDrop(e) {
e.preventDefault();
var targetKey = e.target.closest('[data-key]') && e.target.closest('[data-key]').dataset.key;
if (!_crmDraggingCol || !targetKey || _crmDraggingCol === targetKey) return;
var fromIdx = _crmActiveColumns.indexOf(_crmDraggingCol);
var toIdx = _crmActiveColumns.indexOf(targetKey);
_crmActiveColumns.splice(fromIdx, 1);
_crmActiveColumns.splice(toIdx, 0, _crmDraggingCol);
renderCrmActiveColumns();
}
function saveCrmColumnConfig() {
localStorage.setItem('crm_columns', JSON.stringify(_crmActiveColumns));
// 其他设置
if (document.getElementById('crmCfgFollowMode')) localStorage.setItem('crm_follow_mode', document.getElementById('crmCfgFollowMode').checked ? '1' : '0');
if (document.getElementById('crmCfgAutoRefresh')) localStorage.setItem('crm_auto_refresh', document.getElementById('crmCfgAutoRefresh').checked ? '1' : '0');
if (document.getElementById('crmCfgSoundNotify')) localStorage.setItem('crm_sound_notify', document.getElementById('crmCfgSoundNotify').checked ? '1' : '0');
toast('列配置已保存');
closeCrmColumnModal();
loadCrmCustomers(1);
}
function resetCrmColumns() {
_crmActiveColumns = CRM_ALL_COLUMNS.filter(function(c){ return c.default; }).map(function(c){ return c.key; });
renderCrmActiveColumns();
renderCrmAllColumns();
toast('已恢复默认字段');
}
function closeCrmColumnModal() { closeModal('crmColumnModal'); }
// ── 重置筛选 ────────────────────────────────────────────────
// ── 关闭所有 CRM 弹窗 ────────────────────────────────────────
function closeAllCrmModals() {
['crmMultiSelectModal','crmCollabModal','crmDeptModal','crmOwnerModal','crmColumnModal','crmBatchTransferModal','crmBatchEditModal','crmBatchSmsModal','crmBatchCollabModal'].forEach(function(id) {
closeModal(id);
});
}
// ── 通用弹窗控制 ────────────────────────────────────────────
function openModal(id) {
var el = document.getElementById(id);
if (el) el.classList.remove('hidden');
}
function closeModal(id) {
var el = document.getElementById(id);
if (el) el.classList.add('hidden');
}
// ── loadCrmCustomers 增强:支持多选筛选参数 ─────────────────
var _origLoadCrmCustomers = null;
(function() {
_origLoadCrmCustomers = loadCrmCustomers;
})();
// ── 页面初始化:加载完权限后初始化Tab ───────────────────────
function initCrmPage() {
// 权限Tab显隐
initCrmPermissionTabs();
// 恢复上次Tab
restoreCrmLastTab();
// 跟进模式恢复
var fm = localStorage.getItem('crm_follow_mode') === '1';
var fmEl = document.getElementById('crmFollowMode');
if (fmEl) fmEl.checked = fm;
// 列配置
var savedCols = localStorage.getItem('crm_columns');
if (savedCols) {
try { _crmActiveColumns = JSON.parse(savedCols); } catch(e) {}
}
}
// ── escHtml(工具函数,避免XSS)──────────────────────────────
// ═══════════════════════════════════════════════════════════════
// CRM Phase 3 补充函数包(2026-07-19 代可行)
// ═══════════════════════════════════════════════════════════════
// ── 筛选栏重置 ────────────────────────────────────────────────
function resetCrmFilters() {
var ids = ['crmFilterTag','crmFilterStatus','crmFilterType','crmFilterSource','crmFilterOwner','crmFilterDateFrom','crmFilterDateTo'];
ids.forEach(function(id){ var el=document.getElementById(id); if(el) el.value=''; });
var s = document.getElementById('crmCustomerSearch');
if(s) s.value='';
loadCrmCustomers(1);
}
// ── 跟进模式切换 ──────────────────────────────────────────────
function toggleCrmFollowMode() {
var chk = document.getElementById('crmFollowMode');
if (!chk) return;
var on = chk.checked;
localStorage.setItem('crm_follow_mode', on ? '1' : '0');
toast(on ? '跟进模式已开启' : '跟进模式已关闭');
}
// ── 搜索防抖 ─────────────────────────────────────────────────
var _crmSearchTimer = null;
function debounceCrmSearch() {
if (_crmSearchTimer) clearTimeout(_crmSearchTimer);
_crmSearchTimer = setTimeout(function() { loadCrmCustomers(1); }, 400);
}
// ── 公海领取客户 ──────────────────────────────────────────────
function claimCustomer(id) {
api('POST', '/crm/public-sea/' + id + '/claim', {}).then(function(res) {
toast(res.code === 0 ? '领取成功' : '领取失败: ' + (res.message || '未知错误'));
if (res.code === 0) { loadCrmPublicSea(); }
});
}
// ── 公海退回客户 ──────────────────────────────────────────────
function returnCustomer(id) {
if (!confirm('确定将该客户退回公海?退回后将从您的客户列表移除。')) return;
api('POST', '/crm/public-sea/' + id + '/return', {}).then(function(res) {
toast(res.code === 0 ? '退回成功' : '退回失败: ' + (res.message || '未知错误'));
if (res.code === 0) { loadCrmPublicSea(); loadCrmCustomers(); }
});
}
// ── 公海批量领取 ──────────────────────────────────────────────
function batchClaimPublicCustomers() {
var rows = document.querySelectorAll('#crmPublicSeaList input[type="checkbox"]:checked');
if (!rows.length) { toast('请先选择客户'); return; }
var ids = [];
rows.forEach(function(el){ var id = parseInt(el.dataset.id); if(!isNaN(id)) ids.push(id); });
if (!ids.length) { toast('无有效客户'); return; }
var promises = ids.map(function(id){ return api('POST', '/crm/public-sea/' + id + '/claim', {}); });
Promise.all(promises).then(function(results) {
var ok = results.filter(function(r){ return r.code === 0; }).length;
toast('成功领取 ' + ok + ' 个客户');
loadCrmPublicSea();
});
}
// ── 线索池一键分配 ───────────────────────────────────────────
function batchAssignCrmLeads() {
var rows = document.querySelectorAll('#crmLeadsList input[type="checkbox"]:checked');
if (!rows.length) { toast('请先选择线索'); return; }
var ids = [];
rows.forEach(function(el){ var id = parseInt(el.dataset.id); if(!isNaN(id)) ids.push(id); });
if (!ids.length) { toast('无有效线索'); return; }
// 弹出分配弹窗(部门+负责人)
openCrmOwnerModal(function(userId, userName) {
if (!userId) { toast('请选择负责人'); return; }
var promises = ids.map(function(id){ return api('POST', '/crm/leads/' + id + '/assign', {to_user_id: userId, to_user_name: userName}); });
Promise.all(promises).then(function(results) {
var ok = results.filter(function(r){ return r.code === 0; }).length;
toast('成功分配 ' + ok + ' 条线索');
loadCrmLeads();
});
});
}
// ── 批量添加协作人 ───────────────────────────────────────────
function batchAddCollaboratorsCrm() {
if (!window.crmSelectedIds || !window.crmSelectedIds.length) { toast('请先选择客户'); return; }
closeAllCrmModals();
openCrmCollabModal(function(userId, userName) {
if (!userId) { toast('请选择协作人'); return; }
var promises = window.crmSelectedIds.map(function(id){ return api('POST', '/crm/customers/' + id + '/contacts', {type:'collaborator', user_id: userId, user_name: userName}); });
Promise.all(promises).then(function(results) {
var ok = results.filter(function(r){ return r.code === 0; }).length;
toast('成功添加 ' + ok + ' 个协作人');
loadCrmCustomers();
});
});
}
// ── 批量合并客户 ──────────────────────────────────────────────
function batchMergeCrm() {
if (!window.crmSelectedIds || window.crmSelectedIds.length < 2) { toast('请至少选择2个客户进行合并'); return; }
var masterId = window.crmSelectedIds[0];
var slaveIds = window.crmSelectedIds.slice(1);
if (!confirm('将 ' + slaveIds.length + ' 个客户合并到 ID=' + masterId + ',是否继续?')) return;
api('POST', '/crm/customers/' + masterId + '/merge', {slave_ids: slaveIds}).then(function(res) {
toast(res.code === 0 ? '合并成功' : '合并失败: ' + (res.message || ''));
if (res.code === 0) { window.crmSelectedIds = [masterId]; loadCrmCustomers(); }
});
}
// ── 批量锁定客户 ──────────────────────────────────────────────
function batchLockCrm() {
if (!window.crmSelectedIds || !window.crmSelectedIds.length) { toast('请先选择客户'); return; }
api('POST', '/crm/customers/batch-lock', {ids: window.crmSelectedIds, locked: true}).then(function(res) {
toast(res.code === 0 ? '已锁定 ' + window.crmSelectedIds.length + ' 个客户' : '操作失败: ' + (res.message || ''));
if (res.code === 0) loadCrmCustomers();
});
}
// ── 批量解锁客户 ──────────────────────────────────────────────
function batchUnlockCrm() {
if (!window.crmSelectedIds || !window.crmSelectedIds.length) { toast('请先选择客户'); return; }
api('POST', '/crm/customers/batch-lock', {ids: window.crmSelectedIds, locked: false}).then(function(res) {
toast(res.code === 0 ? '已解锁 ' + window.crmSelectedIds.length + ' 个客户' : '操作失败: ' + (res.message || ''));
if (res.code === 0) loadCrmCustomers();
});
}
// ── 批量软删除 ───────────────────────────────────────────────
function batchSoftDeleteCrm() {
if (!window.crmSelectedIds || !window.crmSelectedIds.length) { toast('请先选择客户'); return; }
if (!confirm('确定删除选中的 ' + window.crmSelectedIds.length + ' 个客户?删除后可在回收站恢复。')) return;
api('POST', '/crm/customers/batch-delete', {ids: window.crmSelectedIds}).then(function(res) {
toast(res.code === 0 ? '已删除 ' + window.crmSelectedIds.length + ' 个客户' : '删除失败: ' + (res.message || ''));
if (res.code === 0) { window.crmSelectedIds = []; var bb = document.getElementById('crmBatchBar'); if(bb) bb.style.display = 'none'; loadCrmCustomers(); }
});
}
// ── 客户详情:商机Tab渲染 ────────────────────────────────────
function renderCrmOpportunities(list) {
var el = document.getElementById('crmOppList');
if (!el) return;
if (!list || !list.length) { el.innerHTML = '
暂无商机记录
'; return; }
var stages = ['初步接触','需求确认','方案报价','商务谈判','合同签订'];
var colors = ['#d9d9d9','#fa8c16','#1890ff','#722ed1','#52c41a'];
var html = '
商机名称
阶段
金额
预计成交
操作
';
list.forEach(function(o) {
var si = stages.indexOf(o.stage || '');
var color = si >= 0 ? colors[si] : '#d9d9d9';
html += '