🏠 家扉管理后台

房产数据管理系统 v2.0

nt:flex-end;gap:8px;">
-
今日跟进
-
公海客户
-
逾期未跟进
// CRM客户管理 var crmCustomerData = { list: [], total: 0, page: 1, pageSize: 20 }; var crmSearchTimer = null; // ============================================ // CRM 前端完整函数包 —— 追加到 前 // ============================================ // ---- 全局状态 ---- var currentCrmCustomerId = null; var currentCrmTab = 'all'; var currentPublicSeaTab = 'sea'; var currentWorkbenchTab = 'today'; var currentCrmDetailTab = 'basic'; var currentCrmFollowFilter = 'all'; var crmSelectedIds = []; var crmPermission = { myUserId: null, subordinates: [], visibleUserIds: [] }; var crmSalesList = []; // ---- 客户列表:loadCrmCustomers(覆盖原有)---- function loadCrmCustomers(page) { if (page === undefined) page = 1; var statusEl = document.getElementById('crmFilterStatus'); var tagEl = document.getElementById('crmFilterTag'); var typeEl = document.getElementById('crmFilterType'); var sourceEl = document.getElementById('crmFilterSource'); var dateFrom = document.getElementById('crmFilterDateFrom'); var dateTo = document.getElementById('crmFilterDateTo'); var searchEl = document.getElementById('crmCustomerSearch'); var params = { page: page, pageSize: 20, tab: currentCrmTab, status: statusEl ? statusEl.value : '', tags: tagEl ? tagEl.value : '', type: typeEl ? typeEl.value : '', source: sourceEl ? sourceEl.value : '', dateFrom: dateFrom ? dateFrom.value : '', dateTo: dateTo ? dateTo.value : '', keyword: searchEl ? searchEl.value.trim() : '', myUserId: crmPermission.myUserId, subordinateIds: (crmPermission.subordinates || []).map(function(s){return s.user_id;}).join(',') }; var qs = Object.keys(params).filter(function(k){return params[k];}) .map(function(k){return encodeURIComponent(k)+'='+encodeURIComponent(params[k]);}) .join('&'); api('GET', '/crm/customers?' + qs).then(function(res) { if (res.code === 0) { crmCustomerData = res.data; renderCrmCustomerList(); renderCrmCustomerPagination(); } else { toast('加载失败: ' + res.message); } }).catch(function(err) { toast('网络错误'); }); } // ---- 客户列表:renderCrmCustomerList(覆盖原有)---- function renderCrmCustomerList() { var tbody = document.getElementById('crmCustomerList'); if (!tbody) return; var list = (crmCustomerData && crmCustomerData.list) || []; if (list.length === 0) { tbody.innerHTML = '暂无客户数据'; return; } var html = ''; list.forEach(function(c) { var sc = c.status_color || '#666'; var selChk = crmSelectedIds.indexOf(c.id) !== -1 ? 'checked' : ''; var tags = (c.tags || []).map(function(t){return '' + escHtml(t) + '';}).join(''); html += ''; html += ''; html += '' + escHtml(c.name||'') + '' + (tags?'
'+tags:'') + ''; html += '' + escHtml(c.type||'') + ''; html += '' + escHtml(c.status||'') + ''; html += '' + escHtml(c.collaborators_display||'-') + ''; html += '' + escHtml(c.phone||'') + ''; html += '' + fmtDate(c.created_at) + ''; html += '' + escHtml(c.assigned_name||'未分配') + ''; html += '' + fmtDate(c.last_follow_time) + ''; html += '' + escHtml(c.last_follow_content||'暂无') + ''; html += ''; html += ''; html += ''; html += ''; }); tbody.innerHTML = html; } // ---- 客户列表:renderCrmCustomerPagination(保留原样)---- function renderCrmCustomerPagination() { var el = document.getElementById('crmCustomerPagination'); if (!el) return; var total = (crmCustomerData && crmCustomerData.total) || 0; var page = (crmCustomerData && crmCustomerData.page) || 1; var totalPages = Math.ceil(total / 20) || 1; var html = ''; if (page > 1) html += ''; html += '第 ' + page + ' / ' + totalPages + ' 页,共 ' + total + ' 条'; if (page < totalPages) html += ''; el.innerHTML = html; } // ---- 标签栏切换 ---- // ---- 筛选重置 ---- // ---- 批量选择 ---- // ---- 批量操作 ---- function importCrmCustomers() { var fi = document.createElement('input'); fi.type = 'file'; fi.accept = '.xlsx,.xls,.csv'; fi.onchange = function(e) { var file = e.target.files[0]; if (!file) return; var fd = new FormData(); fd.append('file', file); toast('正在导入...'); fetch('/api/admin/crm/customers/import', { method: 'POST', headers: {'X-Session-Id': sessionId}, body: fd }).then(function(r) { return r.json(); }) .then(function(r) { if (r.code === 0) { toast('导入成功: ' + (r.data.success || 0) + '条'); loadCrmCustomers(1); } else { toast('导入失败: ' + (r.message || '')); } }).catch(function() { toast('网络错误'); }); }; fi.click(); } function dedupCrmCustomers() { var modal = document.getElementById('crmDuplicateModal'); if (!modal) { toast('查重弹窗未找到'); return; } modal.classList.remove('hidden'); document.getElementById('crmDupResult').innerHTML = '
正在查询...
'; api('GET', '/crm/customers/duplicates').then(function(r) { if (r.code === 0) { var dups = r.data || []; if (dups.length === 0) { document.getElementById('crmDupResult').innerHTML = '
未发现重复客户
'; } else { var h = ''; dups.forEach(function(d) { h += ''; }); h += '
姓名手机号重复数操作
' + (d.name || '-') + '' + (d.mobile || '-') + '' + d.count + '
'; document.getElementById('crmDupResult').innerHTML = h; } } else { document.getElementById('crmDupResult').innerHTML = '
查询失败: ' + (r.message || '') + '
'; } }).catch(function(e) { document.getElementById('crmDupResult').innerHTML = '
网络错误
'; }); } function closeCrmDuplicateModal() { var modal = document.getElementById('crmDuplicateModal'); if (modal) modal.classList.add('hidden'); } function downloadCSV(filename, rows) { var csv = rows.map(function(r){return r.map(function(v){return '"' + String(v).replace(/"/g,'""') + '"';}).join(',');}).join('\n'); var BOM = '\uFEFF'; var blob = new Blob([BOM + csv], {type:'text/csv;charset=utf-8'}); var a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = filename; a.click(); } // ---- 防抖 ---- var crmSearchTimer = null; // ---- 客户详情 ---- function viewCrmCustomer(id) { currentCrmCustomerId = id; var modal = document.getElementById('crmDetailModal'); modal.classList.remove('hidden'); loadCrmDetail(id); } function closeCrmDetailModal() { document.getElementById('crmDetailModal').classList.add('hidden'); currentCrmCustomerId = null; } function loadCrmDetail(id) { api('GET', '/crm/customers/' + id).then(function(res) { if (res.code !== 0) { toast('加载失败: ' + res.message); return; } var c = res.data; // 更新头部 document.getElementById('crmDetailName').textContent = c.name; document.getElementById('crmDetailStatusBadge').textContent = c.status || '待跟进'; document.getElementById('crmDetailStatusBadge').style.cssText = 'background:' + (c.status_color||'#fa8c16') + '20;color:' + (c.status_color||'#fa8c16') + ';padding:2px 8px;border-radius:4px;'; document.getElementById('crmDetailOwner').textContent = c.assigned_name || '未分配'; document.getElementById('crmDetailDept').textContent = c.department || '-'; document.getElementById('crmDetailNextFollow').textContent = c.next_follow_time ? fmtDate(c.next_follow_time) : '-'; document.getElementById('crmDetailPublicSea').textContent = c.is_in_public_sea ? '公海中' : '-'; // 渲染左侧基本资料 renderCrmDetailBasic(c); // 渲染跟进时间线 renderCrmFollowsTimeline(c.follow_records || []); }).catch(function(){ toast('网络错误'); }); } function renderCrmDetailBasic(c) { var el = document.getElementById('crmDetailBasic'); if (!el) return; var tags = (c.tags||[]).map(function(t){return ''+escHtml(t)+'';}).join(' '); var html = ''; // 基本信息 html += '

基本信息

'; html += '
客户名称' + escHtml(c.name||'') + '
'; html += '
客户类型' + escHtml(c.type||'') + '
'; html += '
客户标签' + (tags||'-') + '
'; html += '
公司名称' + escHtml(c.company_name||'') + '
'; html += '
预算' + escHtml(c.budget||'') + '
'; html += '
意向区域' + escHtml((c.district_preference||[]).join(', ')) + '
'; html += '
意向户型' + escHtml((c.room_preference||[]).join(', ')) + '
'; html += '
意向楼盘' + escHtml(c.source_building_name||'') + '
'; html += '
客户来源' + escHtml(c.source||'') + '
'; html += '
优先级' + escHtml(c.priority||'') + '
'; html += '
'; // 联系信息 html += '

联系信息

'; html += '
手机号' + escHtml(c.phone||'') + '
'; html += '
微信' + escHtml(c.wechat||'') + '
'; html += '
QQ' + escHtml(c.qq||'') + '
'; html += '
年龄' + escHtml(c.age_range||'') + '
'; html += '
'; // 归属信息 html += '

归属信息

'; html += '
负责人' + escHtml(c.assigned_name||'未分配') + '
'; html += '
部门' + escHtml(c.department||'') + '
'; html += '
协作人' + escHtml((c.collaborators||[]).map(function(v){return v.user_name;}).join(', ')||'-') + '
'; html += '
创建人' + escHtml(c.created_by_name||'') + '
'; html += '
'; // 跟进信息 html += '

跟进信息

'; html += '
跟进状态' + escHtml(c.status||'') + '
'; html += '
客户阶段' + escHtml(c.stage||'') + '
'; html += '
首次联系' + fmtDate(c.first_contact_time) + '
'; html += '
最近跟进' + fmtDate(c.last_follow_time) + '
'; html += '
下次跟进' + fmtDate(c.next_follow_time) + '
'; html += '
跟进次数' + (c.follow_count||0) + ' 次
'; html += '
逾期天数' + (c.no_follow_days||0) + ' 天
'; html += '
'; // 成交信息 if (c.is_deal || c.stage==='成交') { html += '

成交信息

'; html += '
成交金额' + (c.deal_amount ? c.deal_amount + '万' : '-') + '
'; html += '
成交日期' + fmtDate(c.deal_date) + '
'; html += '
'; } 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 '
' + escHtml(cm.content) + '
'; }).join(''); html += '
'; html += ''; html += ''; if (imgs) html += ''; html += ''; if (comments) html += comments; html += '
'; }); html += '
'; }); el.innerHTML = html; } function previewImg(src) { window.open(src, '_blank'); } function editCrmFollow(id) { toast('编辑跟进功能开发中...'); } function addCrmFollowComment(id) { var content = prompt('请输入评论内容:'); if (!content) return; api('POST', '/crm/follows/' + id + '/comments', {content: content}).then(function(res){ toast(res.code===0 ? '评论成功' : '评论失败'); if (res.code===0) loadCrmDetail(currentCrmCustomerId); }); } function deleteCrmFollow(id) { if (!confirm('确认删除此跟进记录?')) return; api('DELETE', '/crm/follows/' + id).then(function(res){ toast(res.code===0 ? '删除成功' : '删除失败'); if (res.code===0) loadCrmDetail(currentCrmCustomerId); }); } function filterCrmFollows(type, el) { currentCrmFollowFilter = type || 'all'; document.querySelectorAll('.crm-follow-timeline .filter-tab').forEach(function(a){ a.classList.remove('active'); }); if (el) el.classList.add('active'); // 已有数据直接本地过滤 // 实际场景需要重新请求带参数 } function editCrmCustomer() { toast('编辑客户功能开发中...'); } function transferCrmCustomer() { toast('转移客户功能开发中...'); } function editCrmCollaborators() { toast('编辑协作人功能开发中...'); } function fullscreenCrmDetail() { var modal = document.getElementById('crmDetailModal'); if (document.webkitFullscreenElement) document.webkitCancelFullscreen(); else modal.querySelector('.modal').webkitRequestFullscreen && modal.querySelector('.modal').webkitRequestFullscreen(); } function printCrmDetail() { window.print(); } function dropdownCrmNew(el) { var menu = el ? el.nextElementSibling : null; if (menu && menu.classList.contains('crm-dropdown-menu')) { menu.classList.toggle('show'); } } // ---- 写跟进 ---- function openCrmFollowModal(customerId) { var cid = customerId || currentCrmCustomerId; if (!cid) { toast('请先选择客户'); return; } document.getElementById('crmFollowModal').classList.remove('hidden'); document.getElementById('crmFollowContent').value = ''; document.getElementById('crmFollowType').value = '电销'; document.getElementById('crmFollowTime').value = new Date().toISOString().slice(0,16); document.getElementById('crmFollowNextTime').value = ''; document.getElementById('crmFollowStatus').value = '跟进中'; document.getElementById('crmAiResult').innerHTML = ''; // 填充客户名 var sel = document.getElementById('crmFollowCustomer'); if (sel) { var name = document.getElementById('crmDetailName') ? document.getElementById('crmDetailName').textContent : ''; sel.value = name; } } function closeCrmFollowModal() { document.getElementById('crmFollowModal').classList.add('hidden'); } function saveCrmFollow() { var customer_id = currentCrmCustomerId; if (!customer_id) { toast('客户ID缺失'); return; } var follow_type = document.getElementById('crmFollowType').value; var follow_content = document.getElementById('crmFollowContent').value.trim(); var follow_time = document.getElementById('crmFollowTime').value; var next_follow_time = document.getElementById('crmFollowNextTime').value; var status_after = document.getElementById('crmFollowStatus').value; if (!follow_content) { toast('跟进内容必填'); return; } var body = { customer_id: customer_id, follow_type: follow_type, follow_content: follow_content, status_after: status_after }; if (follow_time) body.created_at = new Date(follow_time).toISOString(); if (next_follow_time) body.next_follow_time = new Date(next_follow_time).toISOString(); api('POST', '/crm/follows', body).then(function(res) { toast(res.code===0 ? '跟进保存成功' : '保存失败: ' + res.message); if (res.code===0) { closeCrmFollowModal(); loadCrmDetail(customer_id); loadCrmCustomers(1); } }).catch(function(){ toast('网络错误'); }); } function crmUploadImage() { document.getElementById('crmFollowImageInput').click(); } function onCrmFollowImages(files) { toast('已选择 ' + (files ? files.length : 0) + ' 张图片(实际功能需上传到服务器)'); } function crmUploadAttachment() { document.getElementById('crmFollowAttachInput').click(); } function openCrmWechatInput() { toast('微信写跟进功能开发中...'); } function openCrmAtMention() { toast('@提醒功能开发中...'); } function crmOcrFollow() { var input = document.createElement('input'); input.type = 'file'; input.accept = 'image/*'; input.onchange = function(e) { var file = e.target.files[0]; if (!file) return; var reader = new FileReader(); reader.onload = function(ev) { var base64 = ev.target.result.replace(/^data:image\/\w+;base64,/, ''); document.getElementById('crmAiResult').innerHTML = '🔍 识别中...'; api('POST', '/crm/ai-ocr-follow', {image_base64: base64}).then(function(res) { if (res.code === 0) { document.getElementById('crmFollowContent').value = res.data.text || ''; document.getElementById('crmAiResult').innerHTML = '✅ OCR识别成功'; } else { document.getElementById('crmAiResult').innerHTML = '❌ ' + (res.message||'') + ''; } }).catch(function(){ document.getElementById('crmAiResult').innerHTML = '❌ 网络错误'; }); }; reader.readAsDataURL(file); }; input.click(); } function crmAiGenerateFollowup() { if (!currentCrmCustomerId) { toast('请先选择客户'); return; } document.getElementById('crmAiResult').innerHTML = '🤖 AI生成中...'; api('POST', '/crm/ai-suggest-followup', { customer_id: currentCrmCustomerId, last_follow_content: document.getElementById('crmFollowContent').value.trim() }).then(function(res) { if (res.code === 0 && res.data && res.data.suggestion) { document.getElementById('crmFollowContent').value = res.data.suggestion; document.getElementById('crmAiResult').innerHTML = '✅ AI话术已生成'; } else { document.getElementById('crmAiResult').innerHTML = '⚠️ ' + (res.message||'AI未配置') + ''; } }).catch(function(){ document.getElementById('crmAiResult').innerHTML = '❌ 网络错误'; }); } // ---- 新增客户弹窗 ---- var crmCustomerModalData = {}; function openCrmCustomerModal() { var modal = document.getElementById('crmCustomerModal'); if (!modal) { toast('弹窗未找到'); return; } // Clear form fields var nameEl = document.getElementById('crmNewName'); var phoneEl = document.getElementById('crmNewPhone'); var typeEl = document.getElementById('crmNewType'); var buildingEl = document.getElementById('crmNewBuilding'); var remarkEl = document.getElementById('crmNewRemark'); if (nameEl) nameEl.value = ''; if (phoneEl) phoneEl.value = ''; if (typeEl) typeEl.value = '个人客户'; if (buildingEl) buildingEl.value = ''; if (remarkEl) remarkEl.value = ''; modal.classList.remove('hidden'); } function closeCrmCustomerModal() { var modal = document.getElementById('crmCustomerModal'); if (modal) modal.classList.add('hidden'); } function doSaveCrmCustomer() { var name = document.getElementById('crmNewName').value.trim(); var phone = document.getElementById('crmNewPhone').value.trim(); if (!name || !phone) { toast('姓名和手机号必填'); return; } api('POST', '/crm/customers', { name: name, phone: phone, type: document.getElementById('crmNewType').value, status: document.getElementById('crmNewStatus').value, priority: document.getElementById('crmNewPriority').value, source: document.getElementById('crmNewSource').value, source_building_name: document.getElementById('crmNewBuilding').value, remark: document.getElementById('crmNewRemark').value }).then(function(res) { toast(res.code===0 ? '创建成功' : '创建失败: ' + res.message); if (res.code===0) { document.getElementById('crmCustomerModal').classList.add('hidden'); loadCrmCustomers(1); } }); } // ---- 线索池 ---- function loadCrmLeads(page) { if (page === undefined) page = 1; var keyword = document.getElementById('leadSearch') ? document.getElementById('leadSearch').value.trim() : ''; api('GET', '/crm/leads?page=' + page + '&pageSize=20' + (keyword ? '&keyword=' + encodeURIComponent(keyword) : '')).then(function(res) { if (res.code === 0) { renderCrmLeadsList(res.data.list || []); renderCrmLeadsPagination(res.data); } }); } function renderCrmLeadsList(list) { var tbody = document.getElementById('crmLeadsList'); if (!tbody) return; if (!list.length) { tbody.innerHTML = '暂无线索'; return; } var html = ''; list.forEach(function(l) { html += ''; html += ''; html += '' + escHtml(l.name||'') + ''; html += '' + escHtml(l.phone||'') + ''; html += '' + escHtml(l.building||l.buildingName||'') + ''; html += '' + escHtml(l.type||'') + ''; html += '' + fmtDate(l.submitTime) + ''; html += '' + (l.is_synced ? '已同步' : '未同步') + ''; html += ''; html += ''; html += ''; html += ''; }); tbody.innerHTML = html; } function renderCrmLeadsPagination(data) { var el = document.getElementById('crmLeadsPagination'); if (!el) return; var total = data.total || 0, page = data.page || 1, totalPages = Math.ceil(total/20)||1; var html = ''; if (page > 1) html += ''; html += '第 ' + page + ' / ' + totalPages + ' 页,共 ' + total + ' 条'; if (page < totalPages) html += ''; el.innerHTML = html; } function onLeadCheck(chk) {} function toggleLeadSelectAll(checked) { document.querySelectorAll('#crmLeadsList input[type=checkbox]').forEach(function(c){c.checked=checked;}); } async function batchAssignLeads() { try { const res = await api('GET', '/crm/leads'); if (!res.data || res.data.length === 0) { toast('暂无待分配线索'); return; } const unassigned = res.data.filter(l => !l.assigned_to || l.assigned_to === ''); if (unassigned.length === 0) { toast('暂无待分配线索'); return; } const res2 = await api('POST', '/crm/leads/batch-assign', { ids: unassigned.map(l => l.id) }); if (res2.code === 0) { toast('成功分配 ' + (res2.data?.count || unassigned.length) + ' 条线索'); if (typeof loadLeads === 'function') loadLeads(1); } else { toast(res2.message || '分配失败'); } } catch(e) { toast('网络错误:' + e.message); } } async function exportLeads() { try { const res = await api('GET', '/crm/leads?page=1&pageSize=9999'); if (!res.data || res.data.length === 0) { toast('无数据可导出'); return; } const rows = res.data; const headers = ['姓名','手机','来源','状态','创建时间']; const csv_data = [headers.join(',')]; for (const row of rows) { csv_data.push([row.name||'', row.phone||'', row.source||'', row.status||'', row.created_at||''].map(v => '"' + String(v).replace(/"/g,'""') + '"').join(',')); } const BOM = ''; const blob = new Blob([BOM + csv_data.join('\n'], { type: 'text/csv;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = '线索池_' + new Date().toISOString().slice(0,10) + '.csv'; a.click(); URL.revokeObjectURL(url); toast('导出成功,共 ' + rows.length + ' 条'); } catch(e) { toast('导出失败:' + e.message); } } async function assignLead(id) { try { const res = await api('POST', '/crm/leads/' + id + '/assign', { to_user_id: 1, to_user_name: 'admin' }); if (res.code === 0) { toast('分配成功'); if (typeof loadLeads === 'function') loadLeads(1); } else { toast(res.message || '分配失败'); } } catch(e) { toast('网络错误:' + e.message); } } function convertLead(id) { api('POST', '/crm/leads/' + id + '/assign', {to_user_id: 1, to_user_name: 'admin'}).then(function(res){ toast(res.code===0 ? '转化成功' : '转化失败: ' + res.message); if (res.code===0) loadCrmLeads(1); }); } // ---- 客户公海 ---- function loadCrmPublicSea(page) { if (page === undefined) page = 1; var keyword = document.getElementById('publicSeaSearch') ? document.getElementById('publicSeaSearch').value.trim() : ''; api('GET', '/crm/public-sea?type=' + currentPublicSeaTab + '&page=' + page + '&pageSize=20' + (keyword ? '&keyword=' + encodeURIComponent(keyword) : '')).then(function(res) { if (res.code === 0) { renderCrmPublicSeaList(res.data.list || []); renderCrmPublicSeaPagination(res.data); } }); } function renderCrmPublicSeaList(list) { var tbody = document.getElementById('crmPublicSeaList'); if (!tbody) return; if (!list.length) { tbody.innerHTML = '暂无数据'; return; } var html = ''; list.forEach(function(c) { html += ''; html += '' + escHtml(c.name||'') + ''; html += '' + escHtml(c.assigned_name||'无') + ''; html += '' + fmtDate(c.public_sea_time) + ''; html += '' + (c.no_follow_days||0) + ' 天'; html += '' + escHtml(c.public_sea_reason||'') + ''; html += ''; if (currentPublicSeaTab === 'sea') { html += ''; } else { html += ''; } html += ''; }); tbody.innerHTML = html; } function renderCrmPublicSeaPagination(data) { var el = document.getElementById('crmPublicSeaPagination'); if (!el) return; var total = data.total||0, page = data.page||1, totalPages = Math.ceil(total/20)||1; var html = ''; if (page > 1) html += ''; html += '第 ' + page + ' / ' + totalPages + ' 页,共 ' + total + ' 条'; if (page < totalPages) html += ''; el.innerHTML = html; } function claimCustomer(id) { api('POST', '/crm/public-sea/' + id + '/claim', {}).then(function(res) { toast(res.code===0 ? '领取成功' : '领取失败: ' + res.message); if (res.code===0) loadCrmPublicSea(1); }); } // ---- 跟进工作台 ---- function loadCrmWorkbench(tab) { var t = tab || currentWorkbenchTab; currentWorkbenchTab = t; document.querySelectorAll('#tabCrmWorkbench .crm-tab').forEach(function(a){ a.classList.remove('active'); }); var el = document.querySelector('#tabCrmWorkbench .crm-tab[data-tab="' + t + '"]'); if (el) el.classList.add('active'); var content = document.getElementById('crmWorkbenchContent'); if (!content) return; content.innerHTML = '
加载中...
'; var apiPath = '/crm/workbench/' + (t === 'today' ? 'today-tasks' : t === 'upcoming' ? 'upcoming' : 'overdue'); api('GET', apiPath).then(function(res) { if (res.code === 0) { var items = res.data || []; if (!items.length) { content.innerHTML = '
暂无数据
'; return; } var html = '
'; items.forEach(function(item) { var c = item.customer || {}; var timeStr = item.reminder_time ? fmtDateTime(item.reminder_time) : ''; html += '
'; html += '
'; html += '
' + escHtml(c.name||'') + ' ' + escHtml(c.phone||'') + '
'; html += '
' + escHtml(item.content||'') + '
'; if (timeStr) html += '
⏰ ' + timeStr + '
'; html += '
'; html += '
'; 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 += '
'; html += '' + escHtml(u.username||'') + '' + escHtml(dept) + '
'; }); el.innerHTML = html; } var _selectedTransferUserId = null; var _selectedTransferUserName = ''; function selectCrmTransferUser(id, name) { _selectedTransferUserId = id; _selectedTransferUserName = name; document.getElementById('crmBatchTransferUserList').querySelectorAll('div').forEach(function(d){ d.style.background = ''; }); event.target.closest && (event.target.closest('div').style.background = '#e6f4ff'); } function confirmCrmBatchTransferToSea() { var reason = document.getElementById('crmBatchSeaReason').value; api('POST', '/crm/customers/batch-to-sea', { ids: crmSelectedIds, reason: reason }).then(function(res) { toast(res.code === 0 ? '移入公海成功' : '失败: ' + res.message); if (res.code === 0) { crmSelectedIds = []; updateCrmBatchBar(); loadCrmCustomers(1); } closeCrmBatchTransferModal(); }); } function confirmCrmBatchTransferToUser() { if (!_selectedTransferUserId) { toast('请选择目标销售'); return; } api('POST', '/crm/customers/batch-transfer', { ids: crmSelectedIds, to_user_id: _selectedTransferUserId, to_user_name: _selectedTransferUserName }).then(function(res) { toast(res.code === 0 ? '转移成功' : '失败: ' + res.message); if (res.code === 0) { crmSelectedIds = []; updateCrmBatchBar(); loadCrmCustomers(1); } closeCrmBatchTransferModal(); }); } function closeCrmBatchTransferModal() { _selectedTransferUserId = null; _selectedTransferUserName = ''; closeModal('crmBatchTransferModal'); } // ── 批量编辑 ──────────────────────────────────────────────── function batchEditCrm() { if (crmSelectedIds.length === 0) { toast('请先选择客户'); return; } closeAllCrmModals(); document.getElementById('crmBatchEditCount').textContent = crmSelectedIds.length; document.getElementById('crmBatchEditType').value = ''; document.getElementById('crmBatchEditStatus').value = ''; document.getElementById('crmBatchEditPriority').value = ''; document.getElementById('crmBatchEditRemark').value = ''; openModal('crmBatchEditModal'); } function confirmCrmBatchEdit() { var updates = {}; var t = document.getElementById('crmBatchEditType').value; var s = document.getElementById('crmBatchEditStatus').value; var p = document.getElementById('crmBatchEditPriority').value; var r = document.getElementById('crmBatchEditRemark').value.trim(); if (t) updates.type = t; if (s) updates.status = s; if (p) updates.priority = p; if (r) updates.remark = r; if (Object.keys(updates).length === 0) { toast('请至少选择一个要修改的字段'); return; } api('POST', '/crm/customers/batch-update', { ids: crmSelectedIds, updates: updates }).then(function(res) { toast(res.code === 0 ? '批量编辑成功' : '失败: ' + res.message); if (res.code === 0) { crmSelectedIds = []; updateCrmBatchBar(); loadCrmCustomers(1); } closeCrmBatchEditModal(); }); } function closeCrmBatchEditModal() { closeModal('crmBatchEditModal'); } // ── 批量发送短信 ────────────────────────────────────────────── function batchSendSmsCrm() { if (crmSelectedIds.length === 0) { toast('请先选择客户'); return; } closeAllCrmModals(); document.getElementById('crmBatchSmsCount').textContent = crmSelectedIds.length; document.getElementById('crmBatchSmsCount2').textContent = crmSelectedIds.length; document.getElementById('crmBatchSmsContent').value = ''; openModal('crmBatchSmsModal'); } function confirmCrmBatchSms() { var content = document.getElementById('crmBatchSmsContent').value.trim(); if (!content) { toast('请输入短信内容'); return; } api('POST', '/crm/customers/batch-sms', { ids: crmSelectedIds, content: content }).then(function(res) { toast(res.code === 0 ? '短信已记录' : '失败: ' + res.message); if (res.code === 0) { crmSelectedIds = []; updateCrmBatchBar(); } closeCrmBatchSmsModal(); }); } function closeCrmBatchSmsModal() { closeModal('crmBatchSmsModal'); } // ── 批量删除 ──────────────────────────────────────────────── function batchDeleteCrm() { if (crmSelectedIds.length === 0) { toast('请先选择客户'); return; } if (!confirm('确定删除选中的 ' + crmSelectedIds.length + ' 个客户?(可从回收站恢复)')) return; api('POST', '/crm/customers/batch-soft-delete', { ids: crmSelectedIds }).then(function(res) { toast(res.code === 0 ? '删除成功' : '失败: ' + res.message); if (res.code === 0) { crmSelectedIds = []; updateCrmBatchBar(); loadCrmCustomers(1); } }); } // 退回公海 async function returnToPublicSea(customerId, poolId) { var ids = []; if (poolId) { ids = [poolId]; if (!ids.length) { toast('请先选择线索'); return; } ids.forEach(function(id) { api('DELETE', '/crm/leads/' + id).then(function(res) { if (res.code === 0) { loadCrmLeadsPool(); toast('线索已退回公海'); } else { toast(res.msg || '退回失败'); } }); }); return; } if (customerId) { ids = [customerId]; } else if (typeof selectedCrmIds !== 'undefined' && selectedCrmIds.length > 0) { ids = selectedCrmIds; } else if (window._selectedCrmCustomerIds && window._selectedCrmCustomerIds.length > 0) { ids = window._selectedCrmCustomerIds; } else { var checkedRows = document.querySelectorAll('.crm-table tbody tr input[type="checkbox"]:checked'); checkedRows.forEach(function(cb) { ids.push(cb.value); }); } if (!ids.length) { toast('请先选择客户'); return; } ids.forEach(function(id) { api('DELETE', '/crm/customers/' + id).then(function(res) { if (res.code === 0) { toast('客户已退回公海'); loadCrmCustomers(); } else { toast(res.msg || '退回失败'); } }); }); } // ── 批量导出 ──────────────────────────────────────────────── function exportCrmCustomers(limit) { limit = limit || 'selected'; var params = { limit: limit }; if (limit === 'selected' && crmSelectedIds.length > 0) params.ids = crmSelectedIds.join(','); if (currentCrmTab) params.tab = currentCrmTab; var status = document.getElementById('crmFilterStatusBtn').getAttribute('data-values') || ''; var source = document.getElementById('crmFilterSourceBtn').getAttribute('data-values') || ''; if (status) params.status = status; if (source) params.source = source; var qs = Object.keys(params).filter(function(k){ return params[k]; }).map(function(k){ return encodeURIComponent(k) + '=' + encodeURIComponent(params[k]); }).join('&'); var sid = localStorage.getItem('crm_session_id') || SESSION; var a = document.createElement('a'); a.href = '/api/admin/crm/customers/export?' + qs + (sid ? '&_sid=' + sid : ''); a.download = ''; a.click(); toast('开始导出...'); } // ── 批量合并 ──────────────────────────────────────────────── // ── 批量添加协作人 ─────────────────────────────────────────── function batchAddCollabCrm() { if (crmSelectedIds.length === 0) { toast('请先选择客户'); return; } closeAllCrmModals(); loadCrmBatchCollabUsers(''); openModal('crmBatchCollabModal'); } var _crmBatchCollabUsers = []; function loadCrmBatchCollabUsers(kw) { api('GET', '/crm/sales').then(function(res) { if (res.code !== 0) return; _crmBatchCollabUsers = (res.data || []).filter(function(u) { return u.user_id != CUR_USER.userId; }); renderCrmBatchCollabList(kw || ''); }); } function filterCrmBatchCollabUsers(kw) { renderCrmBatchCollabList(kw); } function renderCrmBatchCollabList(kw) { var el = document.getElementById('crmBatchCollabUserList'); if (!el) return; var filtered = _crmBatchCollabUsers.filter(function(u) { return !kw || (u.username||'').toLowerCase().includes(kw.toLowerCase()); }); if (!filtered.length) { el.innerHTML = '
无匹配销售
'; return; } var html = ''; filtered.forEach(function(u) { html += '
'; html += escHtml(u.username||'') + ' ' + escHtml(u.department||'') + '
'; }); el.innerHTML = html; } var _selectedCollabUserId = null, _selectedCollabUserName = ''; function selectCrmBatchCollabUser(id, name) { _selectedCollabUserId = id; _selectedCollabUserName = name; document.querySelectorAll('#crmBatchCollabUserList div').forEach(function(d){ d.style.background = ''; }); event.target.closest && (event.target.closest('div').style.background = '#e6f4ff'); } function confirmCrmBatchCollab() { if (!_selectedCollabUserId) { toast('请选择协作人'); return; } var perm = document.getElementById('crmBatchCollabPerm').value; api('POST', '/crm/customers/batch-add-collaborators', { ids: crmSelectedIds, collaborators: [{ user_id: _selectedCollabUserId, user_name: _selectedCollabUserName, permission: perm }] }).then(function(res) { toast(res.code === 0 ? '添加协作人成功' : '失败: ' + res.message); if (res.code === 0) { crmSelectedIds = []; updateCrmBatchBar(); } closeCrmBatchCollabModal(); }); } function closeCrmBatchCollabModal() { _selectedCollabUserId = null; _selectedCollabUserName = ''; closeModal('crmBatchCollabModal'); } // ── 批量锁定/解锁 ──────────────────────────────────────────── // ── 跟进模式 ──────────────────────────────────────────────── // ── 多选筛选通用弹窗 ───────────────────────────────────────── var _crmMultiSelectType = ''; var _crmMultiSelectValues = []; var _crmMultiSelectBlank = false; var CRM_MULTI_OPTIONS = { tag: ['高意向','急购房','已看房','投资客','改善型','刚需','别墅','商铺','写字楼'], status: ['待跟进','跟进中','已约访','已看房','价格谈判','已成交','已流失'], type: ['个人客户','企业客户','渠道客户'], source: ['小程序-booking','小程序-consult','手动录入','Excel导入','广告','社交推广','研讨会','搜索引擎','客户介绍','独立开发','代理商','其他','搜客宝','机器人','搜客宝特别版'] }; function openCrmMultiSelectModal(type, btnEl) { closeAllCrmModals(); _crmMultiSelectType = type; _crmMultiSelectBlank = false; // 读取当前已选值 var btn = document.getElementById('crmFilter'+type.charAt(0).toUpperCase()+type.slice(1)+'Btn'); _crmMultiSelectValues = btn ? (btn.getAttribute('data-values') || '').split(',').filter(Boolean) : []; document.getElementById('crmMultiSelectBlank').checked = false; document.getElementById('crmMultiSelectTitle').textContent = { tag:'选择标签', status:'选择跟进状态', type:'选择客户类型', source:'选择客户来源' }[type] || '多选筛选'; document.getElementById('crmMultiSelectSearch').value = ''; renderCrmMultiSelectOptions(''); var modal = document.getElementById('crmMultiSelectModal'); modal.classList.remove('hidden'); } function renderCrmMultiSelectOptions(kw) { var el = document.getElementById('crmMultiSelectOptions'); if (!el) return; var options = (CRM_MULTI_OPTIONS[_crmMultiSelectType] || []).filter(function(o) { return !kw || o.toLowerCase().includes(kw.toLowerCase()); }); if (!options.length) { el.innerHTML = '
无匹配选项
'; 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 += ''; }); 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 += ''; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; }); html += '
商机名称阶段金额预计成交操作
' + escHtml(o.name || '') + '' + escHtml(o.stage || '') + '' + (o.amount ? '¥' + o.amount.toLocaleString() : '-') + '' + (o.expected_date ? fmtDate(o.expected_date) : '-') + '
'; el.innerHTML = html; } // ── 客户详情:合同Tab渲染 ──────────────────────────────────── function renderCrmContracts(list) { var el = document.getElementById('crmContractList'); if (!el) return; if (!list || !list.length) { el.innerHTML = '
暂无合同记录
'; return; } var html = ''; list.forEach(function(c) { var statusColor = (c.status === '已签署') ? '#52c41a' : '#fa8c16'; html += ''; html += ''; html += ''; html += ''; html += ''; html += ''; }); html += '
合同编号金额签署日期状态
' + escHtml(c.no || '') + '' + (c.amount ? '¥' + c.amount.toLocaleString() : '-') + '' + (c.signed_at ? fmtDate(c.signed_at) : '-') + '' + escHtml(c.status || '') + '
'; el.innerHTML = html; } // ── 客户详情:Tab切换 ──────────────────────────────────────── var _crmDetailTab = 'basic'; function switchCrmDetailTab(tab) { _crmDetailTab = tab; var tabs = document.querySelectorAll('.crm-detail-tab'); tabs.forEach(function(el){ el.classList.remove('active'); }); var tabEl = document.querySelector('.crm-detail-tab[data-tab="' + tab + '"]'); if (tabEl) tabEl.classList.add('active'); var basicEl = document.getElementById('crmDetailBasic'); var oppEl = document.getElementById('crmOppSection'); var conEl = document.getElementById('crmContractSection'); if (basicEl) basicEl.style.display = tab === 'basic' ? '' : 'none'; if (oppEl) oppEl.style.display = tab === 'opp' ? '' : 'none'; if (conEl) conEl.style.display = tab === 'contract' ? '' : 'none'; // 懒加载 if (tab === 'opp' && currentCrmCustomerId) { var oppList = document.getElementById('crmOppList'); if (oppList && !oppList.innerHTML.trim()) { api('GET', '/crm/customers/' + currentCrmCustomerId + '/opportunities').then(function(res) { if (res.code === 0) renderCrmOpportunities(res.data || []); }); } } if (tab === 'contract' && currentCrmCustomerId) { var conList = document.getElementById('crmContractList'); if (conList && !conList.innerHTML.trim()) { api('GET', '/crm/customers/' + currentCrmCustomerId + '/contracts').then(function(res) { if (res.code === 0) renderCrmContracts(res.data || []); }); } } } // ── 商机删除 ───────────────────────────────────────────────── function deleteOpp(id) { if (!confirm('确定删除该商机?')) return; api('DELETE', '/crm/customers/' + currentCrmCustomerId + '/opportunities/' + id).then(function(res) { toast(res.code === 0 ? '已删除' : '删除失败'); if (res.code === 0 && currentCrmCustomerId) { api('GET', '/crm/customers/' + currentCrmCustomerId + '/opportunities').then(function(r2) { if (r2.code === 0) renderCrmOpportunities(r2.data || []); }); } }); } // ── 公海Tab切换 ────────────────────────────────────────────── var _publicSeaTab = 'sea'; function switchPublicSeaTab(tab) { _publicSeaTab = tab; var tabs = document.querySelectorAll('#tabCrmPublicSea .crm-tab'); tabs.forEach(function(el){ el.classList.remove('active'); }); var el = document.querySelector('#tabCrmPublicSea .crm-tab[data-tab="' + tab + '"]'); if (el) el.classList.add('active'); var listEl = document.getElementById('crmPublicSeaList'); if (tab === 'sea') { if (listEl) listEl.style.display = ''; loadCrmPublicSea(); } else { if (listEl) { listEl.style.display = ''; listEl.innerHTML = '成交客户库功能开发中...'; } } } // ── 线索池刷新 ─────────────────────────────────────────────── function refreshCrmLeads() { loadCrmLeads(); toast('线索已刷新'); } // ── 更多下拉菜单切换 ───────────────────────────────────────── var _crmMoreMenuVisible = false; function toggleCrmMoreMenu(btn) { var menu = document.getElementById('crmMoreMenu'); if (!menu) return; _crmMoreMenuVisible = !_crmMoreMenuVisible; menu.style.display = _crmMoreMenuVisible ? '' : 'none'; // 点击其他区域关闭 if (_crmMoreMenuVisible) { var closeHandler = function(e) { if (!menu.contains(e.target) && !btn.contains(e.target)) { menu.style.display = 'none'; _crmMoreMenuVisible = false; document.removeEventListener('click', closeHandler); } }; setTimeout(function() { document.addEventListener('click', closeHandler); }, 0); } }