feat: 智能补充参数功能 - 编辑已有产品时上传图片/文本补充缺失字段

This commit is contained in:
2026-04-28 10:09:58 +08:00
parent 438fba347a
commit a647179e72
4 changed files with 544 additions and 35 deletions

View File

@@ -299,6 +299,7 @@
<div id="modalContent" class="p-6"></div>
<div class="p-6 border-t flex justify-end gap-4 sticky bottom-0 bg-white">
<button onclick="closeModal()" class="px-4 py-2 bg-gray-200 text-gray-600 rounded-lg hover:bg-gray-300">取消</button>
<button onclick="openSmartUpdateModal()" id="smartUpdateBtn" class="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700 hidden"><i class="ri-magic-line mr-1"></i>智能补充</button>
<button onclick="saveItem()" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700">保存</button>
</div>
</div>
@@ -386,6 +387,57 @@
</div>
</div>
<!-- 智能补充弹窗 -->
<div id="smartUpdateModal" class="fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center">
<div class="bg-white rounded-xl max-w-4xl w-full mx-4 max-h-[90vh] overflow-auto">
<div class="p-6 border-b flex justify-between items-center sticky top-0 bg-white z-10">
<h2 class="text-xl font-bold text-gray-800"><i class="ri-magic-line mr-2 text-orange-600"></i>智能补充参数</h2>
<button onclick="closeSmartUpdateModal()" class="text-gray-400 hover:text-gray-600"><i class="ri-close-line text-2xl"></i></button>
</div>
<div class="p-6">
<div class="bg-blue-50 rounded-lg p-4 mb-4">
<p class="text-sm text-blue-700"><i class="ri-information-line mr-1"></i>上传图片或输入文本AI将识别参数并补充到现有数据中。只会填充缺失的字段不会覆盖已有值。</p>
</div>
<div class="mb-6">
<p class="text-sm text-gray-500 mb-3">上传产品图片AI将自动识别并解析参数</p>
<div class="flex flex-wrap gap-3 mb-3" id="smartUpdateImagePreviewArea">
<!-- 图片预览区 -->
</div>
<div class="flex gap-3">
<input type="file" id="smartUpdateImageInput" accept="image/*" multiple class="hidden" onchange="handleSmartUpdateImageUpload(event)">
<button onclick="document.getElementById('smartUpdateImageInput').click()" class="px-4 py-2 bg-orange-100 text-orange-600 rounded-lg hover:bg-orange-200 text-sm">
<i class="ri-image-add-line mr-1"></i>选择图片(支持多选)
</button>
<button onclick="pasteSmartUpdateImageFromClipboard()" class="px-4 py-2 bg-gray-100 text-gray-600 rounded-lg hover:bg-gray-200 text-sm">
<i class="ri-clipboard-line mr-1"></i>粘贴图片
</button>
<button onclick="clearSmartUpdateImages()" class="px-4 py-2 bg-gray-100 text-gray-600 rounded-lg hover:bg-gray-200 text-sm">
<i class="ri-delete-bin-line mr-1"></i>清空图片
</button>
</div>
<div class="mt-3 text-xs text-gray-400">
<i class="ri-information-line mr-1"></i>
已选择 <span id="smartUpdateImageCount">0</span> 张图片
</div>
</div>
<div class="border-t pt-4">
<label class="text-sm text-gray-600 mb-2 block">补充文本(可选)</label>
<textarea id="smartUpdateText" rows="4" class="w-full p-4 border border-gray-200 rounded-lg focus:outline-none focus:border-orange-400 text-gray-700" placeholder="可粘贴补充信息文本,如产品规格表、参数说明等..."></textarea>
</div>
<div id="smartUpdatePreview" class="mt-4 hidden">
<h3 class="text-sm font-semibold text-gray-700 mb-2"><i class="ri-checkbox-circle-line text-green-600 mr-1"></i>解析结果:</h3>
<div class="bg-gray-50 rounded-lg p-4 text-sm text-gray-600" id="smartUpdateResult">
<!-- 解析结果显示 -->
</div>
</div>
</div>
<div class="p-6 border-t flex justify-end gap-4 sticky bottom-0 bg-white">
<button onclick="closeSmartUpdateModal()" class="px-4 py-2 bg-gray-200 text-gray-600 rounded-lg hover:bg-gray-300">取消</button>
<button onclick="smartUpdateSubmit()" id="smartUpdateSubmitBtn" class="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700"><i class="ri-magic-line mr-1"></i>解析并补充</button>
</div>
</div>
</div>
<script>
let currentType = '';
let currentId = '';
@@ -880,6 +932,7 @@
document.getElementById('modalTitle').textContent = '添加' + titles[type];
const forms = {category: getCategoryForm, model: getModelForm, gpu: getGpuForm, cpu: getCpuForm, knowledge: getKnowledgeForm, dynamic: getDynamicForm};
document.getElementById('modalContent').innerHTML = forms[type]();
showSmartUpdateButton(); // 添加时隐藏智能补充按钮
document.getElementById('editModal').classList.remove('hidden');
}
@@ -894,6 +947,7 @@
document.getElementById('modalTitle').textContent = '编辑' + titles[type];
const forms = {category: getCategoryForm, model: getModelForm, gpu: getGpuForm, cpu: getCpuForm, knowledge: getKnowledgeForm};
document.getElementById('modalContent').innerHTML = forms[type](currentData);
showSmartUpdateButton(); // 显示智能补充按钮
document.getElementById('editModal').classList.remove('hidden');
}
@@ -905,6 +959,7 @@
currentData = await res.json();
document.getElementById('modalTitle').textContent = '编辑数据';
document.getElementById('modalContent').innerHTML = getDynamicForm(currentData);
showSmartUpdateButton(); // 显示智能补充按钮
document.getElementById('editModal').classList.remove('hidden');
}
@@ -1413,6 +1468,7 @@
document.getElementById('smartAddModal').addEventListener('click', function(e) { if (e.target === this) closeSmartAddModal(); });
document.getElementById('rawDataModal').addEventListener('click', function(e) { if (e.target === this) closeRawDataModal(); });
document.getElementById('subcategoryModal').addEventListener('click', function(e) { if (e.target === this) closeSubcategoryModal(); });
document.getElementById('smartUpdateModal').addEventListener('click', function(e) { if (e.target === this) closeSmartUpdateModal(); });
// ============ 智能添加功能 ============
@@ -1741,6 +1797,209 @@
document.getElementById('rawDataModal').classList.add('hidden');
}
// ============ 智能补充功能 ============
let smartUpdateImages = [];
// 打开智能补充弹框(编辑时显示按钮)
function showSmartUpdateButton() {
const btn = document.getElementById('smartUpdateBtn');
// 只有编辑已有数据时才显示智能补充按钮排除category和knowledge
if (currentId && currentType && currentType !== 'category' && currentType !== 'knowledge') {
btn.classList.remove('hidden');
} else {
btn.classList.add('hidden');
}
}
// 打开智能补充弹框
function openSmartUpdateModal() {
smartUpdateImages = [];
document.getElementById('smartUpdateText').value = '';
document.getElementById('smartUpdatePreview').classList.add('hidden');
document.getElementById('smartUpdateImagePreviewArea').innerHTML = '';
document.getElementById('smartUpdateImageCount').textContent = '0';
document.getElementById('smartUpdateModal').classList.remove('hidden');
}
function closeSmartUpdateModal() {
document.getElementById('smartUpdateModal').classList.add('hidden');
}
// 处理图片上传
async function handleSmartUpdateImageUpload(event) {
const files = event.target.files;
for (let file of files) {
const formData = new FormData();
formData.append('file', file);
try {
const res = await fetch('/api/upload/image', {
method: 'POST',
body: formData
});
const data = await res.json();
if (data.success) {
smartUpdateImages.push(data.url);
updateSmartUpdateImagePreview();
}
} catch (e) {
alert('上传失败: ' + e.message);
}
}
event.target.value = '';
}
// 粘贴图片
async function pasteSmartUpdateImageFromClipboard() {
try {
if (!navigator.clipboard || !navigator.clipboard.read) {
alert('剪贴板API需要HTTPS或localhost环境。\n当前访问地址不支持请使用文件选择上传。\n\n可改用 localhost:19010 访问来支持粘贴功能。');
return;
}
const clipboardItems = await navigator.clipboard.read();
let found = false;
for (const item of clipboardItems) {
for (const type of item.types) {
if (type.startsWith('image/')) {
found = true;
const blob = await item.getType(type);
const reader = new FileReader();
reader.onload = async (e) => {
const base64 = e.target.result;
try {
const res = await fetch('/api/upload/image/base64', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ image: base64 })
});
const data = await res.json();
if (data.success) {
smartUpdateImages.push(data.url);
updateSmartUpdateImagePreview();
}
} catch (err) {
alert('上传失败: ' + err.message);
}
};
reader.readAsDataURL(blob);
}
}
}
if (!found) {
alert('剪贴板中没有图片,请先复制一张图片');
}
} catch (e) {
if (e.name === 'NotAllowedError') {
alert('浏览器拒绝访问剪贴板。\n请使用文件选择上传或改用 localhost:19010 访问。');
} else {
alert('无法从剪贴板获取图片: ' + e.message + '\n请使用文件选择上传');
}
}
}
// 清空图片
function clearSmartUpdateImages() {
smartUpdateImages = [];
updateSmartUpdateImagePreview();
}
// 移除单张图片
function removeSmartUpdateImage(index) {
smartUpdateImages.splice(index, 1);
updateSmartUpdateImagePreview();
}
// 更新图片预览
function updateSmartUpdateImagePreview() {
const area = document.getElementById('smartUpdateImagePreviewArea');
const count = document.getElementById('smartUpdateImageCount');
area.innerHTML = smartUpdateImages.map((url, idx) => `
<div class="relative w-24 h-24 border rounded-lg overflow-hidden group">
<img src="${url}" class="w-full h-full object-cover">
<button onclick="removeSmartUpdateImage(${idx})" class="absolute top-1 right-1 w-6 h-6 bg-red-500 text-white rounded-full opacity-0 group-hover:opacity-100 transition flex items-center justify-center">
<i class="ri-close-line"></i>
</button>
</div>
`).join('');
count.textContent = smartUpdateImages.length;
}
// 执行智能补充
async function smartUpdateSubmit() {
const text = document.getElementById('smartUpdateText').value.trim();
if (!text && smartUpdateImages.length === 0) {
alert('请上传图片或输入文本');
return;
}
const btn = document.getElementById('smartUpdateSubmitBtn');
btn.disabled = true;
btn.innerHTML = '<i class="ri-loader-4-line animate-spin mr-1"></i>解析中...';
try {
let endpoint;
if (currentType === 'model') endpoint = `/api/models/${currentId}/smart-update`;
else if (currentType === 'gpu') endpoint = `/api/gpus/${currentId}/smart-update`;
else if (currentType === 'cpu') endpoint = `/api/cpus/${currentId}/smart-update`;
else if (currentType === 'dynamic') endpoint = `/api/items/${dynamicCategoryId}/${currentId}/smart-update`;
const res = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: text,
images: smartUpdateImages
})
});
const data = await res.json();
if (data.error) {
alert('解析失败: ' + data.error);
} else {
// 显示更新结果
document.getElementById('smartUpdatePreview').classList.remove('hidden');
let html = `<div class="mb-2 text-green-600"><i class="ri-checkbox-circle-fill mr-1"></i>成功补充 ${data.updated_fields.length} 个字段</div>`;
if (data.updated_fields.length > 0) {
html += '<div class="mt-2">';
data.updated_fields.forEach(field => {
html += `<span class="inline-block px-2 py-1 bg-green-100 text-green-700 rounded text-xs mr-1 mb-1">${field}</span>`;
});
html += '</div>';
} else {
html += '<div class="text-gray-500 mt-2">所有字段都已存在,无需补充</div>';
}
document.getElementById('smartUpdateResult').innerHTML = html;
// 更新编辑表单数据
currentData = data[currentType] || data.item || data.model || data.gpu || data.cpu;
const forms = {model: getModelForm, gpu: getGpuForm, cpu: getCpuForm, dynamic: getDynamicForm};
document.getElementById('modalContent').innerHTML = forms[currentType](currentData);
// 关闭智能补充弹框
setTimeout(() => {
closeSmartUpdateModal();
}, 1500);
}
} catch (e) {
alert('请求失败: ' + e.message);
}
btn.disabled = false;
btn.innerHTML = '<i class="ri-magic-line mr-1"></i>解析并补充';
}
// 监听编辑弹框打开,显示智能补充按钮
document.getElementById('editModal').addEventListener('showSmartUpdate', showSmartUpdateButton);
init();
</script>
</body>