'; }
list.forEach(function(s){ var chk = ids.indexOf(String(s.id))>=0?'checked':''; html += ''; });
html += '
';
el.innerHTML = html;
}
function renderCrmOwnerDeptList(list) {
var el = document.getElementById('crmOwnerDeptPane');
if (!el) return;
var btn = document.getElementById('crmFilterOwnerBtn');
var checked = btn ? (btn.getAttribute('data-values')||'') : '';
var ids = checked ? checked.split(',') : [];
var html = '
';
function rd(d, lvl) {
var pad = lvl*16, chk = ids.indexOf('dept_'+d.id)>=0?'checked':'';
var hasChildren = d.children && d.children.length;
var arrow = hasChildren ? ' ►' : '';
html += '';
if (hasChildren) { html += '
'; d.children.forEach(function(c){ rd(c, lvl+1); }); html += '
'; }
}
list.forEach(function(d){ rd(d, 0); });
html += '
';
el.innerHTML = html;
}
function toggleCrmOwnerSel() {
var all = Array.from(document.querySelectorAll('#crmOwnerUserPane input:checked, #crmOwnerDeptPane input:checked')).map(function(i){ return i.value; });
var btn = document.getElementById('crmFilterOwnerBtn');
if (btn) {
var names = Array.from(document.querySelectorAll('#crmOwnerUserPane input:checked + label, #crmOwnerDeptPane input:checked + label')).map(function(l){ return l.textContent.trim(); });
btn.setAttribute('data-values', all.join(','));
btn.textContent = names.length ? names.slice(0,3).join(',')+(names.length>3?'...':'') : '责任人';
}
}
function toggleCrmOwnerDeptNode(id) {
var children = document.getElementById('owner_dept_children_'+id);
if (children) children.style.display = children.style.display==='none' ? '' : 'none';
}
function returnCrmPublicCustomer(id) {
if (!confirm('确定归还该客户到公海吗')) return;
api('/crm/public-sea/'+id+'/return', {method:'POST'}).then(function(r){ if(r.code===0){ toast('归还成功'); loadCrmPublicSea(); } else{ toast('归还失败'); } }).catch(function(){ toast('网络错误'); });
}
function renderCrmStatistics(data) {
if (!data) return;
var s = data.summary || data || {};
var setStat = function(id, val){ var el=document.getElementById(id); if(el) el.textContent=val||'0'; };
setStat('statCrmTotal', s.total_customers || s.total || 0);
setStat('statCrmNew', s.new_today || 0);
setStat('statCrmPending', s.pending_follow || s.pending || 0);
setStat('statCrmDeal', s.deal_done || s.deals || 0);
setStat('statFollowToday', s.follow_today || 0);
setStat('statPublicSea', s.public_sea || 0);
setStat('statOverdue', s.overdue || 0);
}
function switchCrmPermissionTab(tab) {
currentCrmTab = tab;
document.querySelectorAll('.crm-perm-tab').forEach(function(el){ el.classList.remove('active'); });
var el = document.querySelector('.crm-perm-tab[data-tab="'+tab+'"]');
if (el) el.classList.add('active');
loadCrmCustomers(1);
}
function toggleSelectAllCrm(cb) {
var checked = cb ? cb.checked : false;
var cbs = document.querySelectorAll('.crm-row-cb');
cbs.forEach(function(c){ c.checked = checked; });
if (checked) crmSelectedIds = Array.from(cbs).map(function(c){ return parseInt(c.value); }).filter(function(i){ return !isNaN(i); });
else crmSelectedIds = [];
updateCrmBatchBar();
}
function toggleCrmRow(id, cb) {
if (!cb) return;
if (cb.checked && crmSelectedIds.indexOf(id)<0) crmSelectedIds.push(id);
else crmSelectedIds = crmSelectedIds.filter(function(i){ return i!==id; });
updateCrmBatchBar();
}
function initCrmFilterBar() {
var map = {
'crmFilterTypeBtn': 'type', 'crmFilterSourceBtn': 'source',
'crmFilterTagBtn': 'tag', 'crmFilterStatusBtn': 'status',
'crmFilterCollabBtn': 'collab', 'crmFilterDeptBtn': 'dept',
'crmFilterOwnerBtn': 'owner'
};
Object.keys(map).forEach(function(btnId){
var btn = document.getElementById(btnId);
if (!btn) return;
var field = map[btnId];
btn.onclick = function(){
if (field==='type'||field==='source'||field==='tag'||field==='status') openCrmMultiSelectModal(field);
else if (field==='collab') document.getElementById('crmCollabModal').classList.remove('hidden');
else if (field==='dept') document.getElementById('crmDeptModal').classList.remove('hidden');
else if (field==='owner') { document.getElementById('crmOwnerModal').classList.remove('hidden'); loadCrmOwnerPane('user'); }
};
});
}
function selectCrmTag(val, text) { var btn=document.getElementById('crmFilterTagBtn'); if(btn){btn.setAttribute('data-values',val);btn.textContent=text||val;} closeAllCrmModals(); loadCrmCustomers(1); }
function selectCrmStatus(val, text) { var btn=document.getElementById('crmFilterStatusBtn'); if(btn){btn.setAttribute('data-values',val);btn.textContent=text||val;} closeAllCrmModals(); loadCrmCustomers(1); }
function selectCrmType(val, text) { var btn=document.getElementById('crmFilterTypeBtn'); if(btn){btn.setAttribute('data-values',val);btn.textContent=text||val;} closeAllCrmModals(); loadCrmCustomers(1); }
function selectCrmCollab(val, text) { var btn=document.getElementById('crmFilterCollabBtn'); if(btn){btn.setAttribute('data-values',val);btn.textContent=text||val;} closeAllCrmModals(); loadCrmCustomers(1); }
function selectCrmDept(val, text) { var btn=document.getElementById('crmFilterDeptBtn'); if(btn){btn.setAttribute('data-values',val);btn.textContent=text||val;} closeAllCrmModals(); loadCrmCustomers(1); }
function selectCrmOwner(val, text) { var btn=document.getElementById('crmFilterOwnerBtn'); if(btn){btn.setAttribute('data-values',val);btn.textContent=text||val;} closeAllCrmModals(); loadCrmCustomers(1); }
function selectCrmSource(val, text) { var btn=document.getElementById('crmFilterSourceBtn'); if(btn){btn.setAttribute('data-values',val);btn.textContent=text||val;} closeAllCrmModals(); loadCrmCustomers(1); }
function closePublicSeaTab() { switchCrmTab('publicSea'); }
function openCrmSourceModal() { openCrmMultiSelectModal('source'); }
function showCrmDealTab(tab) {
var oppArea = document.getElementById('crmDealOppArea');
var conArea = document.getElementById('crmDealConArea');
var oppBtn = document.getElementById('crmDealOppBtn');
var conBtn = document.getElementById('crmDealConBtn');
if (tab === 'opp') {
if (oppArea) oppArea.style.display = '';
if (conArea) conArea.style.display = 'none';
if (oppBtn) { oppBtn.style.background='#1677ff'; oppBtn.style.color='#fff'; oppBtn.style.borderColor='#1677ff'; }
if (conBtn) { conBtn.style.background='#fff'; conBtn.style.color='#666'; conBtn.style.borderColor='#ddd'; }
if (oppArea && !oppArea.innerHTML.trim()) {
oppArea.innerHTML = '
加载中...
';
api('/crm/deals/all').then(function(r) {
if (r.code === 0) {
var opps = r.data.opportunities || [];
if (!opps.length) { oppArea.innerHTML = '
暂无商机记录
'; return; }
var stages = {'':'\u4e0d\u9650','\u521d\u6b65\u63a5\u89e6':'\u521d\u6b65','\u9700\u6c42\u786e\u8ba4':'\u9700\u6c42','\u65b9\u6848\u62a5\u4ef7':'\u65b9\u6848','\u5546\u52a1\u8c08\u5224':'\u8c08\u5224','\u5df2\u6210\u4ea4':'\u5df2\u6210','\u5df2\u6d41\u5931':'\u6d41\u5931'};
var colors = {'':'#999','\u521d\u6b65\u63a5\u89e6':'#108ee9','\u9700\u6c42\u786e\u8ba4':'#108ee9','\u65b9\u6848\u62a5\u4ef7':'#f50','\u5546\u52a1\u8c08\u5224':'#fa8c16','\u5df2\u6210\u4ea4':'#52c41a','\u5df2\u6d41\u5931':'#f5222d'};
var html = '
客户
商机名称
金额
阶段
预计成交
操作
';
opps.forEach(function(o) {
var stage = o.stage || '';
var color = colors[stage] || '#999';
var stageText = stages[stage] || stage;
var amount = o.amount ? '\u00a5' + o.amount.toLocaleString() : '-';
var date = o.expected_close ? o.expected_close.slice(0,10) : '-';
html += '
'+(o.customer_name||'-')+'
'+(o.name||o.title||'-')+'
'+amount+'
'+stageText+'
'+date+'
';
});
html += '
';
oppArea.innerHTML = '
共 '+opps.length+' 条商机
' + html;
} else { oppArea.innerHTML = '
加载失败
'; }
}).catch(function() { oppArea.innerHTML = '
网络错误
'; });
}
} else {
if (oppArea) oppArea.style.display = 'none';
if (conArea) conArea.style.display = '';
if (conBtn) { conBtn.style.background='#1677ff'; conBtn.style.color='#fff'; conBtn.style.borderColor='#1677ff'; }
if (oppBtn) { oppBtn.style.background='#fff'; oppBtn.style.color='#666'; oppBtn.style.borderColor='#ddd'; }
if (conArea && !conArea.innerHTML.trim()) {
conArea.innerHTML = '
加载中...
';
api('/crm/deals/all').then(function(r) {
if (r.code === 0) {
var contracts = r.data.contracts || [];
if (!contracts.length) { conArea.innerHTML = '
暂无合同记录
'; return; }
var html = '
客户
合同名称
金额
状态
签订日期
操作
';
contracts.forEach(function(c) {
var amount = c.amount ? '\u00a5' + c.amount.toLocaleString() : '-';
var date = c.sign_date ? c.sign_date.slice(0,10) : '-';
var status = c.status || '\u6267\u884c\u4e2d';
var color = status==='\u5df2\u5c65\u7ea6'?'#52c41a':status==='\u5df2\u7ec8\u6b62'?'#f5222d':'#108ee9';
html += '