/** * 线索表单公共组件 v3.0 * * 使用方式: * 1. 引入: * 2. 调用:LeadForm.open({ product, fields, onSubmit }) */ (function() { if (window.LeadForm) return; // ---- 配置 ---- var BASE_URL = 'https://www.weisiyu.com'; var TENCENT_APP_ID = '190585375'; var API_VERIFY_CAPTCHA = BASE_URL + '/coze/auth/external/captcha/verify'; var API_LEAD_PUSH = BASE_URL + '/coze/wecom/messages/text'; var LEAD_PUSH_APP_ID = 'weisiyu_guanwangxiansuotuisong'; var SUBMIT_DUPLICATE_WINDOW_MS = 10000; var currentConfig = null; var lastSubmitFingerprint = ''; var lastSubmitAt = 0; var modalEl = null; // ---- 工具函数 ---- function isValidContact(val) { var v = (val || '').trim(); if (!v) return false; var digits = v.replace(/\D/g, ''); return digits.length >= 7 || (v.indexOf('@') !== -1 && v.indexOf('.') !== -1); } function getValues() { if (!currentConfig) return {}; var vals = {}; if (currentConfig.fields) { currentConfig.fields.forEach(function(f) { var el = document.getElementById(f.id); if (el) vals[f.id] = el.value.trim(); }); } return vals; } function showError(msg) { var el = document.getElementById('__leadError'); if (el) { el.textContent = msg; el.style.display = 'block'; } } function hideError() { var el = document.getElementById('__leadError'); if (el) el.style.display = 'none'; } function resetBtn() { var btn = document.getElementById('__leadSubmit'); if (!btn) return; btn.disabled = false; btn.textContent = '提交并继续'; btn.style.background = 'linear-gradient(to right, #2563eb, #4f46e5)'; } function validate() { if (!currentConfig || !currentConfig.fields) return false; var vals = getValues(); for (var i = 0; i < currentConfig.fields.length; i++) { var f = currentConfig.fields[i]; if (f.required && !vals[f.id]) { showError('请填写' + f.label); return false; } if (f.id === 'contact' && vals[f.id] && !isValidContact(vals[f.id])) { showError('请输入有效的手机号或邮箱'); return false; } } return true; } // ---- 创建 DOM ---- function createFieldHTML(field) { var requiredStar = field.required ? '*' : ''; if (field.type === 'textarea') { return '
' + '' + '' + '
'; } else if (field.type === 'readonly') { return '
' + '' + '' + '
'; } else { return '
' + '' + '' + '
'; } } function buildFormHTML() { if (!currentConfig || !currentConfig.fields) return ''; var html = ''; currentConfig.fields.forEach(function(f) { html += createFieldHTML(f); }); return html; } function createModal() { // Remove existing if any var existing = document.getElementById('__leadModal'); if (existing) existing.parentNode.removeChild(existing); // Create outer modalEl = document.createElement('div'); modalEl.id = '__leadModal'; modalEl.style.cssText = 'position:fixed;inset:0;z-index:9999;display:none;align-items:center;justify-content:center;background:rgba(15,23,42,0.4);backdrop-filter:blur(4px);transition:opacity 0.3s;padding:0 1rem;box-sizing:border-box;'; // Create inner container var inner = document.createElement('div'); inner.style.cssText = 'position:relative;width:100%;max-width:28rem;overflow:hidden;border-radius:24px;background:rgba(255,255,255,0.95);padding:1.5rem;box-shadow:0 25px 50px -12px rgba(0,0,0,0.25);border:1px solid rgba(226,232,240,0.5);backdrop-filter:blur(20px);'; // Form var form = document.createElement('form'); form.id = '__leadForm'; form.style.cssText = 'position:relative;z-index:10;display:flex;flex-direction:column;gap:1rem;'; inner.appendChild(form); // Error var errDiv = document.createElement('div'); errDiv.id = '__leadError'; errDiv.style.cssText = 'display:none;text-align:center;font-size:0.875rem;color:#f43f5e;margin-top:0.5rem;'; inner.appendChild(errDiv); // Submit button var submitBtn = document.createElement('button'); submitBtn.id = '__leadSubmit'; submitBtn.type = 'button'; submitBtn.style.cssText = 'margin-top:1rem;height:2.75rem;width:100%;border-radius:12px;background:linear-gradient(to right, #2563eb, #4f46e5);font-weight:700;color:#fff;box-shadow:0 8px 20px rgba(79,70,229,0.2);transition:all 0.2s;border:none;cursor:pointer;'; submitBtn.textContent = '提交并继续'; submitBtn.addEventListener('click', handleSubmit); inner.appendChild(submitBtn); modalEl.appendChild(inner); document.body.appendChild(modalEl); // Click outside to close modalEl.addEventListener('click', function(e) { if (e.target === modalEl) hide(); }); } function buildForm() { var form = document.getElementById('__leadForm'); if (!form) return; form.innerHTML = buildFormHTML(); } function show() { if (!document.getElementById('__leadModal')) { createModal(); } buildForm(); var modal = document.getElementById('__leadModal'); if (modal) modal.style.display = 'flex'; } function hide() { var modal = document.getElementById('__leadModal'); if (modal) modal.style.display = 'none'; hideError(); } function render(opts) { currentConfig = opts || {}; } function handleSubmit() { hideError(); var data = getValues(); if (!validate()) return; var now = Date.now(); var fp = JSON.stringify(data); if (fp === lastSubmitFingerprint && (now - lastSubmitAt < SUBMIT_DUPLICATE_WINDOW_MS)) { return showError('请勿重复提交,请稍后再试'); } lastSubmitFingerprint = fp; lastSubmitAt = now; var btn = document.getElementById('__leadSubmit'); btn.disabled = true; btn.textContent = '验证中...'; // Custom submit handler if (currentConfig && typeof currentConfig.onSubmit === 'function') { currentConfig.onSubmit(data, function(success) { if (success) { btn.textContent = '提交成功 ✓'; btn.style.background = '#10b981'; setTimeout(function() { hide(); resetBtn(); var form = document.getElementById('__leadForm'); if (form) form.reset(); }, 2000); } else { resetBtn(); } }); return; } // Default submit logic doDefaultSubmit(data, btn); } function doDefaultSubmit(data, btn) { // 步骤1:确保腾讯验证码 SDK 已加载(动态懒注入,避免被当作 JS 加载时第一行报错) if (typeof TencentCaptcha !== 'undefined') { // 已加载,直接走 startCaptcha(data, btn); return; } // 还没加载:检查 script 标签是否已存在 var existing = document.getElementById('__tcaptcha_sdk'); if (existing) { // script 标签存在但 SDK 还在加载中,等 load 事件 existing.addEventListener('load', function() { startCaptcha(data, btn); }, { once: true }); // 兜底:3 秒后还没加载完,提示用户 setTimeout(function() { if (typeof TencentCaptcha === 'undefined') { resetBtn(); showError('验证码加载超时,请重试'); } }, 3000); return; } // 第一次注入:创建 script 并等 load 事件 var s = document.createElement('script'); s.id = '__tcaptcha_sdk'; s.src = 'https://ssl.captcha.qq.com/TCaptcha.js'; s.onload = function() { startCaptcha(data, btn); }; s.onerror = function() { console.error('TCaptcha.js 加载失败'); resetBtn(); showError('验证码 SDK 加载失败,请检查网络'); }; document.head.appendChild(s); // 兜底超时 setTimeout(function() { if (typeof TencentCaptcha === 'undefined') { resetBtn(); showError('验证码加载超时,请重试'); } }, 3000); } function startCaptcha(data, btn) { try { if (typeof TencentCaptcha === 'undefined') throw new Error('验证码 SDK 未就绪'); var captcha = new TencentCaptcha(TENCENT_APP_ID, function(res) { if (res.ret !== 0) { resetBtn(); showError('验证取消,请重试'); return; } btn.textContent = '提交中...'; fetch(API_VERIFY_CAPTCHA, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'omit', body: JSON.stringify({ captchaType: 'TencentCaptcha', ticket: res.ticket, randstr: res.randstr, userLanguage: 'zh-cn' }) }).then(function(vr) { if (!vr.ok) throw new Error('验证失败'); return fetch(API_LEAD_PUSH, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ app_id: LEAD_PUSH_APP_ID, content: '【官网线索】\n公司:' + (data.company || '-') + '\n联系人:' + (data.name || '-') + '\n联系方式:' + (data.contact || '-') + '\n咨询产品:' + (currentConfig && currentConfig.product || '-') + '\n留言:' + (data.message || '未填写') }) }); }).then(function(pr) { if (!pr.ok) throw new Error('线索提交失败'); btn.textContent = '提交成功 ✓'; btn.style.background = '#10b981'; setTimeout(function() { hide(); resetBtn(); var form = document.getElementById('__leadForm'); if (form) form.reset(); }, 2000); }).catch(function(e) { console.error(e); resetBtn(); showError(e.message || '提交失败,请重试'); }); }, { userLanguage: 'zh-cn' }); captcha.show(); } catch(e) { console.error(e); resetBtn(); showError(e.message || '提交失败,请重试'); } } // ---- 公开 API ---- window.LeadForm = { /** * 打开表单弹窗 * @param {object} opts * - product: string 产品名称 * - title: string 弹窗标题 * - subtitle: string 弹窗副标题 * - fields: array 字段定义 [{id, label, type, placeholder, required, value}] * - onSubmit: function 提交回调(data, doneFn),不传则走默认逻辑 */ open: function(opts) { render(opts); show(); }, // 关闭弹窗 close: function() { hide(); }, // 获取当前表单数据 getData: function() { return getValues(); } }; // ---- 初始化:按钮事件绑定 ---- function init() { document.querySelectorAll('[data-lead-open]').forEach(function(btn) { btn.addEventListener('click', function(e) { e.preventDefault(); var product = btn.getAttribute('data-lead-product') || ''; var fields = []; // 默认字段 if (product) { fields.push({ id: 'product', label: '咨询产品', type: 'readonly', value: product, required: true }); } fields.push( { id: 'company', label: '公司名称', type: 'text', placeholder: '请输入公司名称', required: true }, { id: 'name', label: '联系人', type: 'text', placeholder: '请输入您的姓名', required: true }, { id: 'contact', label: '联系方式', type: 'text', placeholder: '请输入手机号或邮箱', required: true }, { id: 'message', label: '留言', type: 'textarea', placeholder: '有什么我们可以帮您的...' } ); window.LeadForm.open({ product: product, fields: fields }); }); }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();