feat:助手首次改版

This commit is contained in:
R524809
2026-08-07 17:34:24 +08:00
parent f88b424301
commit 2083ad1954
29 changed files with 3817 additions and 0 deletions
+225
View File
@@ -0,0 +1,225 @@
/* ================================================================
Ozon Seller Kit - 自定义组件样式
原先内联于 HTML 的 Tailwind @layer utilities,已展开为标准 CSS
(Play CDN 不处理外部文件里的 @apply)。配色与 js/tailwind.config.js
中的调色板保持一致。
================================================================ */
:root {
--color-primary: #3B82F6;
--color-secondary: #10B981;
--color-dark-light: #2D2D3F;
--color-warning: #F59E0B;
--color-danger: #EF4444;
--color-gray-700: #374151;
--shadow-primary-lg: 0 10px 15px -3px rgba(59, 130, 246, 0.1),
0 4px 6px -4px rgba(59, 130, 246, 0.1);
--neon: 0 0 5px rgba(59, 130, 246, 0.5), inset 0 0 5px rgba(59, 130, 246, 0.3);
}
/* 输入框聚焦态 */
.input-focus {
transition: all 0.2s ease;
}
.input-focus:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
}
/* 阴影 */
.card-shadow,
.panel-shadow {
box-shadow: var(--shadow-primary-lg);
}
/* 结果卡片 */
.result-card {
background-color: var(--color-dark-light);
border-radius: 0.75rem;
padding: 1.25rem;
border-color: var(--color-secondary);
transition: all 0.3s ease;
}
/* 警告 / 错误卡片 */
.warning-card,
.error-card {
background-color: var(--color-dark-light);
border-radius: 0.75rem;
padding: 1.25rem;
box-shadow: var(--shadow-primary-lg);
transition: all 0.3s ease;
}
.warning-card {
border-left: 4px solid var(--color-warning);
}
.error-card {
border-left: 4px solid var(--color-danger);
}
/* 文字发光 / 霓虹描边 */
.text-glow {
text-shadow: 0 0 10px rgba(59, 130, 246, 0.5);
}
.neon-border {
box-shadow: var(--neon);
}
/* 图片上传区 */
.upload-area {
border: 2px dashed var(--color-gray-700);
border-radius: 0.75rem;
padding: 2rem;
text-align: center;
cursor: pointer;
transition: border-color 0.2s ease;
}
.upload-area:hover {
border-color: var(--color-primary);
}
/* 预览图片 */
.preview-frame {
position: relative;
display: inline-block;
max-width: 100%;
}
.preview-img {
display: block;
max-width: 100%;
height: auto;
border-radius: 0.5rem;
object-fit: contain;
border: 1px solid var(--color-gray-700);
transition: box-shadow 0.2s ease;
}
.preview-img:hover {
box-shadow: var(--neon);
}
/* 已加水印:可拖动调整位置 */
.preview-img-draggable {
cursor: move;
touch-action: none;
}
/* 预览图右上角的单图操作按钮 */
.preview-actions {
position: absolute;
top: 0.375rem;
right: 0.375rem;
display: flex;
gap: 0.25rem;
}
.preview-action-btn {
padding: 0.2rem 0.5rem;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 0.375rem;
background: rgba(17, 17, 27, 0.75);
color: #e5e7eb;
font-size: 0.6875rem;
line-height: 1.5;
white-space: nowrap;
cursor: pointer;
transition: background-color 0.2s ease, color 0.2s ease;
}
.preview-action-btn:hover:not(:disabled) {
background: var(--color-primary);
color: #fff;
}
.preview-action-btn:disabled {
opacity: 0.6;
cursor: default;
}
/* 俄文文案:平铺只读文本 */
.ai-text-block {
white-space: pre-wrap;
word-break: break-word;
line-height: 1.7;
}
.ai-text-block:empty::before {
content: attr(data-placeholder);
color: #6b7280;
font-size: 0.875rem;
}
.ai-text-block-ru {
color: #f3f4f6;
font-size: 0.9375rem;
}
.ai-text-block-zh {
color: #9ca3af;
font-size: 0.875rem;
}
.ai-text-divider {
margin: 0.875rem 0;
border-top: 1px dashed var(--color-gray-700);
}
/* 俄文文案:推荐标题卡片 */
.ai-title-card {
padding: 0.75rem;
border: 1px solid var(--color-gray-700);
border-radius: 0.5rem;
background: var(--color-dark-light);
}
.ai-title-card-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
margin-bottom: 0.35rem;
}
.ai-title-card-index {
color: #9ca3af;
font-size: 0.75rem;
}
.ai-title-card-ru {
color: #f3f4f6;
font-size: 0.9375rem;
line-height: 1.5;
word-break: break-word;
}
.ai-title-card-zh {
margin-top: 0.35rem;
color: #9ca3af;
font-size: 0.8125rem;
line-height: 1.5;
word-break: break-word;
}
/* 俄文文案:标签芯片 */
.ai-tag-chip {
display: inline-flex;
align-items: center;
gap: 0.35rem;
max-width: 100%;
padding: 0.4rem 0.75rem;
border: 1px solid var(--color-gray-700);
border-radius: 9999px;
background: var(--color-dark-light);
color: var(--color-gray-100);
font-size: 0.875rem;
line-height: 1.3;
cursor: pointer;
transition: border-color 0.2s ease, background-color 0.2s ease, transform 0.15s ease;
}
.ai-tag-chip:hover {
border-color: var(--color-primary);
background: rgba(59, 130, 246, 0.12);
transform: translateY(-1px);
}
.ai-tag-chip-ru {
color: #f3f4f6;
font-weight: 500;
}
.ai-tag-chip-sep {
color: #6b7280;
}
.ai-tag-chip-zh {
color: #9ca3af;
}
/* 上品登记表:型号 / 重量 / 尺寸 默认隐藏 */
#historyTable:not(.show-optional-cols) .history-col-optional {
display: none;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+336
View File
@@ -0,0 +1,336 @@
// ================================================================
// Ozon Seller Kit - 俄文文案(调用本地 FastAPI → 大模型)
// ================================================================
(function () {
const API_BASE = window.location.origin;
const MODEL_STORAGE_KEY = 'ozonAiCopyModel';
const sourceTextEl = document.getElementById('aiSourceText');
const generateBtn = document.getElementById('aiGenerateBtn');
const toggleBtn = document.getElementById('aiToggleBtn');
const bodyEl = document.getElementById('aiCopyBody');
const statusEl = document.getElementById('aiCopyStatus');
const modelSelectEl = document.getElementById('aiModelSelect');
const contextProductNameEl = document.getElementById('aiContextProductName');
const contextModelCodeEl = document.getElementById('aiContextModelCode');
const titlesContainerEl = document.getElementById('aiTitlesContainer');
const tagsContainerEl = document.getElementById('aiTagsContainer');
const copyAllTagsBtn = document.getElementById('aiCopyAllTagsBtn');
const descRuEl = document.getElementById('aiDescRu');
const descZhEl = document.getElementById('aiDescZh');
const productNameInput = document.getElementById('productName');
const modelCodeInput = document.getElementById('modelCode');
let lastTagsRu = [];
if (!sourceTextEl || !generateBtn) {
return;
}
function setStatus(message, isError) {
if (!statusEl) return;
statusEl.textContent = message || '';
statusEl.className = isError
? 'text-sm text-danger'
: 'text-sm text-gray-500';
}
function syncContextHints() {
if (contextProductNameEl) {
const name = (productNameInput && productNameInput.value || '').trim();
contextProductNameEl.textContent = name || '--';
}
if (contextModelCodeEl) {
const model = (modelCodeInput && modelCodeInput.value || '').trim();
contextModelCodeEl.textContent = model || '--';
}
}
async function copyText(text) {
const value = (text || '').trim();
if (!value) {
setStatus('没有可复制的内容', true);
return;
}
try {
await navigator.clipboard.writeText(value);
setStatus('已复制');
} catch (err) {
setStatus('复制失败,请手动选择文本', true);
}
}
function toStringList(value) {
return Array.isArray(value)
? value.map(function (item) { return String(item).trim(); })
: [];
}
function renderTitles(titlesRu, titlesZh) {
if (!titlesContainerEl) return;
const ruList = toStringList(titlesRu).filter(Boolean);
const zhList = toStringList(titlesZh);
titlesContainerEl.innerHTML = '';
if (!ruList.length) {
const empty = document.createElement('p');
empty.className = 'text-sm text-gray-500';
empty.textContent = '生成后将在此展示 2 个推荐标题';
titlesContainerEl.appendChild(empty);
return;
}
ruList.forEach(function (ru, index) {
const card = document.createElement('div');
card.className = 'ai-title-card';
const head = document.createElement('div');
head.className = 'ai-title-card-head';
const label = document.createElement('span');
label.className = 'ai-title-card-index';
label.textContent = '方案 ' + (index + 1) + ' · ' + ru.length + ' 字符';
const actions = document.createElement('div');
actions.className = 'flex items-center gap-2 shrink-0';
const copyBtn = document.createElement('button');
copyBtn.type = 'button';
copyBtn.className = 'px-3 py-1.5 text-sm rounded-lg bg-primary/20 hover:bg-primary/30 text-primary transition-colors duration-200';
copyBtn.textContent = '复制俄文';
copyBtn.addEventListener('click', function () { copyText(ru); });
const fillBtn = document.createElement('button');
fillBtn.type = 'button';
fillBtn.className = 'px-3 py-1.5 text-sm rounded-lg bg-secondary/20 hover:bg-secondary/30 text-secondary transition-colors duration-200';
fillBtn.textContent = '填入商品名';
fillBtn.addEventListener('click', function () {
fillProductName(zhList[index] || '');
});
actions.appendChild(copyBtn);
actions.appendChild(fillBtn);
head.appendChild(label);
head.appendChild(actions);
const ruLine = document.createElement('p');
ruLine.className = 'ai-title-card-ru';
ruLine.textContent = ru;
const zhLine = document.createElement('p');
zhLine.className = 'ai-title-card-zh';
zhLine.textContent = zhList[index] || '—';
card.appendChild(head);
card.appendChild(ruLine);
card.appendChild(zhLine);
titlesContainerEl.appendChild(card);
});
}
function renderTags(tagsRu, tagsZh) {
lastTagsRu = toStringList(tagsRu).filter(Boolean);
const zhList = toStringList(tagsZh);
if (!tagsContainerEl) return;
tagsContainerEl.innerHTML = '';
if (!lastTagsRu.length) {
const empty = document.createElement('p');
empty.className = 'text-sm text-gray-500';
empty.textContent = '生成后将在此展示标签,点击可复制俄文';
tagsContainerEl.appendChild(empty);
return;
}
lastTagsRu.forEach(function (ru, index) {
const chip = document.createElement('button');
chip.type = 'button';
chip.className = 'ai-tag-chip';
chip.title = '点击复制俄文标签';
chip.setAttribute('data-tag-ru', ru);
const ruSpan = document.createElement('span');
ruSpan.className = 'ai-tag-chip-ru';
ruSpan.textContent = ru;
const sep = document.createElement('span');
sep.className = 'ai-tag-chip-sep';
sep.textContent = '';
const zhSpan = document.createElement('span');
zhSpan.className = 'ai-tag-chip-zh';
zhSpan.textContent = zhList[index] || '—';
chip.appendChild(ruSpan);
chip.appendChild(sep);
chip.appendChild(zhSpan);
tagsContainerEl.appendChild(chip);
});
}
function fillProductName(zhTitle) {
const zh = (zhTitle || '').trim();
if (!zh) {
setStatus('该标题没有中文对照', true);
return;
}
if (!productNameInput) {
setStatus('未找到商品名字段', true);
return;
}
productNameInput.value = zh;
productNameInput.dispatchEvent(new Event('input', { bubbles: true }));
syncContextHints();
setStatus('已填入商品名');
}
function fillResult(data) {
renderTitles(data.titles_ru, data.titles_zh);
descRuEl.textContent = data.description_ru || '';
descZhEl.textContent = data.description_zh || '';
renderTags(data.tags_ru, data.tags_zh);
}
async function loadModels() {
if (!modelSelectEl) return;
try {
const resp = await fetch(API_BASE + '/api/ai/models');
const data = await resp.json();
if (!resp.ok) {
throw new Error((data && data.detail) || ('HTTP ' + resp.status));
}
const models = Array.isArray(data.models) ? data.models : [];
const saved = localStorage.getItem(MODEL_STORAGE_KEY) || '';
const preferred = models.some(function (m) { return m.id === saved; })
? saved
: (data.default || (models[0] && models[0].id) || '');
modelSelectEl.innerHTML = '';
if (!models.length) {
modelSelectEl.innerHTML = '<option value="">无可用模型</option>';
return;
}
models.forEach(function (m) {
const opt = document.createElement('option');
opt.value = m.id;
opt.textContent = m.label || m.id;
if (m.id === preferred) opt.selected = true;
modelSelectEl.appendChild(opt);
});
localStorage.setItem(MODEL_STORAGE_KEY, modelSelectEl.value);
} catch (err) {
modelSelectEl.innerHTML = '<option value="">模型列表加载失败</option>';
setStatus(err && err.message ? err.message : '模型列表加载失败', true);
}
}
if (modelSelectEl) {
modelSelectEl.addEventListener('change', function () {
if (modelSelectEl.value) {
localStorage.setItem(MODEL_STORAGE_KEY, modelSelectEl.value);
}
});
}
if (toggleBtn && bodyEl) {
toggleBtn.addEventListener('click', function () {
const collapsed = bodyEl.classList.toggle('hidden');
toggleBtn.textContent = collapsed ? '展开' : '收起';
toggleBtn.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
});
}
async function generateCopy() {
const sourceText = (sourceTextEl.value || '').trim();
if (sourceText.length < 10) {
setStatus('请先粘贴至少 10 个字符的商品资料', true);
sourceTextEl.focus();
return;
}
const payload = {
source_text: sourceText,
product_name: (productNameInput && productNameInput.value || '').trim(),
model_code: (modelCodeInput && modelCodeInput.value || '').trim(),
model: (modelSelectEl && modelSelectEl.value || '').trim(),
};
generateBtn.disabled = true;
const originalLabel = generateBtn.textContent;
generateBtn.textContent = '生成中…';
setStatus('正在调用大模型…');
try {
const resp = await fetch(API_BASE + '/api/ai/copy', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
let body = null;
try {
body = await resp.json();
} catch (parseErr) {
body = null;
}
if (!resp.ok) {
const detail = body && body.detail
? (typeof body.detail === 'string' ? body.detail : JSON.stringify(body.detail))
: ('HTTP ' + resp.status);
throw new Error(detail);
}
fillResult(body);
const usage = body.usage || {};
const modelName = body.model ? (' · ' + body.model) : '';
setStatus(
'生成完成' + modelName +
(usage.prompt_tokens || usage.completion_tokens
? 'tokens: ' + (usage.prompt_tokens || 0) + '+' + (usage.completion_tokens || 0) + ''
: '')
);
} catch (err) {
setStatus(err && err.message ? err.message : '生成失败', true);
} finally {
generateBtn.disabled = false;
generateBtn.textContent = originalLabel;
}
}
generateBtn.addEventListener('click', generateCopy);
document.querySelectorAll('.ai-copy-btn').forEach(function (btn) {
btn.addEventListener('click', function () {
const sourceId = btn.getAttribute('data-copy-source');
const el = sourceId ? document.getElementById(sourceId) : null;
copyText(el ? el.textContent : '');
});
});
if (tagsContainerEl) {
tagsContainerEl.addEventListener('click', function (event) {
const chip = event.target.closest('.ai-tag-chip');
if (!chip || !tagsContainerEl.contains(chip)) return;
copyText(chip.getAttribute('data-tag-ru') || '');
});
}
if (copyAllTagsBtn) {
copyAllTagsBtn.addEventListener('click', function () {
copyText(lastTagsRu.join(', '));
});
}
if (productNameInput) {
productNameInput.addEventListener('input', syncContextHints);
}
if (modelCodeInput) {
modelCodeInput.addEventListener('input', syncContextHints);
}
syncContextHints();
loadModels();
})();
+1388
View File
File diff suppressed because it is too large Load Diff
+22
View File
@@ -0,0 +1,22 @@
// Tailwind Play CDN 运行时配置(须在 cdn.tailwindcss.com 之后加载)
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#3B82F6',
secondary: '#10B981',
neutral: '#1E1E2E',
dark: '#121212',
'dark-card': '#1E1E2E',
'dark-light': '#2D2D3F',
warning: '#F59E0B',
danger: '#EF4444',
caution: '#FBBF24',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
}
}
}
+575
View File
@@ -0,0 +1,575 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>轻量版</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script src="js/tailwind.config.js"></script>
<link rel="stylesheet" href="css/styles.css">
</head>
<body class="bg-dark min-h-screen flex flex-col text-gray-100">
<header class="sticky top-0 z-50 bg-dark-card border-b border-gray-800 shadow-lg shadow-primary/10">
<div class="container mx-auto px-4 py-3 flex flex-wrap items-center justify-between gap-x-6 gap-y-3">
<div class="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm">
<a class="text-gray-300 hover:text-primary transition-colors duration-200"
href="https://cn.bing.com/translator?ref=TThis&text=&from=zh-Hans&to=ru" target="_blank">翻译</a>
<a class="text-gray-300 hover:text-primary transition-colors duration-200"
href="https://wise.com/zh-cn/currency-converter/cny-to-rub-rate" target="_blank">汇率</a>
<span class="text-gray-700">|</span>
<a class="text-gray-300 hover:text-primary transition-colors duration-200"
href="https://seller.ozon.ru/app/dashboard/main/" target="_blank">ozon首页</a>
<a class="text-gray-300 hover:text-primary transition-colors duration-200"
href="https://seller.ozon.ru/app/prices/control" target="_blank">促销活动</a>
<a class="text-gray-300 hover:text-primary transition-colors duration-200"
href="https://seller.ozon.ru/app/postings/crossborder/fbs?tab=awaiting_deliver"
target="_blank">物流</a>
<span class="text-gray-700">|</span>
<a class="text-gray-300 hover:text-primary transition-colors duration-200"
href="https://cn.lianlianpay.com/wallet" target="_blank">连连支付</a>
<a class="text-gray-300 hover:text-primary transition-colors duration-200"
href="https://flowmore.pingpongx.com/entrance/signin?redirecturl=%2Fentrance%2FemailActiveByCode"
target="_blank">Pingpong</a>
<a class="text-gray-300 hover:text-primary transition-colors duration-200" href="https://imgchr.com/"
target="_blank">图片上传</a>
</div>
<div class="flex flex-wrap items-center justify-end gap-3 text-sm ml-auto">
<div class="flex items-center">
<span
class="px-3 py-2 bg-dark-light border border-r-0 border-gray-700 rounded-l-lg text-gray-400">¥</span>
<input type="number" id="fxCny" step="0.01" placeholder="人民币"
class="w-40 px-3 py-2 border border-gray-700 bg-dark-light rounded-r-lg input-focus text-gray-100">
</div>
<span class="text-gray-500"></span>
<div class="flex items-center">
<span
class="px-3 py-2 bg-dark-light border border-r-0 border-gray-700 rounded-l-lg text-gray-400"></span>
<input type="number" id="fxRub" step="0.01" placeholder="卢布"
class="w-40 px-3 py-2 border border-gray-700 bg-dark-light rounded-r-lg input-focus text-gray-100">
</div>
<div class="flex items-center gap-2 pl-2 border-l border-gray-800">
<span class="text-gray-400 whitespace-nowrap">1 ¥ = <span id="fxRateText"
class="text-gray-100">--</span></span>
<span id="fxRateSource" class="text-xs text-gray-500 whitespace-nowrap"></span>
<button type="button" id="fxRefreshBtn"
class="text-xs text-primary hover:underline transition-colors duration-200">刷新</button>
</div>
</div>
</div>
</header>
<main class="flex-grow container mx-auto px-4 py-8">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div class="bg-dark-card rounded-xl p-5 card-shadow neon-border">
<h2 class="text-lg font-semibold text-gray-100 mb-6 flex items-center text-glow">
输入商品信息
</h2>
<form id="priceForm" class="space-y-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="purchasePrice" class="block text-sm font-medium text-gray-300 mb-1">进货价</label>
<div class="flex">
<input type="number" id="purchasePrice" step="1" min="10"
class="w-full px-3 py-1.5 border border-gray-700 bg-dark-light rounded-l-lg input-focus text-gray-100">
<button type="button" id="clearPurchasePriceBtn" title="清空进货价"
class="bg-gray-800 hover:bg-gray-700 text-gray-300 px-3 py-1.5 rounded-r-lg transition-colors duration-200">
<i class="fa fa-edit"></i>
</button>
</div>
</div>
<div>
<label for="profitRate" class="block text-sm font-medium text-gray-300 mb-1">利润率 (%)</label>
<input type="number" id="profitRate" step="1" min="0"
class="w-full px-3 py-1.5 border border-gray-700 bg-dark-light rounded-lg input-focus text-gray-100">
</div>
<div>
<label for="weight" class="block text-sm font-medium text-gray-300 mb-1">商品重量 (g)</label>
<input type="number" id="weight" step="10" min="100"
class="w-full px-3 py-1.5 border border-gray-700 bg-dark-light rounded-lg input-focus text-gray-100">
</div>
<div>
<label for="tiedanPrice" class="block text-sm font-medium text-gray-300 mb-1">贴单费用</label>
<input type="text" id="tiedanPrice"
class="w-full px-3 py-1.5 border border-gray-700 bg-dark-light rounded-lg input-focus text-gray-100">
</div>
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-300 mb-1">外包装尺寸 (cm)</label>
<div class="grid grid-cols-3 gap-3">
<input type="number" id="length"
class="w-full px-3 py-1.5 border border-gray-700 bg-dark-light rounded-lg input-focus text-gray-100"
placeholder="长">
<input type="number" id="width"
class="w-full px-3 py-1.5 border border-gray-700 bg-dark-light rounded-lg input-focus text-gray-100"
placeholder="宽">
<input type="number" id="height"
class="w-full px-3 py-1.5 border border-gray-700 bg-dark-light rounded-lg input-focus text-gray-100"
placeholder="高">
</div>
</div>
<div>
<label for="modelCode" class="block text-sm font-medium text-gray-300 mb-1">型号</label>
<div class="flex">
<input type="text" id="modelCode"
class="w-full px-3 py-1.5 border border-gray-700 bg-dark-light rounded-l-lg input-focus text-gray-100"
placeholder="例如 ABC">
<button type="button" id="copyModelBtn" title="复制型号"
class="bg-gray-800 hover:bg-gray-700 text-gray-300 px-3 py-1.5 rounded-r-lg transition-colors duration-200">
<i class="fa fa-copy"></i>
</button>
</div>
</div>
<div>
<div class="flex items-center justify-between mb-1">
<label for="skuSuffix" class="block text-sm font-medium text-gray-300">货号 (sku)</label>
<span class="text-xs text-gray-500">完整货号:<span id="skuFullPreview"
class="text-gray-300">--</span></span>
</div>
<div class="flex">
<span id="skuPrefix"
class="shrink-0 px-3 py-1.5 border border-r-0 border-gray-700 bg-gray-800 rounded-l-lg text-gray-400 select-none min-w-[3rem]"></span>
<input type="text" id="skuSuffix"
class="w-full px-3 py-1.5 border border-gray-700 bg-dark-light input-focus text-gray-100"
placeholder="后半部分">
<button type="button" id="copySkuBtn" title="复制完整货号"
class="bg-gray-800 hover:bg-gray-700 text-gray-300 px-3 py-1.5 rounded-r-lg transition-colors duration-200">
<i class="fa fa-copy"></i>
</button>
</div>
</div>
<div class="md:col-span-2">
<label for="productName" class="block text-sm font-medium text-gray-300 mb-1">商品名</label>
<div class="flex">
<input type="text" id="productName"
class="w-full px-3 py-1.5 border border-gray-700 bg-dark-light rounded-l-lg input-focus text-gray-100">
<button type="button" id="copyProductNameBtn" title="复制商品名"
class="bg-gray-800 hover:bg-gray-700 text-gray-300 px-3 py-1.5 rounded-r-lg transition-colors duration-200">
<i class="fa fa-copy"></i>
</button>
</div>
</div>
<div class="md:col-span-2">
<label for="purchaseUrl" class="block text-sm font-medium text-gray-300 mb-1">采买地址</label>
<div class="flex">
<input type="text" id="purchaseUrl"
class="w-full px-3 py-1.5 border border-gray-700 bg-dark-light rounded-l-lg input-focus text-gray-100"
placeholder="1688 / 拼多多等采买页链接">
<button type="button" id="openPurchaseUrlBtn" title="打开采买地址"
class="bg-gray-800 hover:bg-gray-700 text-gray-300 px-3 py-1.5 border-r border-gray-700 transition-colors duration-200">
<i class="fa fa-external-link"></i>
</button>
<button type="button" id="copyPurchaseUrlBtn" title="复制采买地址"
class="bg-gray-800 hover:bg-gray-700 text-gray-300 px-3 py-1.5 rounded-r-lg transition-colors duration-200">
<i class="fa fa-copy"></i>
</button>
</div>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-300 mb-2">物流等级</label>
<div class="flex space-x-4">
<label class="inline-flex items-center cursor-pointer">
<input type="radio" name="logisticsLevel" value="low"
class="form-radio text-primary focus:ring-primary h-5 w-5">
<span class="ml-2 text-gray-300"></span>
</label>
<label class="inline-flex items-center cursor-pointer">
<input type="radio" name="logisticsLevel" value="high"
class="form-radio text-primary focus:ring-primary h-5 w-5" checked>
<span class="ml-2 text-gray-300"></span>
</label>
<label class="inline-flex items-center cursor-pointer">
<input type="radio" name="logisticsLevel" value="high2"
class="form-radio text-primary focus:ring-primary h-5 w-5" checked>
<span class="ml-2 text-gray-300">Premium</span>
</label>
</div>
</div>
<div class="flex space-x-4">
<button type="button" id="calculateBtn"
class="w-full bg-primary hover:bg-primary/90 text-white py-3 rounded-lg flex items-center justify-center transition-all duration-200 transform hover:scale-[1.02] hover:shadow-lg hover:shadow-primary/20">
计价
</button>
<button type="button" id="recordBtn"
class="w-full bg-secondary hover:bg-secondary/90 text-white py-3 rounded-lg flex items-center justify-center transition-all duration-200 transform hover:scale-[1.02] hover:shadow-lg hover:shadow-secondary/20">
录入
</button>
</div>
</form>
</div>
<div class="space-y-5">
<div id="logisticsCard" class="flex result-card opacity-0 transform translate-y-4">
<div class="mr-20" style="width: 60%;">
<h3 class="text-lg font-medium text-gray-100 mb-2 flex items-center text-glow">
物流费
</h3>
<p class="text-3xl font-bold text-gray-100" id="logisticsFee">--</p>
</div>
<div style="width: 40%;">
<h3 class="text-lg font-medium text-gray-100 mb-2 flex items-center text-glow">
平台佣金
</h3>
<p class="text-3xl font-bold text-gray-100" id="commission">--</p>
</div>
</div>
<div id="receivedCard" class="flex result-card opacity-0 transform translate-y-4 transition-delay-100">
<div class="mr-20" style="width: 60%;">
<h3 class="text-lg font-medium text-gray-100 mb-2 flex items-center text-glow">
实收价格
</h3>
<p class="text-3xl font-bold text-gray-100" id="receivedPrice">--</p>
</div>
<div style="width: 40%;">
<h3 class="text-lg font-medium text-gray-100 mb-2 flex items-center text-glow">
利润
</h3>
<p class="text-3xl font-bold text-gray-100" id="profitElement">--</p>
</div>
</div>
<div id="sellingCard" class="result-card opacity-0 transform translate-y-4 transition-delay-200">
<div class="flex items-center justify-between mb-2">
<h3 class="text-lg font-medium text-gray-100 flex items-center text-glow">
人民币-销售价格
</h3>
<div class="flex items-center">
<label for="discountReserve" class="text-sm text-gray-400 mr-2">预留折扣空间</label>
<input type="number" id="discountReserve" step="5" min="0" max="95" value="50"
class="w-20 px-2 py-1 border border-gray-700 bg-dark-light rounded-lg input-focus text-gray-100 text-sm">
<span class="ml-1 text-sm text-gray-400">%</span>
</div>
</div>
<div class="flex">
<div class="mr-20" style="width: 60%;">
<p class="text-sm text-gray-400 mb-1">现价</p>
<p class="text-3xl font-bold text-gray-100" id="sellingPrice">--</p>
</div>
<div style="width: 40%;">
<p class="text-sm text-gray-400 mb-1">预留 <span id="cnyReserveLabel">50%</span></p>
<p class="text-3xl font-bold text-gray-100" id="sellingPriceReserved">--</p>
<p class="text-xs text-gray-400 mt-1">折扣空间 <span id="cnyReserveGap">--</span></p>
</div>
</div>
</div>
<div id="sellingCardRub" class="result-card opacity-0 transform translate-y-4 transition-delay-200">
<div class="flex items-center justify-between mb-2">
<h3 class="text-lg font-medium text-gray-100 flex items-center text-glow">
卢布-销售价格
</h3>
<span class="text-sm text-gray-400">按 1 ¥ = <span id="rubCardRate"
class="text-gray-100">--</span></span>
</div>
<div class="flex">
<div class="mr-20" style="width: 60%;">
<p class="text-sm text-gray-400 mb-1">现价</p>
<p class="text-3xl font-bold text-secondary" id="sellingPriceRub">--</p>
</div>
<div style="width: 40%;">
<p class="text-sm text-gray-400 mb-1">预留 <span id="rubReserveLabel">50%</span></p>
<p class="text-3xl font-bold text-secondary" id="sellingPriceRubReserved">--</p>
<p class="text-xs text-gray-400 mt-1">折扣空间 <span id="rubReserveGap"
class="text-secondary">--</span></p>
</div>
</div>
</div>
<div id="dimensionAlert" class="hidden opacity-0 transition-opacity duration-300">
<div id="dimensionWarning" class="warning-card">
<h3 class="text-lg font-medium text-gray-100 mb-2 flex items-center">
尺寸警告
</h3>
<p class="text-gray-300" id="dimensionWarningMsg"></p>
</div>
<div id="dimensionError" class="error-card">
<h3 class="text-lg font-medium text-gray-100 mb-2 flex items-center">
尺寸错误
</h3>
<p class="text-gray-300" id="dimensionErrorMsg"></p>
</div>
<div id="logisticsLevelAlert" class="error-card">
<h3 class="text-lg font-medium text-gray-100 mb-2 flex items-center">
物流等级建议
</h3>
<p class="text-gray-300" id="logisticsLevelMsg"></p>
</div>
<div id="priceRangeAlert" class="warning-card">
<h3 class="text-lg font-medium text-gray-100 mb-2 flex items-center">
价格区间提示
</h3>
<p class="text-gray-300" id="priceRangeMsg"></p>
</div>
</div>
</div>
</div>
<!-- 俄文文案(AI -->
<div class="mt-8 bg-dark-card rounded-xl panel-shadow p-5 neon-border">
<div class="flex flex-wrap items-center justify-between gap-3 mb-4">
<div class="flex items-center gap-3">
<h2 class="text-lg font-semibold text-gray-100 flex items-center text-glow">
俄文文案
</h2>
<button type="button" id="aiToggleBtn" aria-expanded="true" aria-controls="aiCopyBody"
class="shrink-0 px-3 py-1.5 text-sm rounded-lg bg-primary/20 hover:bg-primary/30 text-primary transition-colors duration-200">
收起
</button>
</div>
<p id="aiCopyStatus" class="text-sm text-gray-500"></p>
</div>
<div id="aiCopyBody" class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="space-y-4">
<div>
<label for="aiSourceText" class="block text-sm font-medium text-gray-300 mb-1">商品资料</label>
<textarea id="aiSourceText" rows="16"
placeholder="粘贴采买站的标题、卖点、规格、材质等信息;如本次有特殊要求,直接写在后面,例如:偏搜索词、突出防摔、适合家居类目……"
class="w-full px-3 py-2 border border-gray-700 bg-dark-light rounded-lg input-focus text-gray-100"></textarea>
</div>
<p class="text-xs text-gray-500">
将带入当前:商品名 <span id="aiContextProductName" class="text-gray-300">--</span>
/ 型号 <span id="aiContextModelCode" class="text-gray-300">--</span>
</p>
<div class="flex flex-wrap items-center gap-3">
<label for="aiModelSelect" class="text-sm text-gray-400 whitespace-nowrap">模型</label>
<select id="aiModelSelect"
class="min-w-[14rem] flex-1 px-3 py-2 border border-gray-700 bg-dark-light rounded-lg input-focus text-gray-100 text-sm">
<option value="">加载中…</option>
</select>
</div>
<button type="button" id="aiGenerateBtn"
class="w-full bg-primary hover:bg-primary/90 text-white py-3 rounded-lg transition-all duration-200 transform hover:scale-[1.02] hover:shadow-lg hover:shadow-primary/20">
生成文案
</button>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-300 mb-1">推荐标题</label>
<div id="aiTitlesContainer" class="space-y-3">
<p class="text-sm text-gray-500">生成后将在此展示 2 个推荐标题</p>
</div>
</div>
<div>
<div class="flex items-center justify-between mb-1 gap-3">
<label class="block text-sm font-medium text-gray-300">描述</label>
<button type="button" data-copy-source="aiDescRu"
class="ai-copy-btn shrink-0 px-3 py-1.5 text-sm rounded-lg bg-primary/20 hover:bg-primary/30 text-primary transition-colors duration-200">复制俄文</button>
</div>
<div id="aiDescRu" class="ai-text-block ai-text-block-ru" data-placeholder="俄文描述将展示在这里"></div>
<div class="ai-text-divider"></div>
<p class="text-xs text-gray-500 mb-1">中文对照</p>
<div id="aiDescZh" class="ai-text-block ai-text-block-zh" data-placeholder="中文对照将展示在这里"></div>
</div>
<div>
<div class="flex items-center justify-between mb-1 gap-3">
<label class="block text-sm font-medium text-gray-300">标签</label>
<button type="button" id="aiCopyAllTagsBtn"
class="ai-copy-all-tags-btn shrink-0 px-3 py-1.5 text-sm rounded-lg bg-primary/20 hover:bg-primary/30 text-primary transition-colors duration-200">复制全部俄文标签</button>
</div>
<div id="aiTagsContainer" class="flex flex-wrap gap-2 min-h-[2.5rem]">
<p class="text-sm text-gray-500">生成后将在此展示标签,点击可复制俄文</p>
</div>
</div>
</div>
</div>
</div>
<!-- 图片处理 -->
<div class="mt-8 bg-dark-card rounded-xl panel-shadow p-5 neon-border">
<div class="flex items-center gap-3 mb-4">
<h2 class="text-lg font-semibold text-gray-100 flex items-center text-glow">
<i class="fa fa-picture-o mr-2"></i>图片处理
</h2>
<button type="button" id="imageToggleBtn" aria-expanded="true" aria-controls="imageBody"
class="shrink-0 px-3 py-1.5 text-sm rounded-lg bg-primary/20 hover:bg-primary/30 text-primary transition-colors duration-200">
收起
</button>
</div>
<div id="imageBody" class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-10 gap-5">
<div class="md:col-span-3 space-y-4">
<label class="block text-sm font-medium text-gray-300">水印设置</label>
<div class="flex space-x-6">
<label class="inline-flex items-center cursor-pointer">
<input type="radio" name="watermarkType" value="image" checked
class="form-radio text-primary focus:ring-primary h-5 w-5">
<span class="ml-2 text-gray-300">图片水印</span>
</label>
<label class="inline-flex items-center cursor-pointer">
<input type="radio" name="watermarkType" value="text"
class="form-radio text-primary focus:ring-primary h-5 w-5">
<span class="ml-2 text-gray-300">文字水印</span>
</label>
</div>
<div id="watermarkImageOptions" class="flex items-center gap-3">
<img src="imgs/watermark.jpg" alt="水印预览"
class="w-14 h-14 rounded-full border border-gray-700 object-cover shrink-0">
<span class="text-xs text-gray-500">默认水印图,贴图后为圆形</span>
</div>
<div id="watermarkTextOptions" class="hidden">
<div class="flex items-center">
<label for="watermarkText"
class="px-3 py-2 bg-dark-light border border-r-0 border-gray-700 rounded-l-lg text-gray-400 text-sm whitespace-nowrap">水印文字</label>
<input type="text" id="watermarkText" value="xiongmaoyx"
class="w-full px-3 py-2 border border-gray-700 bg-dark-light rounded-r-lg input-focus text-gray-100 text-sm">
</div>
</div>
<div class="flex items-center">
<label for="watermarkOpacity"
class="px-3 py-2 bg-dark-light border border-r-0 border-gray-700 rounded-l-lg text-gray-400 text-sm whitespace-nowrap">透明度</label>
<input type="number" id="watermarkOpacity" min="0" max="100" step="5" value="30"
class="w-full px-3 py-2 border border-gray-700 bg-dark-light input-focus text-gray-100 text-sm">
<span
class="px-3 py-2 bg-dark-light border border-l-0 border-gray-700 rounded-r-lg text-gray-400 text-sm">%</span>
</div>
<div class="pt-2 border-t border-gray-800">
<div class="flex items-center">
<label for="whiteBgTolerance"
class="px-3 py-2 bg-dark-light border border-r-0 border-gray-700 rounded-l-lg text-gray-400 text-sm whitespace-nowrap">白底容差</label>
<input type="number" id="whiteBgTolerance" min="5" max="200" step="5" value="60"
class="w-full px-3 py-2 border border-gray-700 bg-dark-light rounded-r-lg input-focus text-gray-100 text-sm">
</div>
<p class="text-xs text-gray-500 mt-1">背景没洗干净就调大,商品边缘被吃掉就调小</p>
</div>
</div>
<div class="md:col-span-7 flex flex-col">
<label class="block text-sm font-medium text-gray-300 mb-2">上传图片(支持多张)</label>
<div id="uploadArea" class="upload-area flex-1 flex flex-col items-center justify-center">
<input type="file" id="fileInput" multiple accept="image/*" class="hidden">
<div class="flex flex-col items-center">
<i class="fa fa-cloud-upload text-4xl text-gray-500 mb-2"></i>
<p class="text-gray-400">点击或拖拽图片到此处上传</p>
<p class="text-xs text-gray-500 mt-1">支持JPG、PNG、WEBP格式</p>
</div>
</div>
</div>
</div>
<div>
<div class="flex flex-wrap items-center justify-between gap-2 mb-2">
<label class="block text-sm font-medium text-gray-300">预览(处理后)</label>
<span class="text-xs text-gray-500">添加水印后,可在预览图上按住水印拖动调整位置</span>
</div>
<div id="previewContainer" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3">
<p class="col-span-full text-gray-500 text-center py-4">暂无图片</p>
</div>
</div>
<div class="flex space-x-4">
<button id="addWatermarkBtn"
class="flex-1 bg-primary hover:bg-primary/90 text-white py-3 rounded-lg flex items-center justify-center transition-all duration-200 transform hover:scale-[1.02] hover:shadow-lg hover:shadow-primary/20">
<i class="fa fa-tint mr-2"></i>添加水印
</button>
<button id="exportAllBtn" disabled
class="flex-1 bg-secondary hover:bg-secondary/90 text-white py-3 rounded-lg flex items-center justify-center transition-all duration-200 transform hover:scale-[1.02] hover:shadow-lg hover:shadow-secondary/20 opacity-50 cursor-not-allowed">
<i class="fa fa-download mr-2"></i>导出所有图片
</button>
</div>
</div>
</div>
<div class="mt-8 bg-dark-card rounded-xl panel-shadow p-5 overflow-x-auto neon-border">
<div class="flex flex-wrap justify-between items-center gap-3 mb-4">
<div class="flex flex-wrap items-center gap-3">
<h2 class="text-lg font-semibold text-gray-100 flex items-center text-glow">
上品登记表
</h2>
<button type="button" id="recordCurrentBtn"
class="bg-secondary hover:bg-secondary/90 text-white px-3 py-1.5 rounded-lg text-sm flex items-center transition-colors duration-200">
录入当前商品
</button>
</div>
<div class="flex flex-wrap items-center gap-2">
<button type="button" id="toggleOptionalColsBtn"
class="bg-gray-800 hover:bg-gray-700 text-gray-300 px-3 py-1.5 rounded-lg text-sm flex items-center transition-colors duration-200"
aria-pressed="false">
显示型号/重量/尺寸
</button>
<button type="button" id="transferDataBtn"
class="bg-primary/20 hover:bg-primary/30 text-primary px-3 py-1.5 rounded-lg text-sm flex items-center transition-colors duration-200">
导出组合码
</button>
<button type="button" id="exportHistoryBtn"
class="bg-primary/20 hover:bg-primary/30 text-primary px-3 py-1.5 rounded-lg text-sm flex items-center transition-colors duration-200">
导出记录
</button>
<button type="button" id="clearHistoryBtn"
class="bg-danger/20 hover:bg-danger/30 text-danger px-3 py-1.5 rounded-lg text-sm flex items-center transition-colors duration-200">
清空记录
</button>
</div>
</div>
<table id="historyTable" class="min-w-full divide-y divide-gray-700">
<thead class="bg-dark-light">
<tr>
<th scope="col"
class="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">操作
</th>
<th scope="col"
class="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">货号
(sku)</th>
<th scope="col"
class="history-col-optional px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">
型号</th>
<th scope="col"
class="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">商品名
</th>
<th scope="col"
class="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">进货价
</th>
<th scope="col"
class="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">物流费
</th>
<th scope="col"
class="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">销售价
</th>
<th scope="col"
class="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">实收价
</th>
<th scope="col"
class="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">利润
</th>
<th scope="col"
class="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">利润率
</th>
<th scope="col"
class="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">卢布销价
</th>
<th scope="col"
class="history-col-optional px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">
重量</th>
<th scope="col"
class="history-col-optional px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">
尺寸</th>
<th scope="col"
class="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Ozon地址
</th>
<th scope="col"
class="px-4 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">采买地址
</th>
</tr>
</thead>
<tbody class="bg-dark-card divide-y divide-gray-700" id="historyTableBody">
<tr class="text-center">
<td colspan="15" class="px-6 py-10 text-gray-500"></td>
</tr>
</tbody>
</table>
</div>
<div class="mt-8">
<div class="bg-dark-card rounded-xl panel-shadow p-5 neon-border">
<h2 class="text-lg font-semibold text-gray-100 mb-4 flex items-center text-glow">
上品组合码
</h2>
<div>
<textarea id="resultOutput" rows="10"
class="w-full px-3 py-2 border border-gray-700 bg-dark-light rounded-lg text-gray-100"></textarea>
</div>
<button id="copyBtn"
class="mt-4 w-full bg-primary hover:bg-primary/90 text-white py-2 rounded-lg flex items-center justify-center transition-all duration-200 transform hover:scale-105 hover:shadow-lg hover:shadow-primary/20">
复制结果
</button>
</div>
</div>
</main>
<script src="js/app.js"></script>
<script src="js/ai-copy.js"></script>
</body>
</html>