- 更新了 AI 设置页面,统一了 AI 补充、自然语言搜索、入站预处理和标签标准化的配置。 - 在编辑页面新增了 AI 标签和笔记标准化板块,包括建议预览及应用功能。 - 改进的搜索页面,支持带有示例的自然语言查询和模糊选择下拉菜单。 - 增强的搜索结果显示,包含更多匹配信息和查看 AI 搜索过程的模态。 - 更新新组件样式,优化布局以提升用户体验。
266 lines
10 KiB
HTML
266 lines
10 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>编辑元件</title>
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||
</head>
|
||
<body>
|
||
<header class="hero slim">
|
||
<div>
|
||
<h1>{{ box.name }} - 编号 {{ slot_code }}</h1>
|
||
<p>步骤: 填写核心字段 -> 检查数量 -> 保存</p>
|
||
</div>
|
||
<div class="hero-actions">
|
||
<a class="btn btn-light" href="{{ url_for('search_page', q=search_query) if search_query else url_for('search_page') }}">返回快速搜索</a>
|
||
<a class="btn btn-light" href="{{ url_for('stats_page') }}">统计页</a>
|
||
<a class="btn btn-light" href="{{ url_for('view_box', box_id=box.id) }}">返回宫格</a>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="container">
|
||
{% if error %}
|
||
<p class="alert">{{ error }}</p>
|
||
{% endif %}
|
||
{% if notice %}
|
||
<p class="notice">{{ notice }}</p>
|
||
{% endif %}
|
||
{% if lock_storage_mode %}
|
||
<p class="notice">当前为锁仓模式: 禁止删除位置绑定,禁止替换当前位置料号。</p>
|
||
{% endif %}
|
||
|
||
<div class="entry-shell">
|
||
<section class="entry-main">
|
||
<form class="panel form-grid" method="post">
|
||
<input type="hidden" name="q" value="{{ search_query or '' }}">
|
||
<label>
|
||
料号 *
|
||
<input id="part-no-input" type="text" name="part_no" required value="{{ component.part_no if component else '' }}" aria-label="料号" placeholder="如 STM32F103C8T6">
|
||
</label>
|
||
<label>
|
||
名称 *
|
||
<input id="name-input" type="text" name="name" required value="{{ component.name if component else '' }}" aria-label="名称" placeholder="如 MCU STM32F103C8T6">
|
||
</label>
|
||
<label>
|
||
规格
|
||
<input id="specification-input" type="text" name="specification" value="{{ component.specification if component else '' }}" placeholder="如 Cortex-M3 / LQFP-48">
|
||
</label>
|
||
<label>
|
||
数量
|
||
<input type="number" name="quantity" min="0" value="{{ component.quantity if component else 0 }}">
|
||
</label>
|
||
<label class="full">
|
||
备注
|
||
<textarea id="note-input" name="note" rows="3" placeholder="如 LCSC item 9243">{{ component.note if component else '' }}</textarea>
|
||
</label>
|
||
<label class="full">
|
||
<input type="checkbox" name="confirm_merge" value="1">
|
||
人工确认后合并: 若检测到同料号或同参数物料,保存时将数量合并到已存在位置
|
||
</label>
|
||
<label class="full">
|
||
<input type="checkbox" name="confirm_position_change" value="1">
|
||
我确认替换当前位物料(会改变该位置原有绑定)
|
||
</label>
|
||
|
||
<div class="actions full">
|
||
<button class="btn" type="submit" name="action" value="save">保存</button>
|
||
{% if component %}
|
||
{% if component.is_enabled %}
|
||
<button class="btn btn-light" type="submit" name="action" value="toggle_disable">停用</button>
|
||
{% else %}
|
||
<button class="btn btn-light" type="submit" name="action" value="toggle_enable">启用</button>
|
||
{% endif %}
|
||
<label class="full">
|
||
删除确认(输入当前位置编号 {{ slot_code }})
|
||
<input type="text" name="delete_confirm_slot" placeholder="请输入 {{ slot_code }}">
|
||
</label>
|
||
<button class="btn btn-danger" type="submit" name="action" value="delete" onclick="return confirm('确认删除这个元件记录吗?')">删除</button>
|
||
{% endif %}
|
||
</div>
|
||
</form>
|
||
</section>
|
||
|
||
<aside class="entry-sidebar">
|
||
<section class="panel quick-inbound-panel">
|
||
<h2>AI 标签与备注标准化</h2>
|
||
<p class="hint">生成更适合标签打印的短名称,并自动补全统一搜索关键词。确认后再回填到表单。</p>
|
||
<p class="hint" id="standardize-status" aria-live="polite"></p>
|
||
<section class="ai-standardize-preview" id="standardize-preview" hidden>
|
||
<div class="standardize-grid">
|
||
<div>
|
||
<strong>短标签</strong>
|
||
<p id="standardize-short-label">-</p>
|
||
</div>
|
||
<div>
|
||
<strong>建议名称</strong>
|
||
<p id="standardize-name">-</p>
|
||
</div>
|
||
<div>
|
||
<strong>建议规格</strong>
|
||
<p id="standardize-specification">-</p>
|
||
</div>
|
||
<div class="full">
|
||
<strong>建议备注</strong>
|
||
<p id="standardize-note">-</p>
|
||
</div>
|
||
<div class="full">
|
||
<strong>搜索关键词</strong>
|
||
<div class="match-tags" id="standardize-keywords"></div>
|
||
</div>
|
||
</div>
|
||
<div class="actions">
|
||
<button class="btn" type="button" id="apply-standardization-btn">应用到表单</button>
|
||
</div>
|
||
</section>
|
||
<div class="actions">
|
||
<button class="btn btn-light" type="button" id="generate-standardization-btn">生成标准化建议</button>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel quick-inbound-panel">
|
||
<h2>立创编号入库</h2>
|
||
<p class="hint">当前编辑位置: {{ slot_code }}。仅支持粘贴立创商品详情页链接,系统会自动提取 itemId 并查询。</p>
|
||
<form class="form-grid" method="post" action="{{ url_for('lcsc_import_to_edit_slot', box_id=box.id, slot=slot) }}">
|
||
<label>
|
||
立创商品详情页链接
|
||
<input type="text" name="lcsc_product_id" required placeholder="如 https://item.szlcsc.com/23913.html">
|
||
</label>
|
||
<label>
|
||
数量
|
||
<input type="number" name="quantity" min="0" value="0">
|
||
</label>
|
||
<label class="full">
|
||
<input type="checkbox" name="confirm_merge" value="1">
|
||
人工确认后合并: 若检测到同料号或同参数物料,将合并到已存在位置
|
||
</label>
|
||
<label class="full">
|
||
<input type="checkbox" name="confirm_position_change" value="1">
|
||
我确认替换当前位物料(会改变该位置原有绑定)
|
||
</label>
|
||
<div class="actions full">
|
||
<button class="btn" type="submit">拉取并写入当前位</button>
|
||
<a class="btn btn-light" href="{{ url_for('ai_settings_page') }}">接口参数</a>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
|
||
<section class="panel entry-guide">
|
||
<h2>轻量入库规范</h2>
|
||
<p class="hint">先保证可检索,再补充关键参数,不追求一次填很全。</p>
|
||
<ul class="guide-list">
|
||
<li>必填: 料号(part_no) + 名称(name) + 数量(quantity)</li>
|
||
<li>建议: 规格(specification)写 2-4 个关键参数</li>
|
||
<li>备注(note): 来源编号或链接,如 LCSC item 9243</li>
|
||
</ul>
|
||
<pre class="guide-code">料号: STM32F103C8T6
|
||
名称: MCU STM32F103C8T6
|
||
规格: Cortex-M3 / 64KB Flash / LQFP-48
|
||
数量: 10
|
||
备注: LCSC item 9243</pre>
|
||
</section>
|
||
</aside>
|
||
</div>
|
||
</main>
|
||
<script>
|
||
(function () {
|
||
var partNoInput = document.getElementById('part-no-input');
|
||
var nameInput = document.getElementById('name-input');
|
||
var specificationInput = document.getElementById('specification-input');
|
||
var noteInput = document.getElementById('note-input');
|
||
var generateBtn = document.getElementById('generate-standardization-btn');
|
||
var applyBtn = document.getElementById('apply-standardization-btn');
|
||
var status = document.getElementById('standardize-status');
|
||
var preview = document.getElementById('standardize-preview');
|
||
var shortLabelNode = document.getElementById('standardize-short-label');
|
||
var nameNode = document.getElementById('standardize-name');
|
||
var specificationNode = document.getElementById('standardize-specification');
|
||
var noteNode = document.getElementById('standardize-note');
|
||
var keywordNode = document.getElementById('standardize-keywords');
|
||
var latestSuggestion = null;
|
||
|
||
if (!partNoInput || !nameInput || !specificationInput || !noteInput || !generateBtn || !applyBtn || !status || !preview) {
|
||
return;
|
||
}
|
||
|
||
function escapeHtml(text) {
|
||
return String(text || '')
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
.replace(/'/g, ''');
|
||
}
|
||
|
||
function renderSuggestion(suggestion) {
|
||
latestSuggestion = suggestion;
|
||
preview.hidden = false;
|
||
shortLabelNode.textContent = suggestion.short_label || '-';
|
||
nameNode.textContent = suggestion.name || '-';
|
||
specificationNode.textContent = suggestion.specification || '-';
|
||
noteNode.textContent = suggestion.note || '-';
|
||
keywordNode.innerHTML = (suggestion.keywords || []).map(function (keyword) {
|
||
return '<span class="tag">' + escapeHtml(keyword) + '</span>';
|
||
}).join('') || '<span class="tag">-</span>';
|
||
}
|
||
|
||
generateBtn.addEventListener('click', function () {
|
||
if (!partNoInput.value.trim() && !nameInput.value.trim()) {
|
||
status.textContent = '请先填写料号或名称';
|
||
return;
|
||
}
|
||
|
||
generateBtn.disabled = true;
|
||
status.textContent = '正在生成标准化建议...';
|
||
|
||
var payload = new URLSearchParams();
|
||
payload.set('part_no', partNoInput.value || '');
|
||
payload.set('name', nameInput.value || '');
|
||
payload.set('specification', specificationInput.value || '');
|
||
payload.set('note', noteInput.value || '');
|
||
|
||
fetch('{{ url_for('ai_component_standardize') }}', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
|
||
},
|
||
body: payload.toString()
|
||
}).then(function (resp) {
|
||
return resp.json().then(function (data) {
|
||
if (!resp.ok || !data.ok) {
|
||
throw new Error(data.message || '生成失败');
|
||
}
|
||
return data;
|
||
});
|
||
}).then(function (data) {
|
||
renderSuggestion(data.suggestion || {});
|
||
status.textContent = data.parse_notice || '标准化建议已生成,可先预览再应用';
|
||
}).catch(function (error) {
|
||
status.textContent = '生成失败: ' + error.message;
|
||
}).finally(function () {
|
||
generateBtn.disabled = false;
|
||
});
|
||
});
|
||
|
||
applyBtn.addEventListener('click', function () {
|
||
if (!latestSuggestion) {
|
||
status.textContent = '请先生成标准化建议';
|
||
return;
|
||
}
|
||
|
||
if (latestSuggestion.name) {
|
||
nameInput.value = latestSuggestion.name;
|
||
}
|
||
if (latestSuggestion.specification) {
|
||
specificationInput.value = latestSuggestion.specification;
|
||
}
|
||
if (latestSuggestion.note) {
|
||
noteInput.value = latestSuggestion.note;
|
||
}
|
||
status.textContent = '建议已回填到表单,确认无误后再保存';
|
||
});
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|