deploy: 2026-07-15 15:02 部署更新

This commit is contained in:
selfrelease
2026-07-15 15:02:31 +08:00
parent 8166dfa745
commit 8f10b9fb62
+94 -97
View File
@@ -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');