/**
* 线索表单公共组件 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();
}
})();