feat: 智能补充参数功能 - 编辑已有产品时上传图片/文本补充缺失字段
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user