feat: 新增产品管理模块 + AI基于产品详情生成范式对话 + 一键远程部署脚本
- schema.sql: 新增 product 表(positioning/selling_points) + deal 表加 product_id + 序列重置 + ALTER TABLE 迁移 - server.py: 产品 CRUD API + deal 关联 product_id + 仪表盘产品线统计 + 范式对话动态读取产品详情 - index.html: 产品管理页面 + 录入成交产品下拉 + 仪表盘增强 + 范式对话产品选择 + 导航通用化 - run_demo.sh: python 改 python3 + 预置成交关联 product_id - deploy.sh: 一键远程部署脚本(rsync+ssh+重启) - deploy_remote.sh: 远程数据库迁移脚本
This commit is contained in:
+333
-17
@@ -17,8 +17,9 @@ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-
|
||||
|
||||
<nav class="bg-white border-b shadow-sm sticky top-0 z-50">
|
||||
<div class="max-w-7xl mx-auto px-4 flex items-center h-14 gap-6">
|
||||
<span class="font-bold text-lg text-green-600">益童宝销售管理系统</span>
|
||||
<span class="font-bold text-lg text-green-600">微信销售管理系统</span>
|
||||
<button onclick="showPanel('dashboard')" class="nav-btn text-sm hover:text-green-600">仪表盘</button>
|
||||
<button onclick="showPanel('products')" class="nav-btn text-sm hover:text-green-600">产品管理</button>
|
||||
<button onclick="showPanel('salespersons')" class="nav-btn text-sm hover:text-green-600">销售列表</button>
|
||||
<button onclick="showPanel('customers')" class="nav-btn text-sm hover:text-green-600">客户列表</button>
|
||||
<button onclick="showPanel('deals')" class="nav-btn text-sm hover:text-green-600">成交记录</button>
|
||||
@@ -33,14 +34,99 @@ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-
|
||||
<div id="dashboard-panel" class="panel">
|
||||
<h2 class="text-xl font-bold mb-4">仪表盘</h2>
|
||||
<div id="dashboard-cards" class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6"></div>
|
||||
<h3 class="text-lg font-semibold mb-3">各销售数据对比</h3>
|
||||
<div id="dashboard-sales" class="bg-white rounded-lg shadow p-4 overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead><tr class="border-b text-left text-gray-500">
|
||||
<th class="py-2">销售</th><th>团队</th><th>消息数</th><th>客户数</th><th>成交金额</th><th>最后同步</th>
|
||||
</tr></thead>
|
||||
<tbody id="dashboard-sales-tbody"></tbody>
|
||||
</table>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold mb-3">各销售数据对比</h3>
|
||||
<div id="dashboard-sales" class="bg-white rounded-lg shadow p-4 overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead><tr class="border-b text-left text-gray-500">
|
||||
<th class="py-2">销售</th><th>团队</th><th>消息数</th><th>客户数</th><th>成交金额</th><th>最后同步</th>
|
||||
</tr></thead>
|
||||
<tbody id="dashboard-sales-tbody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold mb-3">本月产品线销售统计</h3>
|
||||
<div id="dashboard-product-stats" class="bg-white rounded-lg shadow p-4">
|
||||
<table class="w-full text-sm">
|
||||
<thead><tr class="border-b text-left text-gray-500">
|
||||
<th class="py-2">产品线</th><th>成交笔数</th><th>成交金额</th>
|
||||
</tr></thead>
|
||||
<tbody id="dashboard-product-tbody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 产品管理 -->
|
||||
<div id="products-panel" class="panel hidden">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-xl font-bold">产品管理</h2>
|
||||
<button onclick="showProductForm()" class="bg-green-600 text-white px-4 py-2 rounded text-sm hover:bg-green-700">+ 新增产品</button>
|
||||
</div>
|
||||
<div class="flex gap-4 mb-4">
|
||||
<select id="product-filter-category" onchange="loadProducts()" class="border rounded px-3 py-1.5 text-sm">
|
||||
<option value="">全部产品线</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="products-list" class="space-y-3"></div>
|
||||
</div>
|
||||
|
||||
<!-- 产品新增/编辑表单 -->
|
||||
<div id="product-form-panel" class="panel hidden">
|
||||
<button onclick="showPanel('products')" class="text-sm text-green-600 mb-3">← 返回产品管理</button>
|
||||
<h2 id="product-form-title" class="text-xl font-bold mb-4">新增产品</h2>
|
||||
<div class="bg-white rounded-lg shadow p-6 max-w-2xl">
|
||||
<form onsubmit="submitProduct(event)" class="space-y-4">
|
||||
<input type="hidden" id="product-form-id">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">产品名称 *</label>
|
||||
<input id="product-name" type="text" required placeholder="如:益童宝益生菌" class="w-full border rounded px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">产品线 *</label>
|
||||
<input id="product-category" type="text" required placeholder="如:儿童益生菌" class="w-full border rounded px-3 py-2 text-sm">
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">规格</label>
|
||||
<input id="product-spec" type="text" placeholder="如:3盒套餐" class="w-full border rounded px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">价格</label>
|
||||
<input id="product-price" type="number" step="0.01" placeholder="798" class="w-full border rounded px-3 py-2 text-sm">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">产品定位</label>
|
||||
<input id="product-positioning" type="text" placeholder="如:进口菌株儿童抗过敏专家" class="w-full border rounded px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">目标人群</label>
|
||||
<input id="product-target-audience" type="text" placeholder="如:3-12岁儿童" class="w-full border rounded px-3 py-2 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">产品详情描述</label>
|
||||
<textarea id="product-description" rows="4" placeholder="详细描述产品功效、成分、用法等,AI 将以此为基础生成销售话术" class="w-full border rounded px-3 py-2 text-sm"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">核心卖点(每行一条)</label>
|
||||
<textarea id="product-selling-points" rows="5" placeholder="每行一条卖点,如:\n丹麦进口菌株,品质有保障\n临床验证改善率超85%" class="w-full border rounded px-3 py-2 text-sm"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">状态</label>
|
||||
<select id="product-status" class="w-full border rounded px-3 py-2 text-sm">
|
||||
<option value="active">上架</option>
|
||||
<option value="inactive">下架</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="bg-green-600 text-white px-6 py-2 rounded text-sm hover:bg-green-700">保存</button>
|
||||
<span id="product-result" class="ml-4 text-sm text-green-600"></span>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -102,6 +188,11 @@ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-
|
||||
<!-- 成交记录列表 -->
|
||||
<div id="deals-panel" class="panel hidden">
|
||||
<h2 class="text-xl font-bold mb-4">成交记录</h2>
|
||||
<div class="flex gap-4 mb-4">
|
||||
<select id="deals-filter-product" onchange="loadDeals()" class="border rounded px-3 py-1.5 text-sm">
|
||||
<option value="">全部产品</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow p-4 overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead><tr class="border-b text-left text-gray-500">
|
||||
@@ -141,11 +232,14 @@ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-
|
||||
<!-- 标准范式视图 -->
|
||||
<div id="analysis-paradigm-view" class="hidden">
|
||||
<div class="bg-white rounded-lg shadow p-4 mb-4">
|
||||
<p class="text-sm text-gray-500 mb-2">选择一个客户,基于其真实对话生成标准范式对话模板</p>
|
||||
<p class="text-sm text-gray-500 mb-2">选择客户和产品,基于产品详情和真实对话生成标准范式对话模板</p>
|
||||
<div class="flex gap-3 items-center">
|
||||
<select id="paradigm-customer-select" class="border rounded px-3 py-2 text-sm flex-1">
|
||||
<option value="">请选择客户</option>
|
||||
</select>
|
||||
<select id="paradigm-product-select" class="border rounded px-3 py-2 text-sm flex-1">
|
||||
<option value="">自动推断产品</option>
|
||||
</select>
|
||||
<button onclick="generateParadigm()" id="paradigm-btn" class="bg-green-600 text-white px-6 py-2 rounded text-sm hover:bg-green-700">生成范式对话</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -171,8 +265,10 @@ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">产品名称 *</label>
|
||||
<input id="deal-product" type="text" required placeholder="如:益童宝3盒套餐" class="w-full border rounded px-3 py-2 text-sm">
|
||||
<label class="block text-sm font-medium mb-1">产品 *</label>
|
||||
<select id="deal-product" required class="w-full border rounded px-3 py-2 text-sm" onchange="onProductSelected()">
|
||||
<option value="">请选择</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">金额 *</label>
|
||||
@@ -212,6 +308,7 @@ function showPanel(name) {
|
||||
const panel = document.getElementById(name + '-panel');
|
||||
if (panel) panel.classList.remove('hidden');
|
||||
if (name === 'dashboard') loadDashboard();
|
||||
if (name === 'products') loadProducts();
|
||||
if (name === 'salespersons') loadSalespersons();
|
||||
if (name === 'customers') loadCustomers();
|
||||
if (name === 'deals') loadDeals();
|
||||
@@ -253,6 +350,171 @@ async function loadDashboard() {
|
||||
<td class="text-gray-400 text-xs">${s.last_synced_at ? new Date(s.last_synced_at).toLocaleString('zh-CN') : '-'}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
const productTbody = document.getElementById('dashboard-product-tbody');
|
||||
const productStats = data.product_stats || [];
|
||||
if (productStats.length === 0) {
|
||||
productTbody.innerHTML = '<tr><td colspan="3" class="py-3 text-center text-gray-400 text-sm">暂无数据</td></tr>';
|
||||
} else {
|
||||
productTbody.innerHTML = productStats.map(p => `
|
||||
<tr class="border-b hover:bg-gray-50">
|
||||
<td class="py-2 font-medium">${p.category}</td>
|
||||
<td>${p.deal_count}</td>
|
||||
<td class="font-bold text-green-600">${formatYuan(p.amount)}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
}
|
||||
|
||||
// --- 产品管理 ---
|
||||
let productsData = [];
|
||||
|
||||
async function loadProducts() {
|
||||
const category = document.getElementById('product-filter-category').value;
|
||||
let path = '/products?';
|
||||
if (category) path += `category=${encodeURIComponent(category)}&`;
|
||||
try {
|
||||
productsData = await api(path);
|
||||
renderProducts();
|
||||
populateProductFilterCategory();
|
||||
} catch (e) {
|
||||
document.getElementById('products-list').innerHTML = `<p class="text-sm text-red-500">加载失败: ${e.message}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
function populateProductFilterCategory() {
|
||||
const sel = document.getElementById('product-filter-category');
|
||||
if (sel.options.length > 1) return;
|
||||
const categories = [...new Set(productsData.map(p => p.category))];
|
||||
categories.forEach(c => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = c; opt.textContent = c;
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
}
|
||||
|
||||
function renderProducts() {
|
||||
const container = document.getElementById('products-list');
|
||||
if (productsData.length === 0) {
|
||||
container.innerHTML = '<p class="text-sm text-gray-400">暂无产品,点击右上角新增</p>';
|
||||
return;
|
||||
}
|
||||
const grouped = {};
|
||||
productsData.forEach(p => {
|
||||
if (!grouped[p.category]) grouped[p.category] = [];
|
||||
grouped[p.category].push(p);
|
||||
});
|
||||
container.innerHTML = Object.entries(grouped).map(([category, items]) => `
|
||||
<div class="bg-white rounded-lg shadow p-4">
|
||||
<h3 class="font-semibold text-sm mb-3 text-green-700">${category}</h3>
|
||||
<div class="space-y-3">
|
||||
${items.map(p => `
|
||||
<div class="border rounded-lg p-4 ${p.status === 'inactive' ? 'opacity-50' : ''}">
|
||||
<div class="flex items-start justify-between mb-2">
|
||||
<div>
|
||||
<span class="font-medium">${p.name}</span>
|
||||
${p.spec ? `<span class="text-gray-400 text-sm ml-2">${p.spec}</span>` : ''}
|
||||
${p.price ? `<span class="text-green-600 font-bold text-sm ml-2">¥${p.price.toFixed(2)}</span>` : ''}
|
||||
${p.status === 'inactive' ? '<span class="ml-2 px-2 py-0.5 rounded text-xs bg-gray-200 text-gray-600">已下架</span>' : ''}
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="editProduct(${p.id})" class="text-xs text-blue-600 hover:underline">编辑</button>
|
||||
<button onclick="deleteProduct(${p.id}, '${p.name}')" class="text-xs text-red-500 hover:underline">${p.status === 'inactive' ? '删除' : '下架'}</button>
|
||||
</div>
|
||||
</div>
|
||||
${p.positioning ? `<p class="text-xs text-gray-500 mb-1">定位:${p.positioning}</p>` : ''}
|
||||
${p.target_audience ? `<p class="text-xs text-gray-500 mb-1">目标人群:${p.target_audience}</p>` : ''}
|
||||
${p.description ? `<p class="text-sm text-gray-600 mb-2">${p.description}</p>` : ''}
|
||||
${(p.selling_points || []).length > 0 ? `
|
||||
<div class="mb-2">
|
||||
<span class="text-xs font-medium text-green-700">核心卖点:</span>
|
||||
<ul class="mt-1 space-y-0.5">
|
||||
${(p.selling_points || []).map(sp => `<li class="text-xs text-gray-600 flex gap-1"><span class="text-green-500">▸</span><span>${escapeHtml(sp)}</span></li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="flex gap-4 text-xs text-gray-400 border-t pt-2 mt-2">
|
||||
<span>成交笔数:${p.deal_count}</span>
|
||||
<span>成交总额:${formatYuan(p.total_amount)}</span>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function showProductForm() {
|
||||
document.getElementById('product-form-title').textContent = '新增产品';
|
||||
document.getElementById('product-form-id').value = '';
|
||||
document.getElementById('product-name').value = '';
|
||||
document.getElementById('product-category').value = '';
|
||||
document.getElementById('product-spec').value = '';
|
||||
document.getElementById('product-price').value = '';
|
||||
document.getElementById('product-positioning').value = '';
|
||||
document.getElementById('product-target-audience').value = '';
|
||||
document.getElementById('product-description').value = '';
|
||||
document.getElementById('product-selling-points').value = '';
|
||||
document.getElementById('product-status').value = 'active';
|
||||
document.getElementById('product-result').textContent = '';
|
||||
showPanel('product-form');
|
||||
}
|
||||
|
||||
function editProduct(id) {
|
||||
const p = productsData.find(x => x.id === id);
|
||||
if (!p) return;
|
||||
document.getElementById('product-form-title').textContent = '编辑产品';
|
||||
document.getElementById('product-form-id').value = p.id;
|
||||
document.getElementById('product-name').value = p.name || '';
|
||||
document.getElementById('product-category').value = p.category || '';
|
||||
document.getElementById('product-spec').value = p.spec || '';
|
||||
document.getElementById('product-price').value = p.price || '';
|
||||
document.getElementById('product-positioning').value = p.positioning || '';
|
||||
document.getElementById('product-target-audience').value = p.target_audience || '';
|
||||
document.getElementById('product-description').value = p.description || '';
|
||||
document.getElementById('product-selling-points').value = (p.selling_points || []).join('\n');
|
||||
document.getElementById('product-status').value = p.status || 'active';
|
||||
document.getElementById('product-result').textContent = '';
|
||||
showPanel('product-form');
|
||||
}
|
||||
|
||||
async function submitProduct(event) {
|
||||
event.preventDefault();
|
||||
const id = document.getElementById('product-form-id').value;
|
||||
const sellingPointsText = document.getElementById('product-selling-points').value.trim();
|
||||
const sellingPoints = sellingPointsText ? sellingPointsText.split('\n').map(s => s.trim()).filter(s => s) : null;
|
||||
const body = {
|
||||
name: document.getElementById('product-name').value,
|
||||
category: document.getElementById('product-category').value,
|
||||
spec: document.getElementById('product-spec').value || null,
|
||||
price: parseFloat(document.getElementById('product-price').value) || null,
|
||||
positioning: document.getElementById('product-positioning').value || null,
|
||||
target_audience: document.getElementById('product-target-audience').value || null,
|
||||
description: document.getElementById('product-description').value || null,
|
||||
selling_points: sellingPoints,
|
||||
status: document.getElementById('product-status').value,
|
||||
};
|
||||
try {
|
||||
const result = await api(`/products${id ? '/' + id : ''}`, {
|
||||
method: id ? 'PUT' : 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
document.getElementById('product-result').textContent = '✓ ' + result.message;
|
||||
setTimeout(() => showPanel('products'), 800);
|
||||
} catch (e) {
|
||||
document.getElementById('product-result').textContent = '✗ 失败: ' + e.message;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteProduct(id, name) {
|
||||
if (!confirm(`确定要下架产品「${name}」吗?`)) return;
|
||||
try {
|
||||
await api(`/products/${id}`, { method: 'DELETE' });
|
||||
await loadProducts();
|
||||
} catch (e) {
|
||||
alert('操作失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// --- 销售列表 ---
|
||||
@@ -417,8 +679,21 @@ async function loadCustomerDeals() {
|
||||
|
||||
// --- 成交记录列表 ---
|
||||
async function loadDeals() {
|
||||
const data = await api('/deals');
|
||||
const productId = document.getElementById('deals-filter-product').value;
|
||||
let path = '/deals?';
|
||||
if (productId) path += `product_id=${productId}&`;
|
||||
const data = await api(path);
|
||||
const tbody = document.getElementById('deals-tbody');
|
||||
// 填充产品筛选
|
||||
const productFilter = document.getElementById('deals-filter-product');
|
||||
if (productFilter.options.length <= 1) {
|
||||
const products = await api('/products');
|
||||
products.forEach(p => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = p.id; opt.textContent = `${p.name} ${p.spec || ''}`;
|
||||
productFilter.appendChild(opt);
|
||||
});
|
||||
}
|
||||
if (data.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="py-4 text-center text-gray-400">暂无成交记录</td></tr>';
|
||||
return;
|
||||
@@ -445,6 +720,18 @@ async function loadDealForm() {
|
||||
opt.value = s.id; opt.textContent = s.name;
|
||||
spSelect.appendChild(opt);
|
||||
});
|
||||
// 加载产品列表到下拉
|
||||
const productSelect = document.getElementById('deal-product');
|
||||
productSelect.innerHTML = '<option value="">请选择</option>';
|
||||
const products = await api('/products?status=active');
|
||||
products.forEach(p => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = p.id;
|
||||
opt.textContent = `${p.name} ${p.spec || ''} (¥${p.price ? p.price.toFixed(2) : '-'})`;
|
||||
opt.dataset.price = p.price || '';
|
||||
opt.dataset.name = `${p.name} ${p.spec || ''}`.trim();
|
||||
productSelect.appendChild(opt);
|
||||
});
|
||||
document.getElementById('deal-date').value = new Date().toISOString().slice(0, 10);
|
||||
document.getElementById('deal-result').textContent = '';
|
||||
}
|
||||
@@ -463,12 +750,25 @@ async function loadDealCustomers() {
|
||||
});
|
||||
}
|
||||
|
||||
function onProductSelected() {
|
||||
const select = document.getElementById('deal-product');
|
||||
const selectedOpt = select.options[select.selectedIndex];
|
||||
if (selectedOpt && selectedOpt.dataset.price) {
|
||||
document.getElementById('deal-amount').value = selectedOpt.dataset.price;
|
||||
}
|
||||
}
|
||||
|
||||
async function submitDeal(event) {
|
||||
event.preventDefault();
|
||||
const productSelect = document.getElementById('deal-product');
|
||||
const selectedOpt = productSelect.options[productSelect.selectedIndex];
|
||||
const productId = parseInt(productSelect.value);
|
||||
const productName = selectedOpt ? selectedOpt.dataset.name || selectedOpt.textContent : '';
|
||||
const body = {
|
||||
salesperson_id: parseInt(document.getElementById('deal-salesperson').value),
|
||||
customer_id: parseInt(document.getElementById('deal-customer').value),
|
||||
product_name: document.getElementById('deal-product').value,
|
||||
product_id: productId || null,
|
||||
product_name: productName,
|
||||
amount: parseFloat(document.getElementById('deal-amount').value),
|
||||
deal_date: document.getElementById('deal-date').value,
|
||||
notes: document.getElementById('deal-notes').value || null,
|
||||
@@ -480,7 +780,7 @@ async function submitDeal(event) {
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
document.getElementById('deal-result').textContent = '✓ 已创建,ID: ' + result.id;
|
||||
document.getElementById('deal-product').value = '';
|
||||
productSelect.value = '';
|
||||
document.getElementById('deal-amount').value = '';
|
||||
document.getElementById('deal-notes').value = '';
|
||||
} catch (e) {
|
||||
@@ -596,6 +896,18 @@ function populateParadigmSelect() {
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
if (currentVal) sel.value = currentVal;
|
||||
// 填充产品选择
|
||||
const productSel = document.getElementById('paradigm-product-select');
|
||||
if (productSel && productSel.options.length <= 1) {
|
||||
api('/products?status=active').then(products => {
|
||||
products.forEach(p => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = p.id;
|
||||
opt.textContent = `${p.name} ${p.spec || ''}`;
|
||||
productSel.appendChild(opt);
|
||||
});
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
async function generateParadigm() {
|
||||
@@ -610,7 +922,10 @@ async function generateParadigm() {
|
||||
btn.textContent = '生成中...';
|
||||
result.innerHTML = '<p class="text-sm text-gray-400">正在调用千问 LLM 生成标准范式对话,请稍候...</p>';
|
||||
try {
|
||||
const data = await api(`/conversations/${customerId}/paradigm`, { method: 'POST' });
|
||||
const productId = document.getElementById('paradigm-product-select').value;
|
||||
let paradigmPath = `/conversations/${customerId}/paradigm`;
|
||||
if (productId) paradigmPath += `?product_id=${productId}`;
|
||||
const data = await api(paradigmPath, { method: 'POST' });
|
||||
renderParadigm(data);
|
||||
} catch (e) {
|
||||
result.innerHTML = `<p class="text-sm text-red-500">生成失败: ${e.message}</p>`;
|
||||
@@ -632,7 +947,8 @@ function renderParadigm(data) {
|
||||
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-4">客户类型: ${data.customer_type || '-'}</p>
|
||||
<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) => `
|
||||
|
||||
Reference in New Issue
Block a user