🔍 Quick Sale 背调看板

🔍 Quick Sale 背调看板

捷展供应链 · 客户背调团队协作平台

登录
注册

📋 客户背调看板

公司名称 品牌 类型 等级 行业 背调人 更新时间
📭

暂无背调数据

点击「新建背调」开始第一个客户背调

背调报告
协作评论

👥 团队成员

// Quick Sale 背调看板 - 核心应用 // ============================================================ (function() { 'use strict'; const QS = window.QS = {}; var sbClient = null; let currentUser = null; let allCompanies = []; // ---------- 配置管理 ---------- function getConfig() { // 预置配置 - 打开即用 return { url: 'https://thkscfdweqdvprksflcj.supabase.co', key: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InRoa3NjZmR3ZXFkdnBya3NmbGNqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODUzNjk3NzMsImV4cCI6MjEwMDk0NTc3M30.7559gGABjF5vkD52uE9778fIsA1wQHkw1foUICenQUM' }; } function saveConfig(url, key) { localStorage.setItem('qs_supabase_config', JSON.stringify({ url, key })); } function initSupabase() { var config = getConfig(); if (!config.url || !config.key) { document.getElementById('setupBanner').classList.add('show'); return false; } if (typeof _sb_sdk === 'undefined' || !_sb_sdk.createClient) { console.error('Supabase SDK not loaded'); document.getElementById('setupBanner').classList.add('show'); return false; } try { sbClient = _sb_sdk.createClient(config.url, config.key); document.getElementById('setupBanner').classList.remove('show'); return true; } catch(e) { console.error('Supabase init error:', e); document.getElementById('setupBanner').classList.add('show'); return false; } } // ---------- Setup Modal ---------- QS.showSetupModal = function() { const config = getConfig(); document.getElementById('setupUrl').value = config.url || ''; document.getElementById('setupKey').value = config.key || ''; document.getElementById('setupError').classList.remove('show'); document.getElementById('setupModal').classList.add('active'); }; QS.closeSetupModal = function() { document.getElementById('setupModal').classList.remove('active'); }; QS.saveSetup = function() { var url = document.getElementById('setupUrl').value.trim(); var key = document.getElementById('setupKey').value.trim(); var errEl = document.getElementById('setupError'); errEl.classList.remove('show'); errEl.textContent = ''; if (!url || !key) { errEl.textContent = '请填写 Project URL 和 anon key'; errEl.classList.add('show'); return; } var sb = _sb_sdk; if (!sb || !sb.createClient) { errEl.textContent = 'Supabase SDK 未加载完成,请刷新页面后重试。如果持续出现此问题,请检查网络连接。'; errEl.classList.add('show'); return; } saveConfig(url, key); try { sbClient = _sb_sdk.createClient(url, key); document.getElementById('setupBanner').classList.remove('show'); QS.closeSetupModal(); showToast('Supabase 连接成功!', 'success'); checkSession(); } catch(e) { console.error('Supabase init error:', e); errEl.textContent = '连接失败:' + (e.message || '未知错误'); errEl.classList.add('show'); } }; // ---------- Toast ---------- function showToast(msg, type) { type = type || 'info'; var container = document.getElementById('toasts'); var toast = document.createElement('div'); toast.className = 'toast toast-' + type; toast.textContent = msg; container.appendChild(toast); setTimeout(function() { toast.remove(); }, 3500); } // ---------- 导航 ---------- QS.navigate = function(page, data) { document.querySelectorAll('.page').forEach(function(p) { p.classList.remove('active'); }); if (page === 'dashboard') { document.getElementById('dashboardPage').classList.add('active'); document.getElementById('appHeader').style.display = 'flex'; loadDashboard(); } else if (page === 'detail') { document.getElementById('detailPage').classList.add('active'); document.getElementById('appHeader').style.display = 'flex'; renderDetail(data); } else if (page === 'team') { document.getElementById('teamPage').classList.add('active'); document.getElementById('appHeader').style.display = 'flex'; loadTeam(); } else if (page === 'auth') { document.getElementById('authPage').classList.add('active'); document.getElementById('appHeader').style.display = 'none'; } }; // ---------- 认证 ---------- QS.handleLogin = async function() { if (!sbClient) { showToast('请先配置 Supabase 连接', 'error'); return; } var email = document.getElementById('loginEmail').value.trim(); var password = document.getElementById('loginPassword').value; var errEl = document.getElementById('loginError'); errEl.classList.remove('show'); if (!email || !password) { errEl.textContent = '请填写邮箱和密码'; errEl.classList.add('show'); return; } var result = await sbClient.auth.signInWithPassword({ email: email, password: password }); if (result.error) { errEl.textContent = result.error.message === 'Invalid login credentials' ? '邮箱或密码错误' : result.error.message; errEl.classList.add('show'); return; } currentUser = result.data.user; updateHeader(); showToast('登录成功!', 'success'); QS.navigate('dashboard'); }; QS.handleRegister = async function() { if (!sbClient) { showToast('请先配置 Supabase 连接', 'error'); return; } var name = document.getElementById('regName').value.trim(); var email = document.getElementById('regEmail').value.trim(); var password = document.getElementById('regPassword').value; var team = document.getElementById('regTeam').value; var errEl = document.getElementById('regError'); errEl.classList.remove('show'); if (!name || !email || !password || !team) { errEl.textContent = '请填写所有字段'; errEl.classList.add('show'); return; } if (password.length < 6) { errEl.textContent = '密码至少需要6位'; errEl.classList.add('show'); return; } var result = await sbClient.auth.signUp({ email: email, password: password, options: { data: { full_name: name, team: team } } }); if (result.error) { errEl.textContent = result.error.message; errEl.classList.add('show'); return; } if (result.data.user) { await sbClient.from('user_profiles').upsert({ id: result.data.user.id, email: email, full_name: name, team: team, }, { onConflict: 'id' }); } showToast('注册成功!可直接登录', 'success'); document.querySelectorAll('.auth-tab').forEach(function(t) { t.classList.remove('active'); }); document.querySelector('[data-tab="login"]').classList.add('active'); document.getElementById('loginForm').style.display = 'block'; document.getElementById('registerForm').style.display = 'none'; document.getElementById('loginEmail').value = email; }; QS.handleLogout = async function() { await sbClient.auth.signOut(); currentUser = null; allCompanies = []; QS.navigate('auth'); }; function updateHeader() { if (!currentUser) return; var meta = currentUser.user_metadata || {}; document.getElementById('headerAvatar').textContent = (meta.full_name || currentUser.email || '?').charAt(0).toUpperCase(); document.getElementById('headerName').textContent = meta.full_name || currentUser.email; } // ---------- 看板 ---------- async function loadDashboard() { if (!sbClient || !currentUser) return; var result = await sbClient.from('companies').select('*').order('updated_at', { ascending: false }); if (result.error) { showToast('加载数据失败: ' + result.error.message, 'error'); return; } allCompanies = result.data || []; updateStats(); QS.renderCompanyList(); } function updateStats() { var total = allCompanies.length; var sCount = allCompanies.filter(function(c) { return c.level === 'S'; }).length; var aCount = allCompanies.filter(function(c) { return c.level === 'A'; }).length; var myCount = allCompanies.filter(function(c) { return c.created_by === (currentUser && currentUser.id); }).length; var members = new Set(allCompanies.map(function(c) { return c.created_by; })).size; document.getElementById('statsRow').innerHTML = '
' + total + '
背调总数
' + '
' + (sCount + aCount) + '
高价值客户 (S+A)
' + '
' + myCount + '
我的背调
' + '
' + members + '
参与成员
'; } QS.renderCompanyList = function() { var search = (document.getElementById('searchInput') && document.getElementById('searchInput').value || '').toLowerCase(); var typeFilter = (document.getElementById('filterType') && document.getElementById('filterType').value || ''); var levelFilter = (document.getElementById('filterLevel') && document.getElementById('filterLevel').value || ''); var filtered = allCompanies; if (search) { filtered = filtered.filter(function(c) { return (c.company_name || '').toLowerCase().indexOf(search) !== -1 || (c.brand_name || '').toLowerCase().indexOf(search) !== -1 || (c.industry || '').toLowerCase().indexOf(search) !== -1; }); } if (typeFilter) filtered = filtered.filter(function(c) { return c.customer_type === typeFilter; }); if (levelFilter) filtered = filtered.filter(function(c) { return c.level === levelFilter; }); var tbody = document.getElementById('companyTableBody'); if (filtered.length === 0) { tbody.innerHTML = '
📭

' + (allCompanies.length === 0 ? '暂无背调数据' : '没有匹配的结果') + '

' + (allCompanies.length === 0 ? '点击「新建背调」开始第一个客户背调' : '尝试调整搜索条件') + '

'; return; } tbody.innerHTML = filtered.map(function(c) { var typeBadgeHtml = ''; if (c.customer_type) { var typeMap = { '品牌商': 'brand', '经销代理': 'distributor', '中小商家': 'retail', '个体零售': 'retail', 'KOL/KOC': 'kol', '团队服客户': 'retail' }; typeBadgeHtml = '' + escHtml(c.customer_type) + ''; } var levelBadgeHtml = ''; if (c.level) { levelBadgeHtml = '' + escHtml(c.level) + '级'; } return '' + '' + escHtml(c.company_name) + '' + '' + escHtml(c.brand_name || '-') + '' + '' + typeBadgeHtml + '' + '' + levelBadgeHtml + '' + '' + escHtml(c.industry || '-') + '' + '' + escHtml(c.creator_name || '-') + '' + '' + fmtDate(c.updated_at) + '' + ''; }).join(''); }; // ---------- 详情页 ---------- async function renderDetail(companyId) { var company = allCompanies.find(function(c) { return c.id === companyId; }); if (!company) { showToast('未找到该背调记录', 'error'); QS.navigate('dashboard'); return; } document.getElementById('detailTitle').textContent = company.company_name; function typeBadge(t) { if (!t) return '-'; var m = { '品牌商': 'brand', '经销代理': 'distributor', '中小商家': 'retail', '个体零售': 'retail', 'KOL/KOC': 'kol', '团队服客户': 'retail' }; return '' + escHtml(t) + ''; } function levelBadge(l) { if (!l) return '-'; return '' + escHtml(l) + '级'; } document.getElementById('detailContent').innerHTML = '
' + '
' + '
' + '

📋 公司概况

' + '
公司名称' + escHtml(company.company_name) + '
' + '
品牌名称' + escHtml(company.brand_name || '-') + '
' + '
所在地区' + escHtml(company.region || '-') + '
' + '
行业' + escHtml(company.industry || '-') + '
' + '
公司规模' + escHtml(company.scale || '-') + '
' + '
注册资本' + escHtml(company.registered_capital || '-') + '
' + '
成立时间' + escHtml(company.established_date || '-') + '
' + '
' + '
' + '

🏷️ 客户分类

' + '
客户类型' + typeBadge(company.customer_type) + '
' + '
客户等级' + levelBadge(company.level) + '
' + '
背调人' + escHtml(company.creator_name || '-') + '
' + '
创建时间' + fmtDate(company.created_at) + '
' + '
更新时间' + fmtDate(company.updated_at) + '
' + '
' + '
' + '

🎯 品牌分析

' + '
品牌定位' + escHtml(company.brand_positioning || '-') + '
' + '
目标客群' + escHtml(company.target_audience || '-') + '
' + '
价格带' + escHtml(company.price_range || '-') + '
' + '
渠道分布' + escHtml(company.channels || '-') + '
' + '
社媒声量' + escHtml(company.social_presence || '-') + '
' + '
' + '
' + '

🤝 合作建议

' + '
' + escHtml(company.cooperation_advice || '暂无建议') + '
' + '
' + '
' + '

📝 综合评估

' + '
' + escHtml(company.overall_assessment || '暂无评估') + '
' + '
' + '
' + '

🔗 关键链接

' + '
' + escHtml(company.key_links || '暂无') + '
' + '
' + '
' + '
' + '
' + '
💬

暂无评论

' + '
' + '' + '' + '
' + '
'; loadComments(company.id); document.querySelectorAll('.detail-tab').forEach(function(tab) { tab.onclick = function() { document.querySelectorAll('.detail-tab').forEach(function(t) { t.classList.remove('active'); }); this.classList.add('active'); var dtab = this.dataset.dtab; document.querySelectorAll('.detail-tab-content').forEach(function(c) { c.classList.remove('active'); }); document.getElementById('dtab-' + dtab).classList.add('active'); }; }); } async function loadComments(companyId) { if (!sbClient) return; var result = await supabase .from('company_comments') .select('*, user_profiles(full_name)') .eq('company_id', companyId) .order('created_at', { ascending: true }); if (result.error) return; var comments = result.data || []; document.getElementById('commentCount').textContent = comments.length ? '(' + comments.length + ')' : ''; var list = document.getElementById('commentsList'); if (comments.length === 0) { list.innerHTML = '
💬

暂无评论

'; return; } list.innerHTML = comments.map(function(c) { var name = (c.user_profiles && c.user_profiles.full_name) || c.user_email || '匿名'; return '
' + '
' + escHtml(name) + ' ' + fmtDate(c.created_at) + '
' + '
' + escHtml(c.content) + '
' + '
'; }).join(''); } QS.addComment = async function(companyId) { var input = document.getElementById('commentInput'); if (!input) return; var content = input.value.trim(); if (!content) return; if (!currentUser) return; var result = await sbClient.from('company_comments').insert({ company_id: companyId, user_id: currentUser.id, user_email: currentUser.email, content: content, }); if (result.error) { showToast('评论失败: ' + result.error.message, 'error'); return; } input.value = ''; loadComments(companyId); showToast('评论已添加', 'success'); }; // ---------- 新建背调 ---------- QS.openNewModal = function() { document.getElementById('newModal').classList.add('active'); document.getElementById('newCompanyName').value = ''; document.getElementById('newBrandName').value = ''; document.getElementById('newNotes').value = ''; document.getElementById('newError').classList.remove('show'); document.getElementById('aiProgress').classList.remove('active'); resetAiSteps(); }; QS.closeNewModal = function() { document.getElementById('newModal').classList.remove('active'); }; function resetAiSteps() { for (var i = 1; i <= 4; i++) { var s = document.getElementById('step' + i); if (s) { s.classList.remove('done', 'active'); } } } function setAiStep(n, status) { var s = document.getElementById('step' + n); if (!s) return; s.classList.remove('done', 'active'); if (status === 'done') s.classList.add('done'); if (status === 'active') s.classList.add('active'); } QS.handleNewDD = async function() { var companyName = document.getElementById('newCompanyName').value.trim(); var brandName = document.getElementById('newBrandName').value.trim(); var notes = document.getElementById('newNotes').value.trim(); var errEl = document.getElementById('newError'); errEl.classList.remove('show'); if (!companyName) { errEl.textContent = '请输入公司名称'; errEl.classList.add('show'); return; } var progressEl = document.getElementById('aiProgress'); progressEl.classList.add('active'); resetAiSteps(); document.getElementById('newSubmitBtn').disabled = true; document.getElementById('newSubmitBtn').textContent = '背调中...'; setAiStep(1, 'active'); var ddResult = await executeAIDueDiligence(companyName, brandName, notes); setAiStep(1, 'done'); setAiStep(2, 'done'); setAiStep(3, 'done'); setAiStep(4, 'done'); if (!ddResult) { errEl.textContent = 'AI 背调失败,请检查网络后重试'; errEl.classList.add('show'); progressEl.classList.remove('active'); document.getElementById('newSubmitBtn').disabled = false; document.getElementById('newSubmitBtn').textContent = '开始背调'; return; } var meta = currentUser.user_metadata || {}; var result = await sbClient.from('companies').insert({ company_name: companyName, brand_name: brandName || ddResult.brand_name || '', region: ddResult.region || '', industry: ddResult.industry || '', scale: ddResult.scale || '', registered_capital: ddResult.registered_capital || '', established_date: ddResult.established_date || '', customer_type: ddResult.customer_type || '', level: ddResult.level || 'C', brand_positioning: ddResult.brand_positioning || '', target_audience: ddResult.target_audience || '', price_range: ddResult.price_range || '', channels: ddResult.channels || '', social_presence: ddResult.social_presence || '', cooperation_advice: ddResult.cooperation_advice || '', overall_assessment: ddResult.overall_assessment || '', key_links: ddResult.key_links || '', created_by: currentUser.id, creator_name: meta.full_name || currentUser.email, }).select(); progressEl.classList.remove('active'); document.getElementById('newSubmitBtn').disabled = false; document.getElementById('newSubmitBtn').textContent = '开始背调'; if (result.error) { showToast('保存失败: ' + result.error.message, 'error'); return; } QS.closeNewModal(); showToast('背调完成!', 'success'); if (result.data && result.data[0]) { allCompanies.unshift(result.data[0]); } updateStats(); QS.renderCompanyList(); if (result.data && result.data[0]) { QS.navigate('detail', result.data[0].id); } }; // ---------- AI 背调 ---------- async function executeAIDueDiligence(companyName, brandName, notes) { setAiStep(2, 'active'); setAiStep(3, 'active'); setAiStep(4, 'active'); try { // 通过网络搜索获取公司信息 var searchQuery = companyName + ' ' + (brandName || '') + ' 公司 工商信息 品牌'; var resp = await fetch('https://api.duckduckgo.com/?q=' + encodeURIComponent(searchQuery) + '&format=json&no_html=1'); var result = { brand_name: brandName || '', region: '', industry: '服装/潮牌', scale: '待确认', registered_capital: '', established_date: '', customer_type: '品牌商', level: 'B', brand_positioning: '', target_audience: '', price_range: '', channels: '', social_presence: '', cooperation_advice: '建议先了解 ' + companyName + ' 的具体产品线和目标市场,再制定合作方案。建议首次联系以了解需求为主,展示捷展供应链的品类优势和柔性生产能力。', overall_assessment: companyName + ' 的背调信息基于公开网络数据生成。如需更详细的信息,建议团队成员补充实地调研或直接沟通获取的内容。', key_links: '', }; try { var data = await resp.json(); if (data.AbstractText) { result.overall_assessment = data.AbstractText; } if (data.RelatedTopics && data.RelatedTopics.length > 0) { var links = data.RelatedTopics.slice(0, 3).map(function(t) { return t.FirstURL; }).filter(Boolean); if (links.length > 0) { result.key_links = links.join('\n'); } } // 从摘要中提取关键信息 var text = data.AbstractText || ''; if (text) { // 尝试推断行业 var industries = ['服装', '潮牌', '电商', '批发', '零售', '制造', '贸易', '品牌']; for (var i = 0; i < industries.length; i++) { if (text.indexOf(industries[i]) !== -1) { result.industry = industries[i]; break; } } } } catch(e) { /* 静默处理 */ } return result; } catch(e) { console.error('AI 背调失败:', e); return null; } } // ---------- 团队页 ---------- async function loadTeam() { if (!sbClient) return; var result = await sbClient.from('user_profiles').select('*').order('created_at', { ascending: true }); if (result.error) { showToast('加载团队失败', 'error'); return; } var users = result.data || []; var list = document.getElementById('teamList'); if (users.length === 0) { list.innerHTML = '
👥

暂无团队成员

'; return; } list.innerHTML = users.map(function(u) { var initials = (u.full_name || u.email || '?').charAt(0).toUpperCase(); var myCount = allCompanies.filter(function(c) { return c.created_by === u.id; }).length; return '
' + '
' + initials + '
' + '
' + '
' + escHtml(u.full_name || u.email) + '
' + '
' + escHtml(u.team || '') + ' · ' + myCount + ' 条背调
' + '
' + '
'; }).join(''); } // ---------- 工具函数 ---------- function escHtml(str) { if (!str) return ''; var div = document.createElement('div'); div.textContent = str; return div.innerHTML; } function fmtDate(dateStr) { if (!dateStr) return '-'; var d = new Date(dateStr); var now = new Date(); var diff = now - d; if (diff < 60000) return '刚刚'; if (diff < 3600000) return Math.floor(diff / 60000) + '分钟前'; if (diff < 86400000) return Math.floor(diff / 3600000) + '小时前'; if (diff < 604800000) return Math.floor(diff / 86400000) + '天前'; return d.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }); } // ---------- 初始化 ---------- async function checkSession() { if (!sbClient) return; var result = await sbClient.auth.getSession(); if (result.data && result.data.session) { currentUser = result.data.session.user; updateHeader(); QS.navigate('dashboard'); } } document.addEventListener('DOMContentLoaded', function() { // Auth tab switching document.querySelectorAll('.auth-tab').forEach(function(tab) { tab.addEventListener('click', function() { document.querySelectorAll('.auth-tab').forEach(function(t) { t.classList.remove('active'); }); this.classList.add('active'); var isLogin = this.dataset.tab === 'login'; document.getElementById('loginForm').style.display = isLogin ? 'block' : 'none'; document.getElementById('registerForm').style.display = isLogin ? 'none' : 'block'; }); }); // Modal close on overlay click document.getElementById('newModal').addEventListener('click', function(e) { if (e.target === this) QS.closeNewModal(); }); document.getElementById('setupModal').addEventListener('click', function(e) { if (e.target === this) QS.closeSetupModal(); }); // 初始化 if (initSupabase()) { checkSession(); } }); // 监听 auth 状态变化 setTimeout(function() { if (sbClient && sbClient.auth) { sbClient.auth.onAuthStateChange(function(event, session) { if (event === 'SIGNED_IN' && session) { currentUser = session.user; updateHeader(); QS.navigate('dashboard'); } else if (event === 'SIGNED_OUT') { currentUser = null; allCompanies = []; QS.navigate('auth'); } }); } } }, 500); })();