let currentUser=null, cache={companies:[],users:[],customers:[],leads:[],contracts:[],followups:[],attachments:[],learningMaterials:[]}, currentView='customers', selectedCustomer=null, selectedContract=null;
const $=s=>document.querySelector(s);
const esc=s=>String(s??'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));
const roles={super_admin:'集团总管理员',company_admin:'分公司管理员',employee:'普通员工'};
const serviceTypes=['三联屏宣传片','短视频拍摄','宣传片拍摄','巨量本地推投流','GEO业务','抖音陪跑业务','抖音直播业务','抖音达人探店业务'];
function serviceSummary(services=[]){return services.map(s=>`${s.type} ¥${Number(s.amount||0).toFixed(2)}`).join('、')||'未添加服务'}
function progressText(s){if(['巨量本地推投流'].includes(s.type))return `${s.accountStatus||'未开户'} · ${s.durationValue||1}${s.durationUnit||'年'}`;if(['GEO业务'].includes(s.type))return `${s.durationValue||1}${s.durationUnit||'年'}`;if(s.type==='抖音陪跑业务')return `完成：${Number(s.completed||0)?'是':'否'}`;return `总数：${Number(s.total||0)}，已完成：${Number(s.completed||0)}，未完成：${Math.max(0,Number(s.total||0)-Number(s.completed||0))}`;}
function progressMeta(s){const isAccount=s.type==='巨量本地推投流',isDuration=['巨量本地推投流','GEO业务'].includes(s.type),isRun=s.type==='抖音陪跑业务';if(isAccount){const done=(s.accountStatus||'未开户')==='已开户'?1:0;return {total:1,done,left:1-done,percent:done*100,target:'开户交付',status:s.accountStatus||'未开户',extra:`合作 ${Number(s.durationValue||1)}${s.durationUnit||'年'}${s.startDate||s.endDate?` · ${s.startDate||'未设开始'} 至 ${s.endDate||'未设结束'}`:''}`};}if(isDuration){return {total:1,done:0,left:1,percent:0,target:`合作 ${Number(s.durationValue||1)}${s.durationUnit||'年'}`,status:'周期服务',extra:`${s.startDate||'未设开始'} 至 ${s.endDate||'未设结束'}`};}if(isRun){const done=Number(s.completed||0)>0?1:0;return {total:1,done,left:1-done,percent:done*100,target:s.remark||'陪跑内容未填写',status:done?'已完成':'待完成',extra:s.remark||'请在编辑模式填写陪跑内容'};}const total=Math.max(0,Number(s.total||0)),done=Math.min(total,Math.max(0,Number(s.completed||0))),left=Math.max(0,total-done),unit=s.type==='宣传片拍摄'?'分钟':s.type==='抖音直播业务'?'场':s.type==='抖音达人探店业务'?'人':'条';return {total,done,left,percent:total?Math.round(done/total*100):0,target:`${total}${unit}`,status:total===0?'待设置':done>=total?'已完成':done>0?'进行中':'未开始',extra:''};}
function progressBar(percent){const p=Math.max(0,Math.min(100,Number(percent)||0));return `<div class="progress-track" title="完成 ${p}%"><span style="width:${p}%"></span></div><b class="progress-percent">${p}%</b>`;}
function toDateTimeLocal(v){if(!v)return '';const m=String(v).match(/(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2})/);return m?`${m[1]}T${m[2]}`:''}
function fromDateTimeLocal(v){return String(v||'').replace('T',' ')}
function notice(t){const n=$('#notice');n.textContent=t;n.classList.add('show');setTimeout(()=>n.classList.remove('show'),2600)}
function isSuper(){return currentUser?.role==='super_admin'} function isManager(){return isSuper()||currentUser?.role==='company_admin'}
function company(id){return cache.companies.find(x=>x.id===Number(id))?.name||'未设置'} function owner(id){return cache.users.find(x=>x.id===Number(id))?.displayName||'未分配'} function salesUsers(){return cache.users.filter(u=>u.role!=='super_admin'&&u.active!==false)} function salesOwnerOptions(id){return `<option value="">全部业务员</option>${salesUsers().map(u=>`<option value="${u.id}" ${Number(id)===Number(u.id)?'selected':''}>${esc(u.displayName)}</option>`).join('')}`} function customer(id){return cache.customers.find(x=>x.id===Number(id))}
function tag(s){const c=s==='新客户'?'new':s==='高意向'||s==='待分配'?'hot':s==='已成交'||s==='已转客户'?'done':'follow';return `<span class="tag ${c}">${esc(s)}</span>`}
async function init(){cache=await window.crm.bootstrap(currentUser);render()}
function nav(){document.querySelectorAll('.nav').forEach(b=>b.classList.toggle('active',b.dataset.view===currentView));const map={employees:'团队管理 / 员工列表',learning:'团队管理 / 学习资料',customers:'客户管理 / 客户列表',leads:'线索管理 / 线索池',public:'客户管理 / 公海池',logs:'跟进管理 / 跟进日志',contracts:'合同管理 / 合同列表',progress:'合同管理 / 成交客户进度',companies:'集团管理 / 分公司',audit:'集团管理 / 操作日志',users:'系统管理 / 账号与权限',info:'系统管理 / 数据说明'};$('#crumb').textContent=map[currentView]||''}
async function render(){nav();if(currentView==='employees')return renderEmployees();if(currentView==='learning')return renderLearning();if(currentView==='customers')return renderCustomers();if(currentView==='leads')return renderLeads();if(currentView==='public')return renderPublic();if(currentView==='logs')return renderLogs();if(currentView==='contracts')return renderContracts();if(currentView==='progress')return renderProgress();if(currentView==='companies')return renderCompanies();if(currentView==='audit')return renderAudit();if(currentView==='users')return renderUsers();return renderInfo()}
async function renderLearning(){const rows=await window.crm.learningList(currentUser);cache.learningMaterials=rows;const formatSize=n=>{const v=Number(n||0);return v>=1024*1024?`${(v/1024/1024).toFixed(1)} MB`:`${Math.max(1,Math.round(v/1024))} KB`};const scopeText=x=>x.scope==='group'?'集团资料':x.companyName||'分公司资料';$('#content').innerHTML=`<div class="title">学习资料</div><div class="sub">管理员上传培训资料，所有员工可下载；分公司资料仅本分公司可见，集团资料对全部员工可见。支持 PDF、DOC、DOCX、PPTX。</div><div class="card"><div class="toolbar">${isManager()?'<button class="primary" id="learningAdd">+ 上传资料</button>':''}<span class="push">共 ${rows.length} 份资料</span></div><table><thead><tr><th>资料名称</th><th>文件</th><th>可见范围</th><th>说明</th><th>上传时间</th><th>操作</th></tr></thead><tbody>${rows.map(x=>`<tr><td><b>${esc(x.title)}</b></td><td>${esc(x.originalName)}<div class="muted">${esc(x.fileType.toUpperCase())} · ${formatSize(x.fileSize)}</div></td><td>${esc(scopeText(x))}</td><td>${esc(x.note||'—')}</td><td>${esc(x.uploadedAt)}</td><td><button class="primary" data-learning-download="${x.id}">下载</button>${isManager()?` <button class="ghost" data-learning-edit="${x.id}">编辑</button> <button class="danger" data-learning-delete="${x.id}">删除</button>`:''}</td></tr>`).join('')||'<tr><td colspan="6" class="muted">暂无学习资料</td></tr>'}</tbody></table></div>`;if(isManager())$('#learningAdd').onclick=()=>learningModal();document.querySelectorAll('[data-learning-download]').forEach(b=>b.onclick=async()=>{try{const r=await window.crm.learningDownload(currentUser,+b.dataset.learningDownload);if(!r.cancelled)notice('资料已保存到：'+r.filePath)}catch(e){notice(e.message||String(e))}});document.querySelectorAll('[data-learning-edit]').forEach(b=>b.onclick=()=>learningModal(rows.find(x=>x.id===+b.dataset.learningEdit)));document.querySelectorAll('[data-learning-delete]').forEach(b=>b.onclick=async()=>{const x=rows.find(v=>v.id===+b.dataset.learningDelete);if(!x||!confirm(`确认删除学习资料“${x.title}”？\n\n资料文件和系统记录都会删除，无法恢复。`))return;try{await window.crm.learningDelete(currentUser,x.id);notice('学习资料已删除');await renderLearning()}catch(e){notice(e.message||String(e))}})}
function learningModal(row=null){const editing=!!row;let selectedFile=null;const pickFile=async()=>{try{const f=await window.crm.learningPick(currentUser);if(f){selectedFile=f;$('#learningFileName').textContent=`已选择：${f.name}`;$('#learningFileName').className='file-selected';}}catch(e){notice(e.message||String(e))}};const scopeField=isSuper()?`<div class="field"><label>可见范围 *</label><select id="learningScope"><option value="group" ${row?.scope!=='company'?'selected':''}>集团资料（全部员工可见）</option><option value="company" ${row?.scope==='company'?'selected':''}>分公司资料</option></select></div><div class="field" id="learningCompanyWrap"><label>所属分公司 *</label><select id="learningCompany">${cache.companies.map(c=>`<option value="${c.id}" ${Number(row?.companyId||currentUser.companyId||1)===Number(c.id)?'selected':''}>${esc(c.name)}</option>`).join('')}</select></div>`:`<input type="hidden" id="learningScope" value="company"><input type="hidden" id="learningCompany" value="${currentUser.companyId}">`;const body=`${editing?`<div class="field"><label>原文件</label><input value="${esc(row.originalName)}" disabled></div>`:''}<div class="field"><label>资料名称 *</label><input id="learningTitle" value="${esc(row?.title||'')}" placeholder="例如：新员工培训手册"></div>${editing?'': '<div class="field"><label>选择文件 *</label><button class="ghost" type="button" id="learningPick">选择 PDF / DOC / DOCX / PPTX 文件</button><span id="learningFileName" class="muted" style="display:block;margin-top:7px">尚未选择文件</span></div>'}${scopeField}<div class="field"><label>资料说明</label><textarea id="learningNote">${esc(row?.note||'')}</textarea></div>`;modal(editing?'编辑学习资料':'上传学习资料',body,async()=>{const payload={title:$('#learningTitle').value,note:$('#learningNote').value,scope:$('#learningScope').value,companyId:$('#learningCompany').value};if(editing)await window.crm.learningUpdate(currentUser,{...payload,id:row.id});else{if(!selectedFile)throw new Error('请先点击“选择文件”选择 PDF、DOC、DOCX 或 PPTX 文件。');await window.crm.learningAdd(currentUser,{...payload,filePath:selectedFile.path});}closeModal();notice(editing?'学习资料已修改':'学习资料已上传');await renderLearning()});if(!editing)$('#learningPick').onclick=pickFile;if(isSuper()&&!editing){const update=()=>$('#learningCompanyWrap').style.display=$('#learningScope').value==='company'?'block':'none';$('#learningScope').onchange=update;update();}}
async function renderEmployees(){if(!isManager()){currentView='customers';notice('普通员工无员工列表查看权限。');return render()}const rows=await window.crm.employeesStats(currentUser);const totalCustomers=rows.reduce((n,x)=>n+x.customerCount,0),totalDeals=rows.reduce((n,x)=>n+x.dealCount,0);$('#content').innerHTML=`<div class="title">员工列表</div><div class="sub">按员工查看所属分公司、当前有效客户数量和已成交合同数量。客户数量不包含公海客户；成交数量按已签约、履约中、已完成合同统计。</div><div class="grid" style="margin-bottom:14px"><div class="mini"><label>员工总数</label><strong style="font-size:22px">${rows.length}</strong></div><div class="mini"><label>客户总数</label><strong style="font-size:22px;color:#2f80ed">${totalCustomers}</strong></div><div class="mini"><label>成交合同总数</label><strong style="font-size:22px;color:#cf1322">${totalDeals}</strong></div></div><div class="card"><div class="toolbar"><button class="primary" id="employeeAdd">+ 新增员工</button><span class="push">共 ${rows.length} 人</span></div><table><thead><tr><th>员工姓名</th><th>登录账号</th><th>所属分公司</th><th>客户数量</th><th>成交数量</th><th>角色</th><th>状态</th><th>操作</th></tr></thead><tbody>${rows.map(x=>`<tr><td><b>${esc(x.displayName)}</b></td><td>${esc(x.username)}</td><td>${esc(x.companyName)}</td><td><b>${x.customerCount}</b> 个</td><td><b style="color:#cf1322">${x.dealCount}</b> 份合同</td><td>${esc(roles[x.role]||x.role)}</td><td>${x.active?'<span class="tag done">启用</span>':'<span class="tag hot">已停用</span>'}</td><td>${x.active?`<button class="ghost" data-employee-edit="${x.id}">编辑</button> <button class="primary" data-employee-transfer="${x.id}">调岗/离职</button>`:'<span class="muted">已完成离职</span>'}</td></tr>`).join('')||'<tr><td colspan="8" class="muted">暂无员工数据</td></tr>'}</tbody></table></div>`;$('#employeeAdd').onclick=()=>userModal();document.querySelectorAll('[data-employee-edit]').forEach(b=>b.onclick=()=>userModal(rows.find(x=>x.id===+b.dataset.employeeEdit)));document.querySelectorAll('[data-employee-transfer]').forEach(b=>b.onclick=()=>userTransferModal(rows.find(x=>x.id===+b.dataset.employeeTransfer)))}
function empty(t){return `<div class="muted" style="padding:22px">${t}</div>`}
async function renderCustomers(){const c=$('#content');c.innerHTML=`<div class="title">客户列表</div><div class="sub">展示当前账号有权限管理的私有客户；集团总管理员可跨分公司查看。</div><div class="card"><div class="toolbar"><input id="ckey" placeholder="搜索客户名称、联系人或手机号"><select id="csource"><option value="">全部来源</option><option>主动开发</option><option>抖音广告</option><option>转介绍</option></select><select id="cstatus"><option value="">全部状态</option><option>新客户</option><option>跟进中</option><option>高意向</option><option>已成交</option></select><select id="cowner">${salesOwnerOptions()}</select><button class="primary" id="cquery">查询</button><button class="ghost" id="cadd">+ 新增客户</button><span class="push" id="ctotal"></span></div><div class="split"><div class="left"><table><thead><tr><th>客户主体</th><th>所属分公司</th><th>联系方式</th><th>来源</th><th>获客时间</th><th>业务员</th><th>状态</th></tr></thead><tbody id="crows"></tbody></table></div><aside class="detail" id="cdetail"></aside></div></div>`;$('#cquery').onclick=loadCustomers;$('#cadd').onclick=customerModal;await loadCustomers()}
async function loadCustomers(){const ownerId=$('#cowner')?.value||'';const rows=await window.crm.customersList(currentUser,{keyword:$('#ckey').value.trim(),source:$('#csource').value,status:$('#cstatus').value,ownerId});const filtered=rows;cache.customers=filtered;$('#ctotal').textContent=`共 ${filtered.length} 条客户`;$('#crows').innerHTML=filtered.map(x=>`<tr data-id="${x.id}"><td><b style="color:#2f80ed">${esc(x.name)}</b><div class="muted">${esc(x.contact)}</div></td><td>${esc(company(x.companyId))}</td><td>${esc(x.mobile)}</td><td>${esc(x.source)}</td><td>${esc(x.acquiredAt)}</td><td>${esc(owner(x.ownerId))}</td><td>${tag(x.status)}</td></tr>`).join('');document.querySelectorAll('#crows tr').forEach(e=>e.onclick=()=>showCustomer(+e.dataset.id));if(filtered.length)showCustomer(filtered.some(x=>x.id===selectedCustomer)?selectedCustomer:filtered[0].id);else $('#cdetail').innerHTML=empty('暂无客户记录')}
async function showCustomer(id){selectedCustomer=id;const x=customer(id),logs=await window.crm.followupsList(currentUser,id),contracts=(await window.crm.contractsList(currentUser,{})).filter(c=>c.customerId===id);$('#cdetail').innerHTML=`<h2>${esc(x.name)}</h2><div class="muted">${esc(x.contact||'未填写联系人')} · ${esc(x.mobile||'未填写联系方式')}</div><div class="grid"><div class="mini"><label>所属分公司</label>${esc(company(x.companyId))}</div><div class="mini"><label>所属业务员</label>${esc(owner(x.ownerId))}</div><div class="mini"><label>客户来源</label>${esc(x.source)}</div><div class="mini"><label>下次跟进</label>${esc(x.nextFollowUp||'未设置')}</div></div><div style="display:flex;gap:8px;flex-wrap:wrap"><button class="primary" id="addFollow">+ 新增跟进</button><button class="ghost" id="editCustomer">编辑客户</button><button class="ghost" id="toPublic">转入公海</button><button class="danger" id="deleteCustomer">删除客户</button></div><div style="margin-top:14px;font-weight:600">跟进时间线</div><div class="timeline">${logs.map(f=>`<div class="event"><div class="event-time">${esc(f.createdAt)} · ${esc(f.method)} · ${esc(owner(f.operatorId))}</div><div class="event-desc">${esc(f.content)}</div><div class="muted">下次跟进：${esc(f.nextFollowUp||'未设置')}</div></div>`).join('')||empty('暂无跟进记录')}</div><div style="font-weight:600;margin:12px 0 6px">关联合同（${contracts.length}）</div>${contracts.map(ct=>`<div class="mini" style="margin:5px 0;cursor:pointer" data-contract="${ct.id}">${esc(ct.contractNo)} · ${esc(ct.title)}<br><span class="muted">${ct.businessModules?.map(esc).join('、')||'未设置业务模块'}　¥${Number(ct.amount).toFixed(2)}</span></div>`).join('')||empty('暂无合同')}`;$('#addFollow').onclick=()=>followModal(id);$('#editCustomer').onclick=()=>customerEditModal(x);$('#toPublic').onclick=async()=>{if(!confirm(`确认将“${x.name}”转入公海池？\n转入后当前业务员将失去私有归属，分公司内员工可认领。`))return;try{await window.crm.customersToPublic(currentUser,id);notice('已转入公海池');selectedCustomer=null;await init()}catch(e){notice(e.message)}};$('#deleteCustomer').onclick=async()=>{if(!confirm(`确认删除客户“${x.name}”？\n删除为软删除；有关联合同的客户不能删除。`))return;try{await window.crm.customersDelete(currentUser,id);notice('客户已删除');selectedCustomer=null;await init()}catch(e){notice(e.message)}};document.querySelectorAll('[data-contract]').forEach(e=>e.onclick=()=>{currentView='contracts';selectedContract=+e.dataset.contract;render()})}
function customerForm(x={}){const canSetOwner=isManager(),companies=isSuper()?`<div class="field"><label>所属分公司 *</label><select id="mCompany">${cache.companies.map(c=>`<option value="${c.id}" ${Number(x.companyId||currentUser.companyId)===c.id?'selected':''}>${esc(c.name)}</option>`).join('')}</select></div>`:'';const users=cache.users.filter(u=>u.role!=='super_admin'&&(isSuper()?true:u.companyId===currentUser.companyId));return `${companies}<div class="field"><label>客户名称 *</label><input id="mName" value="${esc(x.name||'')}"></div><div class="field"><label>联系人</label><input id="mContact" value="${esc(x.contact||'')}"></div><div class="field"><label>联系方式</label><input id="mMobile" value="${esc(x.mobile||'')}"></div><div class="field"><label>客户来源</label><select id="mSource">${['主动开发','抖音广告','转介绍'].map(v=>`<option ${x.source===v?'selected':''}>${v}</option>`).join('')}</select></div><div class="field"><label>所属业务员</label><select id="mOwner" ${canSetOwner?'':'disabled'}>${users.map(u=>`<option value="${u.id}" ${Number(x.ownerId||currentUser.id)===u.id?'selected':''}>${esc(u.displayName)}</option>`).join('')}</select></div><div class="field"><label>客户状态</label><select id="mStatus">${['新客户','跟进中','高意向','已成交'].map(v=>`<option ${x.status===v?'selected':''}>${v}</option>`).join('')}</select></div><div class="field"><label>下次跟进</label><input id="mNext" type="datetime-local" value="${toDateTimeLocal(x.nextFollowUp||'')}" placeholder="可选择或手动输入"></div>`}
function customerModal(){modal('新增客户',customerForm(),async()=>{const r=await window.crm.customersAdd(currentUser,{name:$('#mName').value,contact:$('#mContact').value,mobile:$('#mMobile').value,source:$('#mSource').value,companyId:$('#mCompany')?.value,ownerId:$('#mOwner').value,status:$('#mStatus').value,nextFollowUp:fromDateTimeLocal($('#mNext').value)});closeModal();notice('客户已保存');selectedCustomer=r.id;await init()})}
function customerEditModal(x){modal('编辑客户',customerForm(x),async()=>{const r=await window.crm.customersUpdate(currentUser,{id:x.id,name:$('#mName').value,contact:$('#mContact').value,mobile:$('#mMobile').value,source:$('#mSource').value,ownerId:$('#mOwner').value,status:$('#mStatus').value,nextFollowUp:fromDateTimeLocal($('#mNext').value)});closeModal();notice('客户信息已更新');selectedCustomer=r.id;await init()})}
function followModal(customerId){modal('新增跟进记录',`<div class="field"><label>跟进方式</label><select id="fMethod"><option>电话</option><option>微信</option><option>面谈</option><option>短信</option><option>其他</option></select></div><div class="field"><label>跟进内容 *</label><textarea id="fContent"></textarea></div><div class="field"><label>下次跟进</label><input id="fNext" type="datetime-local" placeholder="可选择或手动输入"></div>`,async()=>{await window.crm.followupsAdd(currentUser,{customerId,method:$('#fMethod').value,content:$('#fContent').value,nextFollowUp:fromDateTimeLocal($('#fNext').value)});closeModal();notice('跟进记录已保存');await init()})}
let leadPage=1;const LEAD_PAGE_SIZE=20;
async function renderLeads(page=leadPage){const result=await window.crm.leadsList(currentUser,{page,pageSize:LEAD_PAGE_SIZE});const rows=result.leads||[];const pagination=result.pagination||{page:1,pageSize:LEAD_PAGE_SIZE,total:rows.length,totalPages:1};leadPage=pagination.page;cache.leads=rows;const c=$('#content'),manageable=isManager();const pager=pagination.totalPages>1?`<div class="toolbar" style="justify-content:flex-end;margin-top:12px"><span class="muted">第 ${pagination.page} / ${pagination.totalPages} 页</span><button class="ghost" id="leadPrev" ${pagination.page<=1?'disabled':''}>上一页</button><button class="ghost" id="leadNext" ${pagination.page>=pagination.totalPages?'disabled':''}>下一页</button></div>`:'';c.innerHTML=`<div class="title">线索池</div><div class="sub">公共线索池只显示本分公司待分配线索。员工领取或管理员分配后，会自动转入对应业务员的客户列表。</div><div class="card"><div class="toolbar"><button class="primary" id="ladd">+ 新增线索</button><button class="ghost" id="lrefresh">刷新线索池</button><button class="ghost" id="limport" ${manageable?'':'disabled'}>导入 Excel</button>${manageable?'<button class="danger" id="lbatchDelete" disabled>批量删除（0）</button>':''}<span class="push">待分配共 ${pagination.total} 条 · 每页 ${pagination.pageSize} 条</span></div><table><thead><tr>${manageable?'<th><input type="checkbox" id="lcheckAll" title="全选本页"></th>':''}<th>企业名称</th><th>法定代表人</th><th>手机</th><th>分公司</th><th>来源</th><th>状态</th><th>获客时间</th><th>操作</th></tr></thead><tbody>${rows.map(x=>`<tr>${manageable?`<td><input type="checkbox" class="lead-check" value="${x.id}" aria-label="选择 ${esc(x.enterpriseName||x.name)}"></td>`:''}<td><b>${esc(x.enterpriseName||x.name)}</b><div class="muted">${esc(x.industry||'')}</div></td><td>${esc(x.legalRepresentative||x.contact)}</td><td>${esc(x.mobile)}</td><td>${esc(company(x.companyId))}</td><td>${esc(x.source)}</td><td>${tag(x.status)}</td><td>${esc(x.acquiredAt)}</td><td>${manageable?`<button class="ghost" data-assign="${x.id}">分配</button> `:''}${!isSuper()?`<button class="primary" data-claim="${x.id}">领取</button>`:''}</td></tr>`).join('')||`<tr><td colspan="${manageable?9:8}">暂无待分配线索</td></tr>`}</tbody></table>${pager}</div>`;$('#ladd').onclick=leadModal;$('#lrefresh').onclick=async()=>{const b=$('#lrefresh');b.disabled=true;b.textContent='刷新中...';try{await renderLeads(pagination.page);notice('线索池已刷新')}catch(e){notice(e.message||String(e))}finally{if(document.body.contains(b)){b.disabled=false;b.textContent='刷新线索池'}}};$('#limport').onclick=async()=>{try{const r=await window.crm.leadsImport(currentUser);if(!r.cancelled){const file=r.errorFilePath?`；失败明细已保存到桌面：${r.errorFilePath}`:'';notice(`导入完成：成功 ${r.imported} 条；重复 ${r.duplicated} 条；失败 ${r.failed} 条${file}`);leadPage=1;await init()}}catch(e){notice(e.message||String(e))}};if($('#leadPrev'))$('#leadPrev').onclick=()=>renderLeads(pagination.page-1);if($('#leadNext'))$('#leadNext').onclick=()=>renderLeads(pagination.page+1);document.querySelectorAll('[data-assign]').forEach(b=>b.onclick=()=>leadAssign(rows.find(x=>x.id===+b.dataset.assign)));document.querySelectorAll('[data-claim]').forEach(b=>b.onclick=async()=>{try{const customer=await window.crm.leadsClaim(currentUser,+b.dataset.claim);notice(`已领取并转入客户列表：${customer.name}`);selectedCustomer=customer.id;currentView='customers';await init()}catch(e){notice(e.message||String(e))}});if(manageable){const checks=()=>[...document.querySelectorAll('.lead-check:checked')].map(x=>+x.value),sync=()=>{$('#lbatchDelete').disabled=!checks().length;$('#lbatchDelete').textContent=`批量删除（${checks().length}）`;$('#lcheckAll').checked=document.querySelectorAll('.lead-check').length>0&&checks().length===document.querySelectorAll('.lead-check').length;};document.querySelectorAll('.lead-check').forEach(x=>x.onchange=sync);$('#lcheckAll').onchange=e=>{document.querySelectorAll('.lead-check').forEach(x=>x.checked=e.target.checked);sync()};$('#lbatchDelete').onclick=async()=>{const ids=checks();if(!ids.length||!confirm(`确认删除选中的 ${ids.length} 条待分配线索吗？

删除后将不再在线索池显示，但保留操作审计记录。`))return;try{const r=await window.crm.leadsDeleteBatch(currentUser,ids);notice(`已删除 ${r.deleted} 条${r.ignored?`，忽略 ${r.ignored} 条无权或已删除记录`:''}`);if(rows.length===ids.length&&pagination.page>1)leadPage=pagination.page-1;await init()}catch(e){notice(e.message||String(e))}};}}function leadModal(){const comp=isSuper()?`<div class="field"><label>所属分公司 *</label><select id="lCompany">${cache.companies.map(c=>`<option value="${c.id}">${esc(c.name)}</option>`).join('')}</select></div>`:'';modal('新增线索',`${comp}<div class="field"><label>企业名称 *</label><input id="lEnterprise"></div><div class="field"><label>法定代表人</label><input id="lLegal"></div><div class="field"><label>手机</label><input id="lMobile"></div><div class="field"><label>线索来源</label><select id="lSource"><option>抖音广告</option><option>主动开发</option><option>转介绍</option></select></div><div class="field"><label>备注</label><textarea id="lNote"></textarea></div>`,async()=>{await window.crm.leadsAdd(currentUser,{companyId:$('#lCompany')?.value,enterpriseName:$('#lEnterprise').value,legalRepresentative:$('#lLegal').value,mobile:$('#lMobile').value,source:$('#lSource').value,note:$('#lNote').value});closeModal();notice('线索已进入线索池');await init()})}
function leadAssign(x){const users=cache.users.filter(u=>u.companyId===x.companyId&&u.role==='employee');modal('分配线索',`<div class="field"><label>线索</label><input value="${esc(x.name||x.enterpriseName)}" disabled></div><div class="field"><label>分配给 *</label><select id="lOwner">${users.map(u=>`<option value="${u.id}">${esc(u.displayName)}</option>`).join('')}</select></div><div class="sub">确认分配后，该线索会立即从公共线索池移除，并自动进入该业务员的客户列表。</div>`,async()=>{const customer=await window.crm.leadsAssign(currentUser,{id:x.id,ownerId:$('#lOwner').value});closeModal();notice(`线索已分配并转入客户列表：${customer.name}`);await init()})}
async function renderPublic(){const rows=await window.crm.publicList(currentUser);const c=$('#content');c.innerHTML=`<div class="title">公海池</div><div class="sub">无合同客户连续 7 天未填写跟进记录会自动转入本分公司公海池；原负责人不可重新认领，分公司之间数据隔离。</div><div class="card"><table><thead><tr><th>客户主体</th><th>所属分公司</th><th>联系人</th><th>来源</th><th>状态</th><th>转入原因</th><th>转入时间</th><th>操作</th></tr></thead><tbody>${rows.map(x=>`<tr><td><b>${esc(x.name)}</b></td><td>${esc(company(x.companyId))}</td><td>${esc(x.contact)} ${esc(x.mobile)}</td><td>${esc(x.source)}</td><td>${tag(x.status)}</td><td>${esc(x.publicReason||'手动转入')}</td><td>${esc(x.publicAt||'')}</td><td>${Number(x.blockedClaimUserId)===Number(currentUser.id)?'<span class="muted">原负责人不可认领</span>':`<button class="primary" data-claim="${x.id}">认领客户</button>`}</td></tr>`).join('')||`<tr><td colspan="8">公海池暂无客户</td></tr>`}</tbody></table></div>`;document.querySelectorAll('[data-claim]').forEach(b=>b.onclick=async()=>{try{await window.crm.publicClaim(currentUser,+b.dataset.claim);notice('客户已认领到本人名下');currentView='customers';await init()}catch(e){notice(e.message)}})}
async function renderLogs(){const all=[];for(const x of await window.crm.customersList(currentUser,{})){const logs=await window.crm.followupsList(currentUser,x.id);logs.forEach(f=>all.push({...f,customerName:x.name,companyId:x.companyId}))}all.sort((a,b)=>b.id-a.id);const selectedOwner=$('#logOwner')?.value||'';const filtered=selectedOwner?all.filter(f=>Number(f.operatorId)===Number(selectedOwner)):all;$('#content').innerHTML=`<div class="title">跟进日志</div><div class="sub">跟进日志采用追加式记录，不覆盖历史内容。</div><div class="card"><div class="toolbar"><select id="logOwner">${salesOwnerOptions(selectedOwner)}</select><button class="primary" id="logQuery">查询</button><span class="push">共 ${filtered.length} 条记录</span></div><table><thead><tr><th>跟进时间</th><th>客户</th><th>分公司</th><th>业务员</th><th>方式</th><th>跟进内容</th><th>下次跟进</th></tr></thead><tbody>${filtered.map(f=>`<tr><td>${esc(f.createdAt)}</td><td>${esc(f.customerName)}</td><td>${esc(company(f.companyId))}</td><td>${esc(owner(f.operatorId))}</td><td>${esc(f.method)}</td><td>${esc(f.content)}</td><td>${esc(f.nextFollowUp||'未设置')}</td></tr>`).join('')||`<tr><td colspan="7">暂无跟进日志</td></tr>`}</tbody></table></div>`;$('#logQuery').onclick=()=>renderLogs()}
async function renderContracts(){const c=$('#content');c.innerHTML=`<div class="title">合同管理</div><div class="sub">合同按“业务服务明细”录入：每项服务独立选择模板、填写金额；成交后的完成情况统一在“成交客户进度”维护。</div><div class="card"><div class="toolbar"><input id="tkey" placeholder="搜索合同编号或名称"><select id="tstatus"><option value="">全部状态</option><option>草稿</option><option>待签约</option><option>履约中</option><option>已完成</option><option>已到期</option><option>已终止</option></select><button class="primary" id="tquery">查询</button><button class="ghost" id="tadd">+ 新建合同</button><span class="push" id="ttotal"></span></div><div class="split"><div class="left"><table><thead><tr><th>合同编号</th><th>合同名称</th><th>分公司</th><th>客户主体</th><th>业务服务</th><th>总金额</th><th>状态</th></tr></thead><tbody id="trows"></tbody></table></div><aside class="detail" id="tdetail"></aside></div></div>`;$('#tquery').onclick=loadContracts;$('#tadd').onclick=contractModal;await loadContracts()}
async function loadContracts(){const rows=await window.crm.contractsList(currentUser,{keyword:$('#tkey').value.trim(),status:$('#tstatus').value});cache.contracts=rows;$('#ttotal').textContent=`共 ${rows.length} 份合同`;$('#trows').innerHTML=rows.map(x=>`<tr data-id="${x.id}"><td>${esc(x.contractNo)}</td><td><b style="color:#2f80ed">${esc(x.title)}</b></td><td>${esc(company(x.companyId))}</td><td>${esc(customer(x.customerId)?.name||'')}</td><td>${esc(serviceSummary(x.services||[]))}</td><td>¥${Number(x.amount).toFixed(2)}</td><td>${tag(x.status)}</td></tr>`).join('');document.querySelectorAll('#trows tr').forEach(e=>e.onclick=()=>showContract(+e.dataset.id));if(rows.length)showContract(rows.some(x=>x.id===selectedContract)?selectedContract:rows[0].id);else $('#tdetail').innerHTML=empty('暂无合同记录')}
async function showContract(id){selectedContract=id;const ct=cache.contracts.find(x=>x.id===id),files=await window.crm.attachmentsList(currentUser,id);cache.attachments=files;$('#tdetail').innerHTML=`<h2>${esc(ct.title)}</h2><div class="muted">${esc(ct.contractNo)} · ${esc(customer(ct.customerId)?.name||'')}</div><div class="grid"><div class="mini"><label>所属分公司</label>${esc(company(ct.companyId))}</div><div class="mini"><label>负责人</label>${esc(owner(ct.ownerId))}</div><div class="mini"><label>合同总金额</label>¥${Number(ct.amount).toFixed(2)}</div><div class="mini"><label>合同状态</label>${tag(ct.status)}</div><div class="mini"><label>服务周期</label>${esc(ct.serviceStart||'未填写')} 至 ${esc(ct.serviceEnd||'未填写')}</div></div><div style="display:flex;gap:8px;flex-wrap:wrap"><button class="ghost" id="editContract">修改合同</button><button class="danger" id="deleteContract">删除合同</button><button class="ghost" id="goProgress">维护成交进度</button><button class="primary" id="upload">+ 上传合同附件</button></div><div style="font-weight:600;margin:14px 0 6px">业务服务明细</div>${(ct.services||[]).map(s=>`<div class="mini" style="margin:6px 0"><b>${esc(s.type)}</b>　¥${Number(s.amount||0).toFixed(2)}<br><span class="muted">${esc(progressText(s))}${s.remark?` · ${esc(s.remark)}`:''}</span></div>`).join('')||empty('暂无业务服务')}<div style="font-weight:600;margin:14px 0 6px">合同附件（PDF / 图片）</div><ul class="files">${files.map(a=>`<li data-file="${a.id}">${esc(a.originalName)}<br><span class="muted">${esc(a.uploadedAt)}</span></li>`).join('')||'<li class="muted">暂无附件</li>'}</ul>`;$('#editContract').onclick=()=>contractEditModal(ct);$('#deleteContract').onclick=async()=>{if(!confirm(`确认删除合同“${ct.title}”吗？\n\n此操作会同时删除该合同的 ${files.length} 个附件记录及本地附件文件，无法恢复。`))return;try{const r=await window.crm.contractsDelete(currentUser,id);notice(`合同已删除，同时移除 ${r.removedAttachments} 个附件`);selectedContract=null;await init();currentView='contracts';render()}catch(e){notice(e.message||String(e))}};$('#goProgress').onclick=()=>{currentView='progress';selectedContract=id;render()};$('#upload').onclick=async()=>{try{const a=await window.crm.attachmentsAdd(currentUser,id);if(a){notice('附件已保存');showContract(id)}}catch(e){notice(e.message)}};document.querySelectorAll('[data-file]').forEach(el=>el.onclick=async()=>{try{await window.crm.attachmentsOpen(currentUser,+el.dataset.file)}catch(e){notice(e.message)}})}
function serviceRow(i,s={type:'短视频拍摄',amount:0,total:0,completed:0,accountStatus:'未开户',durationValue:1,durationUnit:'年',startDate:'',endDate:'',remark:''},allowRemove=true){const isAccount=s.type==='巨量本地推投流',isDuration=['巨量本地推投流','GEO业务'].includes(s.type),isRemark=s.type==='抖音陪跑业务';const numeric=!isAccount&&!isDuration&&!isRemark;const amount=Number(s.amount||0)||(s.type==='GEO业务'?12800:0);return `<div class="mini service-row" data-service-row="${i}" style="margin:8px 0">${allowRemove?`<button class="danger" type="button" data-remove-service="${i}" style="float:right;padding:3px 8px">移除</button>`:''}<div class="field"><label>业务类型 *</label><select data-s="type">${serviceTypes.map(t=>`<option ${s.type===t?'selected':''}>${t}</option>`).join('')}</select></div><div class="field"><label>该业务金额（元） *</label><input data-s="amount" value="${amount}"></div>${numeric?`<div class="field"><label>总${s.type==='宣传片拍摄'?'时长（分钟）':s.type==='抖音直播业务'?'场次':s.type==='抖音达人探店业务'?'人数':'条数'} / 已完成</label><input data-s="total" value="${Number(s.total||0)}" style="width:49%"><input data-s="completed" value="${Number(s.completed||0)}" style="width:49%;float:right"></div>`:''}${isAccount?`<div class="field"><label>开户状态</label><select data-s="accountStatus"><option ${s.accountStatus==='未开户'?'selected':''}>未开户</option><option ${s.accountStatus==='已开户'?'selected':''}>已开户</option></select></div>`:''}${isDuration?`<div class="field"><label>合作时长 / 开始结束</label><input data-s="durationValue" value="${Number(s.durationValue||1)}" style="width:24%"><select data-s="durationUnit" style="width:24%"><option ${s.durationUnit==='年'?'selected':''}>年</option><option ${s.durationUnit==='月'?'selected':''}>月</option></select><input data-s="startDate" type="date" value="${esc(s.startDate||'')}" style="width:25%"><input data-s="endDate" type="date" value="${esc(s.endDate||'')}" style="width:25%"></div>`:''}${isRemark?`<div class="field"><label>陪跑内容备注 / 是否完成</label><textarea data-s="remark">${esc(s.remark||'')}</textarea><select data-s="completed"><option value="0" ${Number(s.completed||0)===0?'selected':''}>未完成</option><option value="1" ${Number(s.completed||0)===1?'selected':''}>已完成</option></select></div>`:''}</div>`}
function readServices(){return [...document.querySelectorAll('[data-service-row]')].map((row,i)=>{const get=k=>row.querySelector(`[data-s="${k}"]`)?.value||'';return {type:get('type'),amount:get('amount'),total:get('total'),completed:get('completed'),accountStatus:get('accountStatus'),durationValue:get('durationValue'),durationUnit:get('durationUnit'),startDate:get('startDate'),endDate:get('endDate'),remark:get('remark')}})}
async function contractModal(){const customers=await window.crm.customersList(currentUser,{});if(!customers.length)return notice('请先新增客户后再建立合同。');modal('新建合同',`<div class="field"><label>合同名称 *</label><input id="ctTitle"></div><div class="field"><label>关联客户 *</label><select id="ctCustomer">${customers.map(x=>`<option value="${x.id}">${esc(x.name)}（${esc(company(x.companyId))}）</option>`).join('')}</select></div><div style="font-weight:600;margin:12px 0">业务服务明细 *</div><div id="serviceRows">${serviceRow(0)}</div><button class="ghost" type="button" id="addService">+ 添加业务服务</button><div class="field"><label>签约日期</label><input id="ctSigned" type="date"></div><div class="field"><label>服务开始 / 结束</label><input id="ctStart" type="date" style="width:49%"><input id="ctEnd" type="date" style="width:49%;float:right"></div><div class="field"><label>合同状态</label><select id="ctStatus"><option>草稿</option><option>待签约</option><option>履约中</option><option>已完成</option><option>已到期</option><option>已终止</option></select></div><div class="field"><label>备注</label><textarea id="ctNote"></textarea></div>`,async()=>{const r=await window.crm.contractsAdd(currentUser,{title:$('#ctTitle').value,customerId:$('#ctCustomer').value,services:readServices(),signedAt:$('#ctSigned').value,serviceStart:$('#ctStart').value,serviceEnd:$('#ctEnd').value,status:$('#ctStatus').value,note:$('#ctNote').value});closeModal();notice('合同已保存');selectedContract=r.id;await init();currentView='contracts';render()});$('#addService').onclick=()=>{const n=document.querySelectorAll('[data-service-row]').length;$('#serviceRows').insertAdjacentHTML('beforeend',serviceRow(n));bindServiceRemove()};bindServiceRemove()}
function bindServiceRemove(){document.querySelectorAll('[data-remove-service]').forEach(b=>b.onclick=()=>{const rows=document.querySelectorAll('[data-service-row]');if(rows.length<=1)return notice('合同至少保留一项业务服务。');b.closest('[data-service-row]').remove()})}
async function contractEditModal(ct){const customers=await window.crm.customersList(currentUser,{});modal('修改合同',`<div class="field"><label>合同名称 *</label><input id="ctTitle" value="${esc(ct.title)}"></div><div class="field"><label>关联客户 *</label><select id="ctCustomer">${customers.map(x=>`<option value="${x.id}" ${x.id===ct.customerId?'selected':''}>${esc(x.name)}（${esc(company(x.companyId))}）</option>`).join('')}</select></div><div style="font-weight:600;margin:12px 0">业务服务明细 *</div><div id="serviceRows">${(ct.services||[]).map((s,i)=>serviceRow(i,s)).join('')||serviceRow(0)}</div><button class="ghost" type="button" id="addService">+ 添加业务服务</button><div class="field"><label>签约日期</label><input id="ctSigned" type="date" value="${esc(ct.signedAt||'')}"></div><div class="field"><label>服务开始 / 结束</label><input id="ctStart" type="date" value="${esc(ct.serviceStart||'')}" style="width:49%"><input id="ctEnd" type="date" value="${esc(ct.serviceEnd||'')}" style="width:49%;float:right"></div><div class="field"><label>合同状态</label><select id="ctStatus">${['草稿','待签约','履约中','已完成','已到期','已终止'].map(x=>`<option ${ct.status===x?'selected':''}>${x}</option>`).join('')}</select></div><div class="field"><label>备注</label><textarea id="ctNote">${esc(ct.note||'')}</textarea></div>`,async()=>{try{await window.crm.contractsUpdate(currentUser,{id:ct.id,title:$('#ctTitle').value,customerId:$('#ctCustomer').value,services:readServices(),signedAt:$('#ctSigned').value,serviceStart:$('#ctStart').value,serviceEnd:$('#ctEnd').value,status:$('#ctStatus').value,note:$('#ctNote').value});closeModal();notice('合同已修改');selectedContract=ct.id;await init();currentView='contracts';render()}catch(e){notice(e.message||String(e))}});$('#addService').onclick=()=>{const n=document.querySelectorAll('[data-service-row]').length;$('#serviceRows').insertAdjacentHTML('beforeend',serviceRow(n));bindServiceRemove()};bindServiceRemove()}
async function renderProgress(){const rows=await window.crm.contractsList(currentUser,{});cache.contracts=rows;const ct=rows.find(x=>x.id===selectedContract)||rows[0];if(!ct){$('#content').innerHTML=`<div class="title">成交客户进度</div>${empty('暂无合同，请先新建合同。')}`;return;}const services=ct.services||[];const measured=services.map(progressMeta);const total=measured.reduce((n,x)=>n+x.total,0),done=measured.reduce((n,x)=>n+x.done,0),overall=total?Math.round(done/total*100):0;$('#content').innerHTML=`<div class="title">成交客户进度</div><div class="sub">横向查看每项交付内容的目标、完成数量、待完成数量和完成率；点击“编辑进度”可更新明细。</div><div class="card progress-card"><div class="progress-head"><div class="field"><label>选择合同</label><select id="progressContract">${rows.map(x=>`<option value="${x.id}" ${x.id===ct.id?'selected':''}>${esc(x.contractNo)} · ${esc(x.title)} · ${esc(customer(x.customerId)?.name||'')}</option>`).join('')}</select></div><div class="overall-progress"><span>整体交付进度</span>${progressBar(overall)}<em>${done} / ${total||0} 项完成</em></div><button class="primary" id="editProgress">编辑进度</button></div><div class="progress-scroll"><table class="progress-table"><thead><tr><th>交付内容</th><th>目标 / 服务周期</th><th>已完成</th><th>待完成</th><th class="progress-column">进度</th><th>当前状态</th><th>业务金额</th><th>备注</th></tr></thead><tbody>${services.map((s,i)=>{const m=measured[i];return `<tr><td><b>${esc(s.type)}</b></td><td>${esc(m.target)}${m.extra?`<div class="muted">${esc(m.extra)}</div>`:''}</td><td><b class="progress-done">${m.done}</b></td><td><b class="progress-left">${m.left}</b></td><td>${progressBar(m.percent)}</td><td><span class="progress-status ${m.percent===100?'complete':m.percent>0?'active':'pending'}">${esc(m.status)}</span></td><td>¥${Number(s.amount||0).toFixed(2)}</td><td class="progress-note">${esc(s.remark||'—')}</td></tr>`}).join('')||`<tr><td colspan="8">暂无服务明细，请点击“编辑进度”添加。</td></tr>`}</tbody></table></div></div>`;$('#progressContract').onchange=()=>{selectedContract=+$('#progressContract').value;renderProgress()};$('#editProgress').onclick=()=>progressEditModal(ct)}
function progressEditModal(ct){modal('编辑成交客户进度',`<div class="sub">维护每项业务的总量与已完成量，保存后会自动更新横向进度条。</div><div id="progressRows">${(ct.services||[]).map((s,i)=>serviceRow(i,s)).join('')||serviceRow(0)}</div><button class="ghost" type="button" id="addProgressService">+ 添加业务服务</button>`,async()=>{try{await window.crm.contractsUpdateServices(currentUser,{id:ct.id,services:readServices()});closeModal();notice('成交客户进度已保存');await init();currentView='progress';render()}catch(e){notice(e.message||String(e))}});$('#addProgressService').onclick=()=>{const n=document.querySelectorAll('[data-service-row]').length;$('#progressRows').insertAdjacentHTML('beforeend',serviceRow(n));bindServiceRemove()};bindServiceRemove()}
async function renderAudit(){if(!isSuper()){currentView='customers';notice('仅集团总管理员可查看操作日志。');return render()}const rows=await window.crm.auditList(currentUser);$('#content').innerHTML=`<div class="title">操作日志</div><div class="sub">仅集团总管理员可查看。记录账号、客户、线索、合同、分公司等关键操作，按最新时间排序。</div><div class="card"><table><thead><tr><th>操作时间</th><th>操作账号</th><th>操作类型</th><th>操作详情</th></tr></thead><tbody>${rows.map(x=>`<tr><td>${esc(x.createdAt)}</td><td>${esc(x.operator)}</td><td><b>${esc(x.action)}</b></td><td>${esc(x.detail)}</td></tr>`).join('')||`<tr><td colspan="4">暂无操作日志</td></tr>`}</tbody></table></div>`}
async function renderCompanies(){if(!isSuper()){currentView='customers';notice('仅集团总管理员可管理分公司。');return render()}const rows=await window.crm.companiesList(currentUser);cache.companies=rows;$('#content').innerHTML=`<div class="title">分公司管理</div><div class="sub">集团总管理员统一维护分公司。分公司数据、客户、公海池、线索池按公司隔离。</div><div class="card"><div class="toolbar"><button class="primary" id="coadd">+ 新增分公司</button></div><table><thead><tr><th>分公司名称</th><th>状态</th><th>创建时间</th><th>操作</th></tr></thead><tbody>${rows.map(x=>`<tr><td><b>${esc(x.name)}</b></td><td><span class="tag done">启用</span></td><td>${esc(x.createdAt||'')}</td><td><button class="ghost" data-company-edit="${x.id}">编辑</button> <button class="danger" data-company-delete="${x.id}">删除</button></td></tr>`).join('')||'<tr><td colspan="4" class="muted">暂无分公司</td></tr>'}</tbody></table></div>`;$('#coadd').onclick=()=>companyModal();document.querySelectorAll('[data-company-edit]').forEach(b=>b.onclick=()=>companyModal(rows.find(x=>x.id===+b.dataset.companyEdit)));document.querySelectorAll('[data-company-delete]').forEach(b=>b.onclick=async()=>{const companyRow=rows.find(x=>x.id===+b.dataset.companyDelete);if(!companyRow||!confirm(`确认删除分公司“${companyRow.name}”？\n\n仅在该分公司没有账号、客户、线索和合同数据时才允许删除。`))return;try{await window.crm.companiesDelete(currentUser,companyRow.id);notice('分公司已删除');await init()}catch(e){notice(e.message||String(e))}})}
function companyModal(row=null){const editing=!!row;modal(editing?'编辑分公司':'新增分公司',`<div class="field"><label>分公司名称 *</label><input id="coName" value="${esc(row?.name||'')}" placeholder="例如：上海分公司"></div>`,async()=>{if(editing){await window.crm.companiesUpdate(currentUser,{id:row.id,name:$('#coName').value});notice('分公司已修改')}else{await window.crm.companiesAdd(currentUser,{name:$('#coName').value});notice('分公司已创建')}closeModal();await init();currentView='companies';render()})}
async function renderUsers(){if(!isManager()){currentView='customers';notice('普通员工无账号管理权限。');return render()}const users=await window.crm.usersList(currentUser);const c=$('#content');c.innerHTML=`<div class="title">账号与权限</div><div class="sub">集团总管理员可管理全部分公司账号；分公司管理员仅可管理本分公司普通员工。</div><div class="card"><div class="toolbar"><button class="primary" id="uadd">+ 新增员工账号</button><span class="push">共 ${users.length} 个账号</span></div><table><thead><tr><th>姓名</th><th>账号</th><th>所属分公司</th><th>角色</th><th>状态</th><th>操作</th></tr></thead><tbody>${users.map(u=>`<tr><td><b>${esc(u.displayName)}</b></td><td>${esc(u.username)}</td><td>${esc(company(u.companyId))}</td><td>${esc(roles[u.role]||u.role)}</td><td>${u.active?'<span class="tag done">启用</span>':'<span class="tag hot">已停用</span>'}</td><td>${u.role==='super_admin'||(!isSuper()&&u.id===currentUser.id)?'<span class="muted">当前账号</span>':`<button class="ghost" data-user-edit="${u.id}">编辑</button> <button class="ghost" data-user-pwd="${u.id}">重置密码</button> <button class="primary" data-user-transfer="${u.id}">调岗/离职</button>`}</td></tr>`).join('')}</tbody></table></div>`;$('#uadd').onclick=()=>userModal();document.querySelectorAll('[data-user-edit]').forEach(b=>b.onclick=()=>userModal(users.find(x=>x.id===+b.dataset.userEdit)));document.querySelectorAll('[data-user-pwd]').forEach(b=>b.onclick=()=>passwordModal(users.find(x=>x.id===+b.dataset.userPwd)));document.querySelectorAll('[data-user-transfer]').forEach(b=>b.onclick=()=>userTransferModal(users.find(x=>x.id===+b.dataset.userTransfer)))}
function userTransferModal(u){const companyOptions=cache.companies.map(c=>`<option value="${c.id}" ${Number(c.id)===Number(u.companyId)?'selected':''}>${esc(c.name)}</option>`).join('');const sameCompanyUsers=()=>cache.users.filter(x=>x.active&&x.role!=='super_admin'&&Number(x.id)!==Number(u.id)&&Number(x.companyId)===Number($('#transferCompany')?.value||u.companyId));modal('员工调岗 / 离职',`<div class="field"><label>员工</label><input value="${esc(u.displayName)}（${esc(company(u.companyId))}）" disabled></div><div class="field"><label>处理方式 *</label><select id="transferMode"><option value="transfer">转交给其他员工</option><option value="public">离职：客户/线索转公海，合同转交员工</option></select></div><div class="field"><label>目标分公司 *</label><select id="transferCompany" ${u.role==='employee'?'':'disabled'}>${companyOptions}</select></div><div class="field"><label>目标员工 *</label><select id="transferUser"></select></div><div class="sub" id="transferHint">客户、线索、合同会转给目标员工，跟进记录和合同附件保留。</div>`,async()=>{const r=await window.crm.usersTransfer(currentUser,{id:u.id,mode:$('#transferMode').value,toCompanyId:$('#transferCompany').value,toUserId:$('#transferUser').value});closeModal();notice(`处理完成：客户 ${r.customerCount} 个、线索 ${r.leadCount} 条、合同 ${r.contractCount} 份已交接`);await init();currentView='users';render()});const refresh=()=>{$('#transferUser').innerHTML=sameCompanyUsers().map(x=>`<option value="${x.id}">${esc(x.displayName)}</option>`).join('')||'<option value="">暂无可接收员工</option>';$('#transferHint').textContent=$('#transferMode').value==='public'?'客户将全部进入公海，线索变为待分配；合同仍需转交给目标员工，跟进记录和合同附件保留。':'客户、线索、合同会转给目标员工，跟进记录和合同附件保留。'};$('#transferCompany').onchange=refresh;$('#transferMode').onchange=refresh;refresh()}
function userModal(u=null){const edit=!!u,companyField=isSuper()?`<div class="field"><label>所属分公司</label><select id="uCompany">${cache.companies.map(c=>`<option value="${c.id}" ${Number(u?.companyId||1)===c.id?'selected':''}>${esc(c.name)}</option>`).join('')}</select></div>`:'';modal(edit?'编辑账号':'新增员工账号',`<div class="field"><label>员工姓名 *</label><input id="uName" value="${esc(u?.displayName||'')}"></div><div class="field"><label>登录账号 *</label><input id="uLogin" value="${esc(u?.username||'')}" ${edit?'disabled':''} placeholder="例如 sales02"></div>${edit?'':`<div class="field"><label>初始密码 *</label><input id="uPassword" type="password" placeholder="至少 6 位"></div>`}${companyField}<div class="field"><label>账号角色</label><select id="uRole"><option value="employee" ${u?.role!=='company_admin'?'selected':''}>普通员工</option>${isSuper()?`<option value="company_admin" ${u?.role==='company_admin'?'selected':''}>分公司管理员</option>`:''}</select></div>${edit?`<div class="field"><label>账号状态</label><select id="uActive"><option value="true" ${u.active?'selected':''}>启用</option><option value="false" ${!u.active?'selected':''}>停用</option></select></div>`:''}`,async()=>{if(edit){await window.crm.usersUpdate(currentUser,{id:u.id,displayName:$('#uName').value,companyId:$('#uCompany')?.value,role:$('#uRole').value,active:$('#uActive').value==='true'});notice('账号已更新')}else{await window.crm.usersAdd(currentUser,{username:$('#uLogin').value,displayName:$('#uName').value,password:$('#uPassword').value,companyId:$('#uCompany')?.value,role:$('#uRole').value});notice('账号已创建')}closeModal();await init();currentView='users';render()})}
function passwordModal(u){modal('重置密码',`<div class="field"><label>账号</label><input value="${esc(u.username)}" disabled></div><div class="field"><label>新密码 *</label><input id="newPassword" type="password" placeholder="至少 6 位"></div>`,async()=>{await window.crm.usersResetPassword(currentUser,{id:u.id,password:$('#newPassword').value});closeModal();notice('密码已重置')})}
async function renderInfo(){const i=await window.crm.info();$('#content').innerHTML=`<div class="title">系统说明</div><div class="sub">云端协同版：全部数据保存在公司服务器，所有员工看到的是同一份实时数据。</div><div class="card" style="padding:22px;line-height:1.9"><b>集团多分公司权限</b><ol><li>集团总管理员：查看全部分公司数据，管理分公司与分公司管理员。</li><li>分公司管理员：仅查看并管理本分公司客户、线索、公海、合同和普通员工。</li><li>普通员工：仅查看本人负责的私有客户、跟进日志和合同；可认领本分公司公海客户。</li><li>公海池和线索池均按分公司隔离，不能跨公司认领或查看。</li><li>合同附件、学习资料统一存放在服务器，换电脑登录同一账号即可看到。</li></ol><b>当前连接的服务器</b><br>${esc(i.serverUrl)}<br><br><b>客户端版本</b><br>v${esc(i.version)}（服务器地址可在登录页修改）<br><br><b>本机配置文件</b><br>${esc(i.configPath)}<br><br><b>附件临时缓存目录</b><br>${esc(i.cachePath)}</div>`}
function focusFirstInput(){const first=$('#modalRoot input:not([disabled]):not([readonly]), #modalRoot textarea:not([disabled]):not([readonly]), #modalRoot select:not([disabled])');if(first){first.tabIndex=0;first.focus({preventScroll:true});}}
function modal(title,body,onSave){const root=$('#modalRoot');let saving=false;root.innerHTML=`<div class="modal-bg" role="dialog" aria-modal="true" aria-label="${esc(title)}" tabindex="-1"><div class="modal" role="document"><h3>${esc(title)}</h3><div class="modal-body">${body}</div><div class="modal-actions"><button class="ghost" type="button" id="cancel">取消</button><button class="primary" type="button" id="save">保存</button></div></div></div>`;document.body.classList.add('modal-open');const overlay=root.firstElementChild,save=$('#save');$('#cancel').onclick=closeModal;overlay.addEventListener('click',e=>{if(e.target===overlay)closeModal()});overlay.addEventListener('keydown',e=>{if(e.key==='Escape')closeModal()});save.onclick=async()=>{if(saving)return;saving=true;save.disabled=true;save.textContent='保存中…';try{await onSave()}catch(e){notice(e.message||String(e));saving=false;save.disabled=false;save.textContent='保存';}};requestAnimationFrame(focusFirstInput)}function closeModal(){$('#modalRoot').innerHTML='';document.body.classList.remove('modal-open')}
function prepareFreshLogin(){closeModal();const login=$('#login'),appView=$('#app'),user=$('#loginUser'),password=$('#loginPassword'),button=$('#loginBtn');appView.classList.add('hidden');login.classList.remove('hidden');[user,password,button].forEach(el=>{el.disabled=false;el.readOnly=false;el.style.pointerEvents='auto';});button.textContent='登录系统';requestAnimationFrame(()=>{password.focus();password.select();});}
async function submitLogin(){const user=$('#loginUser'),password=$('#loginPassword'),button=$('#loginBtn'),server=$('#loginServer');if(button.disabled)return;if(!server.value.trim()){notice('请先填写服务器地址。');server.focus();return;}if(!user.value.trim()){notice('请填写登录账号。');user.focus();return;}button.disabled=true;button.textContent='正在登录…';try{const s=await window.crm.setServer(server.value);server.value=s.serverUrl;const r=await window.crm.login({username:user.value.trim(),password:password.value});if(!r.ok){password.focus();password.select();notice(r.message);return;}currentUser=r.user;$('#userInfo').textContent=`当前登录：${currentUser.displayName}（${roles[currentUser.role]||currentUser.role}）`;document.querySelectorAll('.super-only').forEach(el=>el.classList.toggle('hidden',!isSuper()));document.querySelectorAll('.manager-only').forEach(el=>el.classList.toggle('hidden',!isManager()));$('#login').classList.add('hidden');$('#app').classList.remove('hidden');await init();}catch(e){notice(e.message||String(e));password.focus();}finally{button.disabled=false;button.textContent='登录系统';}}
async function checkForUpdates(){const btn=$('#checkUpdate'),label=$('#appVersion');btn.disabled=true;btn.textContent='检查中…';try{const r=await window.crm.updateCheck();if(!r.configured){notice('服务器上还没有发布更新包，当前已是可用版本。');return;}label.textContent=`当前版本 v${r.currentVersion}`;if(!r.latestVersion||r.latestVersion===r.currentVersion){notice('当前已经是最新版本。');return;}const detail=r.notes?`\n\n更新内容：${r.notes}`:'';if(!confirm(`发现新版本 v${r.latestVersion}。${detail}\n\n是否打开下载地址？`))return;if(!r.downloadUrl)throw new Error('服务器未提供更新下载地址。');await window.crm.updateOpen(r.downloadUrl);}catch(e){notice(e.message||String(e));}finally{btn.disabled=false;btn.textContent='检查更新';}}
async function initLoginVersion(){try{const i=await window.crm.info();if(i.version)$('#appVersion').textContent=`当前版本 v${i.version}`;}catch(e){}}
async function initServerField(){try{const s=await window.crm.getServer();$('#loginServer').value=s.serverUrl||'';}catch(e){}}
async function testServerConn(){const btn=$('#testServer'),server=$('#loginServer');if(!server.value.trim()){notice('请先填写服务器地址。');server.focus();return;}btn.disabled=true;btn.textContent='连接中…';try{const s=await window.crm.setServer(server.value);server.value=s.serverUrl;const r=await window.crm.ping();notice(`服务器连接正常${r.serverVersion?`（服务端 v${r.serverVersion}）`:''}，可以登录了。`);}catch(e){notice(e.message||String(e));}finally{btn.disabled=false;btn.textContent='测试连接';}}
function bindLoginControls(){prepareFreshLogin();$('#checkUpdate').onclick=checkForUpdates;$('#testServer').onclick=testServerConn;initLoginVersion();initServerField();$('#loginBtn').onclick=submitLogin;['loginServer','loginUser','loginPassword'].forEach(id=>$('#'+id).onkeydown=e=>{if(e.key==='Enter'){e.preventDefault();submitLogin();}});$('#logoutBtn').onclick=async()=>{if(!confirm('确认退出当前账号并返回登录页？'))return;try{await window.crm.logout()}catch(e){}currentUser=null;cache={companies:[],users:[],customers:[],leads:[],contracts:[],followups:[],attachments:[],learningMaterials:[]};selectedCustomer=null;selectedContract=null;currentView='customers';window.location.reload();};document.querySelectorAll('.nav').forEach(b=>b.onclick=()=>{currentView=b.dataset.view;render()});}
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',bindLoginControls);else bindLoginControls();
