deploy: 2026-07-15 15:02 部署更新
This commit is contained in:
+94
-97
@@ -1197,120 +1197,117 @@ async function generateParadigm() {
|
||||
}
|
||||
}
|
||||
|
||||
/** 轻量 Markdown 转 HTML 渲染器 */
|
||||
/** 轻量 Markdown 转 HTML 渲染器(支持标题、列表、子列表、引用、粗体、代码) */
|
||||
function renderMarkdown(md) {
|
||||
if (!md) return '';
|
||||
const lines = md.split('\n');
|
||||
let html = '';
|
||||
let inList = false;
|
||||
let inOrderedList = false;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i];
|
||||
// 标题
|
||||
if (/^### /.test(line)) {
|
||||
if (inList) { html += '</ul>'; inList = false; }
|
||||
if (inOrderedList) { html += '</ol>'; inOrderedList = false; }
|
||||
html += `<h4 class="font-semibold text-base mt-4 mb-2 text-gray-800">${inlineMd(line.slice(4))}</h4>`;
|
||||
} else if (/^## /.test(line)) {
|
||||
if (inList) { html += '</ul>'; inList = false; }
|
||||
if (inOrderedList) { html += '</ol>'; inOrderedList = false; }
|
||||
html += `<h3 class="font-bold text-lg mt-5 mb-2 text-gray-900 border-b pb-1">${inlineMd(line.slice(3))}</h3>`;
|
||||
} else if (/^# /.test(line)) {
|
||||
if (inList) { html += '</ul>'; inList = false; }
|
||||
if (inOrderedList) { html += '</ol>'; inOrderedList = false; }
|
||||
html += `<h2 class="font-bold text-xl mt-5 mb-3 text-gray-900">${inlineMd(line.slice(2))}</h2>`;
|
||||
} else if (/^[-*] /.test(line)) {
|
||||
if (!inList) { html += '<ul class="space-y-1 ml-4 list-disc">'; inList = true; }
|
||||
html += `<li>${inlineMd(line.slice(2))}</li>`;
|
||||
} else if (/^\d+\. /.test(line)) {
|
||||
if (!inOrderedList) { html += '<ol class="space-y-1 ml-5 list-decimal">'; inOrderedList = true; }
|
||||
html += `<li>${inlineMd(line.replace(/^\d+\. /, ''))}</li>`;
|
||||
} else if (line.trim() === '') {
|
||||
if (inList) { html += '</ul>'; inList = false; }
|
||||
if (inOrderedList) { html += '</ol>'; inOrderedList = false; }
|
||||
html += '';
|
||||
} else {
|
||||
if (inList) { html += '</ul>'; inList = false; }
|
||||
if (inOrderedList) { html += '</ol>'; inOrderedList = false; }
|
||||
html += `<p class="mb-2">${inlineMd(line)}</p>`;
|
||||
let i = 0;
|
||||
/** 关闭所有打开的列表 */
|
||||
function closeLists(stack) {
|
||||
while (stack.length) {
|
||||
html += stack.pop() === 'ul' ? '</ul>' : '</ol>';
|
||||
}
|
||||
}
|
||||
if (inList) html += '</ul>';
|
||||
if (inOrderedList) html += '</ol>';
|
||||
const listStack = [];
|
||||
while (i < lines.length) {
|
||||
let line = lines[i];
|
||||
const trimmed = line.trimStart();
|
||||
const indent = line.length - trimmed.length;
|
||||
// 空行
|
||||
if (trimmed === '') {
|
||||
closeLists(listStack);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
// 标题
|
||||
if (/^### /.test(trimmed)) {
|
||||
closeLists(listStack);
|
||||
html += `<h4 class="font-semibold text-base mt-4 mb-2 text-gray-800">${inlineMd(trimmed.slice(4))}</h4>`;
|
||||
i++; continue;
|
||||
} else if (/^## /.test(trimmed)) {
|
||||
closeLists(listStack);
|
||||
html += `<h3 class="font-bold text-lg mt-5 mb-2 text-gray-900 border-b pb-1">${inlineMd(trimmed.slice(3))}</h3>`;
|
||||
i++; continue;
|
||||
} else if (/^# /.test(trimmed)) {
|
||||
closeLists(listStack);
|
||||
html += `<h2 class="font-bold text-xl mt-5 mb-3 text-gray-900">${inlineMd(trimmed.slice(2))}</h2>`;
|
||||
i++; continue;
|
||||
}
|
||||
// 引用块 >
|
||||
if (/^> /.test(trimmed)) {
|
||||
closeLists(listStack);
|
||||
const quoteLines = [];
|
||||
while (i < lines.length && /^>\s?/.test(lines[i].trimStart())) {
|
||||
quoteLines.push(lines[i].trimStart().replace(/^>\s?/, ''));
|
||||
i++;
|
||||
}
|
||||
html += `<blockquote class="border-l-4 border-gray-300 pl-3 py-1 my-2 text-gray-600 italic">${inlineMd(quoteLines.join(' '))}</blockquote>`;
|
||||
continue;
|
||||
}
|
||||
// 有序列表
|
||||
if (/^\d+\. /.test(trimmed)) {
|
||||
const level = Math.floor(indent / 2);
|
||||
while (listStack.length > level) {
|
||||
html += listStack.pop() === 'ul' ? '</ul>' : '</ol>';
|
||||
}
|
||||
if (listStack.length === level) {
|
||||
if (listStack[level - 1] !== 'ol') {
|
||||
if (listStack.length) html += listStack.pop() === 'ul' ? '</ul>' : '</ol>';
|
||||
html += '<ol class="space-y-1 ml-5 list-decimal">';
|
||||
listStack.push('ol');
|
||||
}
|
||||
} else {
|
||||
html += '<ol class="space-y-1 ml-5 list-decimal">';
|
||||
listStack.push('ol');
|
||||
}
|
||||
html += `<li class="text-gray-700">${inlineMd(trimmed.replace(/^\d+\. /, ''))}</li>`;
|
||||
i++; continue;
|
||||
}
|
||||
// 无序列表
|
||||
if (/^[-*] /.test(trimmed)) {
|
||||
const level = Math.floor(indent / 2);
|
||||
while (listStack.length > level) {
|
||||
html += listStack.pop() === 'ul' ? '</ul>' : '</ol>';
|
||||
}
|
||||
if (listStack.length === level) {
|
||||
if (listStack[level - 1] !== 'ul') {
|
||||
if (listStack.length) html += listStack.pop() === 'ul' ? '</ul>' : '</ol>';
|
||||
html += '<ul class="space-y-1 ml-4 list-disc">';
|
||||
listStack.push('ul');
|
||||
}
|
||||
} else {
|
||||
html += '<ul class="space-y-1 ml-4 list-disc">';
|
||||
listStack.push('ul');
|
||||
}
|
||||
html += `<li class="text-gray-700">${inlineMd(trimmed.slice(2))}</li>`;
|
||||
i++; continue;
|
||||
}
|
||||
// 普通段落(收集连续非空非格式行)
|
||||
closeLists(listStack);
|
||||
const paraLines = [trimmed];
|
||||
i++;
|
||||
while (i < lines.length) {
|
||||
const nextTrimmed = lines[i].trimStart();
|
||||
if (nextTrimmed === '' || /^#{1,3} /.test(nextTrimmed) || /^[-*] /.test(nextTrimmed) ||
|
||||
/^\d+\. /.test(nextTrimmed) || /^> /.test(nextTrimmed)) break;
|
||||
paraLines.push(nextTrimmed);
|
||||
i++;
|
||||
}
|
||||
html += `<p class="mb-3 text-gray-700 leading-relaxed">${inlineMd(paraLines.join(' '))}</p>`;
|
||||
}
|
||||
closeLists(listStack);
|
||||
return html;
|
||||
}
|
||||
|
||||
/** 行内 Markdown:粗体、行内代码、链接 */
|
||||
function inlineMd(text) {
|
||||
// 转义 HTML
|
||||
text = escapeHtml(text);
|
||||
// 粗体 **text**
|
||||
text = text.replace(/\*\*(.+?)\*\*/g, '<strong class="font-semibold text-gray-900">$1</strong>');
|
||||
// 行内代码 `code`
|
||||
text = text.replace(/`(.+?)`/g, '<code class="bg-gray-100 px-1 rounded text-xs">$1</code>');
|
||||
return text;
|
||||
}
|
||||
|
||||
function renderParadigm(data) {
|
||||
const result = document.getElementById('paradigm-result');
|
||||
const stageColors = {
|
||||
'成交': 'border-green-400 bg-green-50',
|
||||
'异议处理': 'border-orange-400 bg-orange-50',
|
||||
'报价': 'border-blue-400 bg-blue-50',
|
||||
'需求发现': 'border-purple-400 bg-purple-50',
|
||||
'建立联系': 'border-gray-300 bg-gray-50',
|
||||
};
|
||||
result.innerHTML = `
|
||||
<div class="bg-white rounded-lg shadow p-6">
|
||||
<h3 class="text-lg font-bold mb-1">标准范式对话模板</h3>
|
||||
<p class="text-sm text-gray-500 mb-2">客户类型: ${data.customer_type || '-'}</p>
|
||||
${data.product_positioning ? `<p class="text-sm text-green-700 mb-4">产品推荐理由: ${escapeHtml(data.product_positioning)}</p>` : '<p class="mb-4"></p>'}
|
||||
|
||||
<div class="space-y-4 mb-6">
|
||||
${(data.stages || []).map((s, i) => `
|
||||
<div class="border-l-4 ${stageColors[s.stage] || stageColors['建立联系']} pl-4 py-3">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="bg-gray-700 text-white rounded-full w-6 h-6 flex items-center justify-center text-xs font-bold">${i + 1}</span>
|
||||
<span class="font-semibold text-sm">${s.stage}</span>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 mb-2">目标: ${s.goal || '-'}</p>
|
||||
<div class="mb-2">
|
||||
<span class="text-xs font-medium text-green-700">销售话术:</span>
|
||||
<p class="text-sm text-gray-700 mt-1 bg-green-50 rounded p-2">${escapeHtml(s.sales_script || '-')}</p>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="text-xs font-medium text-gray-600">预期回应:</span>
|
||||
<p class="text-sm text-gray-600 mt-1">${escapeHtml(s.expected_response || '-')}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-xs font-medium text-blue-600">技巧提示:</span>
|
||||
<p class="text-sm text-gray-600 mt-1">${escapeHtml(s.tips || '-')}</p>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
|
||||
${(data.key_techniques || []).length > 0 ? `
|
||||
<div class="mb-4">
|
||||
<h4 class="text-sm font-semibold mb-2 text-green-700">核心销售技巧</h4>
|
||||
<ul class="space-y-1">
|
||||
${(data.key_techniques || []).map(t => `<li class="text-sm text-gray-700 flex gap-2"><span class="text-green-500">✓</span><span>${escapeHtml(t)}</span></li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${(data.improvement_points || []).length > 0 ? `
|
||||
<div class="mb-4">
|
||||
<h4 class="text-sm font-semibold mb-2 text-orange-700">可改进的点</h4>
|
||||
<ul class="space-y-1">
|
||||
${(data.improvement_points || []).map(t => `<li class="text-sm text-gray-700 flex gap-2"><span class="text-orange-500">!</span><span>${escapeHtml(t)}</span></li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// --- 工具 ---
|
||||
function escapeHtml(s) {
|
||||
const div = document.createElement('div');
|
||||
|
||||
Reference in New Issue
Block a user