查询ASIN/最低价ASIN:精简新增弹窗
Build Backend JAR / build (push) Has been cancelled

- 国家由多选下拉改为单选,一次录入一个站点的 ASIN
- 店铺名改为按分组联动的下拉,移除带分页表格的「选择店铺」二级弹窗
- 表单压成两行(分组/店铺名/国家 + ASIN[/最低价]),按钮文案改为「保存」
- 保存成功后保留分组/店铺/国家,仅清空 ASIN 并聚焦,便于连续录入
- 清理随之失效的死代码:选择店铺弹窗 JS、按国家渲染输入框的 render/collect
  函数、.field-with-action/.skip-price-entry 等无引用 CSS
- admin.js 版本号改为 asin-create-v4,避免新 HTML 命中旧 JS 缓存

提交报文格式不变(countries:[code] + asin_mappings:{code:asin}),后端无需改动
This commit is contained in:
2026-09-01 13:39:50 +08:00
parent ac2b15d72f
commit f18d488cea
2 changed files with 155 additions and 485 deletions
+90 -322
View File
@@ -3409,12 +3409,10 @@
var filterSel = document.getElementById('shopManageFilterGroupId');
var skipCreateSel = document.getElementById('skipPriceAsinGroupSelect');
var skipFilterSel = document.getElementById('skipPriceAsinFilterGroupId');
var chooseSkipShopSel = document.getElementById('chooseSkipPriceAsinShopGroupId');
var skipImportSel = document.getElementById('skipPriceAsinImportGroupId');
var skipDeleteImportSel = document.getElementById('skipPriceAsinDeleteImportGroupId');
var queryCreateSel = document.getElementById('queryAsinGroupSelect');
var queryFilterSel = document.getElementById('queryAsinFilterGroupId');
var chooseQueryShopSel = document.getElementById('chooseQueryAsinShopGroupId');
var queryImportSel = document.getElementById('queryAsinImportGroupId');
var queryDeleteImportSel = document.getElementById('queryAsinDeleteImportGroupId');
var selectedInvalidCreateId = invalidCreateSel ? invalidCreateSel.value : '';
@@ -3427,10 +3425,8 @@
var selectedFilterId = filterSel ? filterSel.value : '';
var selectedSkipCreateId = skipCreateSel ? skipCreateSel.value : '';
var selectedSkipFilterId = skipFilterSel ? skipFilterSel.value : '';
var selectedChooseSkipShopId = chooseSkipShopSel ? chooseSkipShopSel.value : '';
var selectedQueryCreateId = queryCreateSel ? queryCreateSel.value : '';
var selectedQueryFilterId = queryFilterSel ? queryFilterSel.value : '';
var selectedChooseQueryShopId = chooseQueryShopSel ? chooseQueryShopSel.value : '';
var createOpts = ['<option value="">请选择分组</option>'];
var filterOpts = ['<option value="">全部分组</option>'];
shopManageGroups.forEach(function (g) {
@@ -3479,9 +3475,7 @@
if (queryDeleteImportSel) queryDeleteImportSel.innerHTML = createOpts.join('');
if (filterSel) filterSel.innerHTML = filterOpts.join('');
if (skipFilterSel) skipFilterSel.innerHTML = filterOpts.join('');
if (chooseSkipShopSel) chooseSkipShopSel.innerHTML = filterOpts.join('');
if (queryFilterSel) queryFilterSel.innerHTML = filterOpts.join('');
if (chooseQueryShopSel) chooseQueryShopSel.innerHTML = filterOpts.join('');
if (selectedCreateId != null) createSel.value = String(selectedCreateId);
if (selectedEditId != null) editSel.value = String(selectedEditId);
if (invalidCreateSel) {
@@ -3506,10 +3500,8 @@
if (filterSel && selectedFilterId) filterSel.value = selectedFilterId;
if (skipCreateSel && selectedSkipCreateId) skipCreateSel.value = selectedSkipCreateId;
if (skipFilterSel && selectedSkipFilterId) skipFilterSel.value = selectedSkipFilterId;
if (chooseSkipShopSel && selectedChooseSkipShopId) chooseSkipShopSel.value = selectedChooseSkipShopId;
if (queryCreateSel && selectedQueryCreateId) queryCreateSel.value = selectedQueryCreateId;
if (queryFilterSel && selectedQueryFilterId) queryFilterSel.value = selectedQueryFilterId;
if (chooseQueryShopSel && selectedChooseQueryShopId) chooseQueryShopSel.value = selectedChooseQueryShopId;
}
function renderShopManageGroupSummary() {
@@ -3865,10 +3857,57 @@
};
// ========== 版本管理 ==========
// ========== 新增 ASIN 弹窗共用:店铺下拉 ==========
// 按分组拉取该组全部店铺名(接口单页上限 100,逐页取完)
function loadAsinShopNameOptions(selectId, groupId) {
var select = document.getElementById(selectId);
if (!select) return;
if (!groupId) {
select.innerHTML = '<option value="">请先选择分组</option>';
select.disabled = true;
return;
}
select.innerHTML = '<option value="">加载中…</option>';
select.disabled = true;
var requestGroupId = String(groupId);
select.setAttribute('data-loading-group-id', requestGroupId);
var names = [];
function fetchPage(page) {
return fetch('/api/admin/shop-manages?page=' + page + '&page_size=100&group_id=' + encodeURIComponent(requestGroupId))
.then(function (r) { return r.json(); })
.then(function (res) {
if (!res.success) throw new Error(res.error || '加载店铺失败');
(res.items || []).forEach(function (item) {
var name = (item.shop_name || '').trim();
if (name && names.indexOf(name) === -1) names.push(name);
});
var pageSize = Number(res.page_size) || 100;
if (page * pageSize < Number(res.total || 0) && page < 20) return fetchPage(page + 1);
return null;
});
}
fetchPage(1)
.then(function () {
// 期间又切了分组,丢弃这次结果
if (select.getAttribute('data-loading-group-id') !== requestGroupId) return;
if (!names.length) {
select.innerHTML = '<option value="">该分组暂无店铺</option>';
return;
}
select.innerHTML = ['<option value="">请选择店铺</option>'].concat(names.map(function (name) {
return '<option value="' + escapeHtml(name) + '">' + escapeHtml(name) + '</option>';
})).join('');
select.disabled = false;
})
.catch(function () {
if (select.getAttribute('data-loading-group-id') !== requestGroupId) return;
select.innerHTML = '<option value="">店铺加载失败</option>';
});
}
// ========== 跳过跟价 ASIN ==========
var skipPriceAsinPage = 1, skipPriceAsinPageSize = 15;
var skipPriceAsinItemsById = {};
var chooseSkipPriceAsinShopPage = 1, chooseSkipPriceAsinShopPageSize = 10;
var skipPriceCountryColumns = [
{ code: 'DE', field: 'asin_de', minimumPriceField: 'minimum_price_de', label: '德国' },
{ code: 'UK', field: 'asin_uk', minimumPriceField: 'minimum_price_uk', label: '英国' },
@@ -3876,131 +3915,12 @@
{ code: 'IT', field: 'asin_it', minimumPriceField: 'minimum_price_it', label: '意大利' },
{ code: 'ES', field: 'asin_es', minimumPriceField: 'minimum_price_es', label: '西班牙' }
];
function buildChooseSkipPriceAsinShopQuery(page) {
var query = 'page=' + (page || 1) + '&page_size=' + chooseSkipPriceAsinShopPageSize;
var groupId = (document.getElementById('chooseSkipPriceAsinShopGroupId').value || '').trim();
var shopName = (document.getElementById('chooseSkipPriceAsinShopKeyword').value || '').trim();
if (groupId) query += '&group_id=' + encodeURIComponent(groupId);
if (shopName) query += '&shop_name=' + encodeURIComponent(shopName);
return query;
}
function loadChooseSkipPriceAsinShops(page) {
chooseSkipPriceAsinShopPage = page || 1;
fetch('/api/admin/shop-manages?' + buildChooseSkipPriceAsinShopQuery(chooseSkipPriceAsinShopPage))
.then(function (r) { return r.json(); })
.then(function (res) {
var tbody = document.getElementById('chooseSkipPriceAsinShopListBody');
if (!res.success) {
tbody.innerHTML = '<tr><td colspan="7" class="empty-tip">加载失败: ' + (res.error || '') + '</td></tr>';
return;
}
var items = res.items || [];
if (!items.length) {
tbody.innerHTML = '<tr><td colspan="6" class="empty-tip">暂无店铺</td></tr>';
} else {
tbody.innerHTML = items.map(function (item, index) {
var rowNo = (chooseSkipPriceAsinShopPage - 1) * chooseSkipPriceAsinShopPageSize + index + 1;
return '<tr><td>' + rowNo + '</td><td>' + (item.group_name || '') + '</td><td>' + (item.shop_name || '') + '</td><td>' + (item.mall_name || '') + '</td><td>' + (item.account || '') + '</td><td>' +
'<button class="btn btn-sm" type="button" data-choose-skip-price-asin-shop="' + item.id + '" data-shop-name="' + (item.shop_name || '').replace(/"/g, '&quot;') + '" data-group-id="' + (item.group_id || '') + '">选择</button>' +
'</td></tr>';
}).join('');
}
renderPagination('chooseSkipPriceAsinShopPagination', res.total, res.page, res.page_size, loadChooseSkipPriceAsinShops);
bindChooseSkipPriceAsinShopActions();
})
.catch(function () {
document.getElementById('chooseSkipPriceAsinShopListBody').innerHTML = '<tr><td colspan="7" class="empty-tip">请求失败</td></tr>';
});
}
function bindChooseSkipPriceAsinShopActions() {
document.querySelectorAll('[data-choose-skip-price-asin-shop]').forEach(function (btn) {
btn.onclick = function () {
document.getElementById('skipPriceAsinShopName').value = (btn.dataset.shopName || '').replace(/&quot;/g, '"');
if (!document.getElementById('skipPriceAsinGroupSelect').value && btn.dataset.groupId) {
document.getElementById('skipPriceAsinGroupSelect').value = btn.dataset.groupId;
}
document.getElementById('chooseSkipPriceAsinShopModal').classList.remove('show');
};
});
}
function openChooseSkipPriceAsinShopModal() {
var currentGroupId = (document.getElementById('skipPriceAsinGroupSelect').value || '').trim();
if (currentGroupId) {
document.getElementById('chooseSkipPriceAsinShopGroupId').value = currentGroupId;
}
document.getElementById('chooseSkipPriceAsinShopKeyword').value = (document.getElementById('skipPriceAsinShopName').value || '').trim();
document.getElementById('chooseSkipPriceAsinShopModal').classList.add('show');
loadChooseSkipPriceAsinShops(1);
}
function getSelectedSkipPriceCountries() {
return Array.from(document.getElementById('skipPriceAsinCountries').selectedOptions).map(function (option) {
return option.value;
});
}
function formatSkipPriceMinimumPrice(value) {
if (value === null || value === undefined || value === '') return '';
var num = Number(value);
if (!isFinite(num)) return String(value);
return num.toFixed(2);
}
function getSkipPriceCountryLabel(countryCode) {
var country = skipPriceCountryColumns.find(function (item) { return item.code === countryCode; });
return country ? country.label : countryCode;
}
function renderSkipPriceAsinInputs() {
var container = document.getElementById('skipPriceAsinInputs');
if (!container) return;
var selectedCountries = getSelectedSkipPriceCountries();
var existingValues = {};
var existingMinimumPrices = {};
container.querySelectorAll('[data-skip-price-country-input]').forEach(function (input) {
existingValues[input.getAttribute('data-skip-price-country-input')] = input.value;
});
container.querySelectorAll('[data-skip-price-country-minimum-price-input]').forEach(function (input) {
existingMinimumPrices[input.getAttribute('data-skip-price-country-minimum-price-input')] = input.value;
});
if (!selectedCountries.length) {
container.innerHTML = '<div class="asin-empty-hint">请选择国家后输入 ASIN 和最低价</div>';
return;
}
container.innerHTML = selectedCountries.map(function (countryCode) {
var label = getSkipPriceCountryLabel(countryCode);
var value = existingValues[countryCode] || '';
var minimumPrice = existingMinimumPrices[countryCode] || '';
return '<div class="skip-price-entry">' +
'<span class="skip-price-country">' + escapeHtml(label) + '</span>' +
'<input class="skip-price-asin-input" type="text" data-skip-price-country-input="' + escapeHtml(countryCode) + '" value="' + escapeHtml(value) + '" placeholder="请输入' + escapeHtml(label) + ' ASIN">' +
'<input class="skip-price-minimum-input" type="number" data-skip-price-country-minimum-price-input="' + escapeHtml(countryCode) + '" value="' + escapeHtml(minimumPrice) + '" min="0" step="0.01" placeholder="最低价">' +
'</div>';
}).join('');
}
function collectSkipPriceAsinMappings(countries) {
var asinMappings = {};
var minimumPriceMappings = {};
for (var i = 0; i < countries.length; i++) {
var countryCode = countries[i];
var input = document.querySelector('[data-skip-price-country-input="' + countryCode + '"]');
var minimumPriceInput = document.querySelector('[data-skip-price-country-minimum-price-input="' + countryCode + '"]');
var asin = input ? (input.value || '').trim().toUpperCase() : '';
if (!asin) {
var label = getSkipPriceCountryLabel(countryCode);
throw new Error(label + ' ASIN 不能为空');
}
var minimumPrice = minimumPriceInput ? (minimumPriceInput.value || '').trim() : '';
if (minimumPrice) {
var minimumPriceNumber = Number(minimumPrice);
if (!isFinite(minimumPriceNumber) || minimumPriceNumber < 0) {
throw new Error(getSkipPriceCountryLabel(countryCode) + ' 最低价格式不正确');
}
minimumPriceMappings[countryCode] = minimumPrice;
}
asinMappings[countryCode] = asin;
}
return {
asinMappings: asinMappings,
minimumPriceMappings: minimumPriceMappings
};
}
function buildSkipPriceAsinFilterQuery() {
var query = '';
var groupId = (document.getElementById('skipPriceAsinFilterGroupId').value || '').trim();
@@ -4517,21 +4437,8 @@
};
xhr.send(formData);
}
document.getElementById('btnChooseSkipPriceAsinShop').onclick = openChooseSkipPriceAsinShopModal;
document.getElementById('btnSearchChooseSkipPriceAsinShop').onclick = function () {
loadChooseSkipPriceAsinShops(1);
};
document.getElementById('chooseSkipPriceAsinShopGroupId').onchange = function () {
loadChooseSkipPriceAsinShops(1);
};
document.getElementById('chooseSkipPriceAsinShopKeyword').addEventListener('keydown', function (e) {
if (e.key === 'Enter') {
e.preventDefault();
loadChooseSkipPriceAsinShops(1);
}
});
document.getElementById('btnCloseChooseSkipPriceAsinShopModal').onclick = function () {
document.getElementById('chooseSkipPriceAsinShopModal').classList.remove('show');
document.getElementById('skipPriceAsinGroupSelect').onchange = function () {
loadAsinShopNameOptions('skipPriceAsinShopName', (this.value || '').trim());
};
document.getElementById('btnCloseSkipPriceAsinDrawer').onclick = closeSkipPriceAsinDrawer;
document.getElementById('btnCancelSkipPriceAsinDrawer').onclick = closeSkipPriceAsinDrawer;
@@ -4539,7 +4446,6 @@
document.getElementById('skipPriceAsinDrawer').addEventListener('click', function (event) {
if (event.target === this) closeSkipPriceAsinDrawer();
});
document.getElementById('skipPriceAsinCountries').addEventListener('change', renderSkipPriceAsinInputs);
document.getElementById('btnSearchSkipPriceAsin').onclick = function () {
loadSkipPriceAsin(1);
};
@@ -4560,12 +4466,10 @@
});
document.getElementById('btnOpenCreateSkipPriceAsin').onclick = function () {
document.getElementById('skipPriceAsinGroupSelect').value = '';
document.getElementById('skipPriceAsinShopName').value = '';
Array.from(document.getElementById('skipPriceAsinCountries').options).forEach(function (option) {
option.selected = false;
});
refreshDropdownMultiSelect('skipPriceAsinCountries');
renderSkipPriceAsinInputs();
loadAsinShopNameOptions('skipPriceAsinShopName', '');
document.getElementById('skipPriceAsinCountry').value = '';
document.getElementById('skipPriceAsinValue').value = '';
document.getElementById('skipPriceAsinMinimumPrice').value = '';
document.getElementById('msgSkipPriceAsin').textContent = '';
document.getElementById('msgSkipPriceAsin').className = 'msg';
document.getElementById('createSkipPriceAsinModal').classList.add('show');
@@ -4598,31 +4502,31 @@
document.getElementById('btnCreateSkipPriceAsin').onclick = function () {
var groupId = (document.getElementById('skipPriceAsinGroupSelect').value || '').trim();
var shopName = (document.getElementById('skipPriceAsinShopName').value || '').trim();
var countries = getSelectedSkipPriceCountries();
var country = (document.getElementById('skipPriceAsinCountry').value || '').trim();
var asinInput = document.getElementById('skipPriceAsinValue');
var minimumPriceInput = document.getElementById('skipPriceAsinMinimumPrice');
var asin = (asinInput.value || '').trim().toUpperCase();
var minimumPrice = (minimumPriceInput.value || '').trim();
var msgEl = document.getElementById('msgSkipPriceAsin');
msgEl.textContent = '';
msgEl.className = 'msg';
if (!groupId || !shopName || !countries.length) {
if (!groupId || !shopName || !country || !asin) {
msgEl.textContent = '请完整填写分组、店铺名、国家和 ASIN';
msgEl.className = 'msg err';
return;
}
var asinMappings = {};
var minimumPriceMappings = {};
try {
var mappings = collectSkipPriceAsinMappings(countries);
asinMappings = mappings.asinMappings || {};
minimumPriceMappings = mappings.minimumPriceMappings || {};
} catch (err) {
msgEl.textContent = err.message || '请输入 ASIN';
if (minimumPrice) {
var minimumPriceNumber = Number(minimumPrice);
if (!isFinite(minimumPriceNumber) || minimumPriceNumber < 0) {
msgEl.textContent = '最低价格式不正确';
msgEl.className = 'msg err';
return;
}
var fallbackAsin = '';
Object.keys(asinMappings).some(function (countryCode) {
fallbackAsin = asinMappings[countryCode] || '';
return !!fallbackAsin;
});
}
var asinMappings = {};
asinMappings[country] = asin;
var minimumPriceMappings = {};
if (minimumPrice) minimumPriceMappings[country] = minimumPrice;
var operatorQuery = buildSkipPriceAsinOperatorQuery();
fetch('/api/admin/skip-price-asin' + (operatorQuery ? ('?' + operatorQuery) : ''), {
method: 'POST',
@@ -4630,8 +4534,8 @@
body: JSON.stringify({
group_id: Number(groupId),
shop_name: shopName,
countries: countries,
asin: fallbackAsin,
countries: [country],
asin: asin,
asin_mappings: asinMappings,
minimum_price_mappings: minimumPriceMappings
})
@@ -4643,13 +4547,10 @@
msgEl.className = 'msg err';
return;
}
document.getElementById('skipPriceAsinGroupSelect').value = '';
document.getElementById('skipPriceAsinShopName').value = '';
Array.from(document.getElementById('skipPriceAsinCountries').options).forEach(function (option) {
option.selected = false;
});
refreshDropdownMultiSelect('skipPriceAsinCountries');
renderSkipPriceAsinInputs();
// 保留分组/店铺/国家,方便连续录入同一店铺的多个 ASIN
asinInput.value = '';
minimumPriceInput.value = '';
asinInput.focus();
msgEl.textContent = res.msg || '保存成功';
msgEl.className = 'msg ok';
loadSkipPriceAsin(1);
@@ -4659,19 +4560,16 @@
msgEl.className = 'msg err';
});
};
initDropdownMultiSelect('skipPriceAsinCountries', '请选择国家');
document.getElementById('btnImportSkipPriceAsin').onclick = function () {
uploadSkipPriceAsinImport(false);
};
document.getElementById('btnDeleteImportSkipPriceAsin').onclick = function () {
uploadSkipPriceAsinImport(true);
};
renderSkipPriceAsinInputs();
// ========== 查询 ASIN ==========
var queryAsinPage = 1, queryAsinPageSize = 15;
var queryAsinItemsById = {};
var chooseQueryAsinShopPage = 1, chooseQueryAsinShopPageSize = 10;
var queryAsinCountryColumns = [
{ code: 'DE', field: 'asin_de', label: '德国' },
{ code: 'UK', field: 'asin_uk', label: '英国' },
@@ -4679,104 +4577,6 @@
{ code: 'IT', field: 'asin_it', label: '意大利' },
{ code: 'ES', field: 'asin_es', label: '西班牙' }
];
function getQueryAsinCountryLabel(countryCode) {
var country = queryAsinCountryColumns.find(function (item) { return item.code === countryCode; });
return country ? country.label : countryCode;
}
function getSelectedQueryAsinCountries() {
return Array.from(document.getElementById('queryAsinCountries').selectedOptions).map(function (option) {
return option.value;
});
}
function renderQueryAsinInputs() {
var container = document.getElementById('queryAsinInputs');
var selectedCountries = getSelectedQueryAsinCountries();
var existingValues = {};
container.querySelectorAll('[data-query-asin-country-input]').forEach(function (input) {
existingValues[input.getAttribute('data-query-asin-country-input')] = input.value;
});
if (!selectedCountries.length) {
container.innerHTML = '<div style="color:#999;font-size:13px;">请选择国家后输入 ASIN</div>';
return;
}
container.innerHTML = selectedCountries.map(function (countryCode) {
var label = getQueryAsinCountryLabel(countryCode);
var value = existingValues[countryCode] || '';
return '<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">' +
'<span style="min-width:56px;color:#555;">' + label + '</span>' +
'<input type="text" data-query-asin-country-input="' + countryCode + '" value="' + value.replace(/"/g, '&quot;') + '" placeholder="请输入' + label + ' ASIN" style="flex:1;min-width:180px;">' +
'</div>';
}).join('');
}
function collectQueryAsinMappings(countries) {
var asinMappings = {};
for (var i = 0; i < countries.length; i++) {
var countryCode = countries[i];
var input = document.querySelector('[data-query-asin-country-input="' + countryCode + '"]');
var asin = input ? (input.value || '').trim().toUpperCase() : '';
if (!asin) {
throw new Error(getQueryAsinCountryLabel(countryCode) + ' ASIN 不能为空');
}
asinMappings[countryCode] = asin;
}
return asinMappings;
}
function buildChooseQueryAsinShopQuery(page) {
var query = 'page=' + (page || 1) + '&page_size=' + chooseQueryAsinShopPageSize;
var groupId = (document.getElementById('chooseQueryAsinShopGroupId').value || '').trim();
var shopName = (document.getElementById('chooseQueryAsinShopKeyword').value || '').trim();
if (groupId) query += '&group_id=' + encodeURIComponent(groupId);
if (shopName) query += '&shop_name=' + encodeURIComponent(shopName);
return query;
}
function loadChooseQueryAsinShops(page) {
chooseQueryAsinShopPage = page || 1;
fetch('/api/admin/shop-manages?' + buildChooseQueryAsinShopQuery(chooseQueryAsinShopPage))
.then(function (r) { return r.json(); })
.then(function (res) {
var tbody = document.getElementById('chooseQueryAsinShopListBody');
if (!res.success) {
tbody.innerHTML = '<tr><td colspan="6" class="empty-tip">加载失败: ' + (res.error || '') + '</td></tr>';
return;
}
var items = res.items || [];
if (items.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="empty-tip">暂无店铺</td></tr>';
} else {
tbody.innerHTML = items.map(function (item, index) {
var rowNo = (chooseQueryAsinShopPage - 1) * chooseQueryAsinShopPageSize + index + 1;
return '<tr><td>' + rowNo + '</td><td>' + (item.group_name || '') + '</td><td>' + (item.shop_name || '') + '</td><td>' + (item.mall_name || '') + '</td><td>' + (item.username || '') + '</td><td>' +
'<button class="btn btn-sm" type="button" data-choose-query-asin-shop="' + item.id + '" data-shop-name="' + (item.shop_name || '').replace(/"/g, '&quot;') + '" data-group-id="' + (item.group_id || '') + '">选择</button>' +
'</td></tr>';
}).join('');
}
renderPagination('chooseQueryAsinShopPagination', res.total, res.page, res.page_size, loadChooseQueryAsinShops);
bindChooseQueryAsinShopActions();
})
.catch(function () {
document.getElementById('chooseQueryAsinShopListBody').innerHTML = '<tr><td colspan="6" class="empty-tip">请求失败</td></tr>';
});
}
function bindChooseQueryAsinShopActions() {
document.querySelectorAll('[data-choose-query-asin-shop]').forEach(function (btn) {
btn.onclick = function () {
document.getElementById('queryAsinShopName').value = (btn.dataset.shopName || '').replace(/&quot;/g, '"');
if (btn.dataset.groupId) {
document.getElementById('queryAsinGroupSelect').value = btn.dataset.groupId;
}
document.getElementById('chooseQueryAsinShopModal').classList.remove('show');
};
});
}
function openChooseQueryAsinShopModal() {
var currentGroupId = (document.getElementById('queryAsinGroupSelect').value || '').trim();
if (currentGroupId) {
document.getElementById('chooseQueryAsinShopGroupId').value = currentGroupId;
}
document.getElementById('chooseQueryAsinShopKeyword').value = (document.getElementById('queryAsinShopName').value || '').trim();
document.getElementById('chooseQueryAsinShopModal').classList.add('show');
loadChooseQueryAsinShops(1);
}
function buildQueryAsinQuery(page) {
var query = 'page=' + (page || 1) + '&page_size=' + queryAsinPageSize;
var filterQuery = buildQueryAsinFilterQuery();
@@ -5135,21 +4935,8 @@
};
xhr.send(formData);
}
document.getElementById('btnChooseQueryAsinShop').onclick = openChooseQueryAsinShopModal;
document.getElementById('btnSearchChooseQueryAsinShop').onclick = function () {
loadChooseQueryAsinShops(1);
};
document.getElementById('chooseQueryAsinShopGroupId').onchange = function () {
loadChooseQueryAsinShops(1);
};
document.getElementById('chooseQueryAsinShopKeyword').addEventListener('keydown', function (e) {
if (e.key === 'Enter') {
e.preventDefault();
loadChooseQueryAsinShops(1);
}
});
document.getElementById('btnCloseChooseQueryAsinShopModal').onclick = function () {
document.getElementById('chooseQueryAsinShopModal').classList.remove('show');
document.getElementById('queryAsinGroupSelect').onchange = function () {
loadAsinShopNameOptions('queryAsinShopName', (this.value || '').trim());
};
document.getElementById('btnCloseQueryAsinDrawer').onclick = closeQueryAsinDrawer;
document.getElementById('btnCancelQueryAsinDrawer').onclick = closeQueryAsinDrawer;
@@ -5157,7 +4944,6 @@
document.getElementById('queryAsinDrawer').addEventListener('click', function (event) {
if (event.target === this) closeQueryAsinDrawer();
});
document.getElementById('queryAsinCountries').addEventListener('change', renderQueryAsinInputs);
document.getElementById('btnSearchQueryAsin').onclick = function () {
loadQueryAsin(1);
};
@@ -5178,12 +4964,9 @@
});
document.getElementById('btnOpenCreateQueryAsin').onclick = function () {
document.getElementById('queryAsinGroupSelect').value = '';
document.getElementById('queryAsinShopName').value = '';
Array.from(document.getElementById('queryAsinCountries').options).forEach(function (option) {
option.selected = false;
});
refreshDropdownMultiSelect('queryAsinCountries');
renderQueryAsinInputs();
loadAsinShopNameOptions('queryAsinShopName', '');
document.getElementById('queryAsinCountry').value = '';
document.getElementById('queryAsinValue').value = '';
document.getElementById('msgQueryAsin').textContent = '';
document.getElementById('msgQueryAsin').className = 'msg';
document.getElementById('createQueryAsinModal').classList.add('show');
@@ -5216,36 +4999,27 @@
document.getElementById('btnCreateQueryAsin').onclick = function () {
var groupId = (document.getElementById('queryAsinGroupSelect').value || '').trim();
var shopName = (document.getElementById('queryAsinShopName').value || '').trim();
var countries = getSelectedQueryAsinCountries();
var country = (document.getElementById('queryAsinCountry').value || '').trim();
var asinInput = document.getElementById('queryAsinValue');
var asin = (asinInput.value || '').trim().toUpperCase();
var msgEl = document.getElementById('msgQueryAsin');
msgEl.textContent = '';
msgEl.className = 'msg';
if (!groupId || !shopName || !countries.length) {
if (!groupId || !shopName || !country || !asin) {
msgEl.textContent = '请完整填写分组、店铺名、国家和 ASIN';
msgEl.className = 'msg err';
return;
}
var asinMappings = {};
try {
asinMappings = collectQueryAsinMappings(countries);
} catch (err) {
msgEl.textContent = err.message || '请输入 ASIN';
msgEl.className = 'msg err';
return;
}
var fallbackAsin = '';
Object.keys(asinMappings).some(function (countryCode) {
fallbackAsin = asinMappings[countryCode] || '';
return !!fallbackAsin;
});
asinMappings[country] = asin;
fetch('/api/admin/query-asin', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
group_id: Number(groupId),
shop_name: shopName,
countries: countries,
asin: fallbackAsin,
countries: [country],
asin: asin,
asin_mappings: asinMappings
})
})
@@ -5256,13 +5030,9 @@
msgEl.className = 'msg err';
return;
}
document.getElementById('queryAsinGroupSelect').value = '';
document.getElementById('queryAsinShopName').value = '';
Array.from(document.getElementById('queryAsinCountries').options).forEach(function (option) {
option.selected = false;
});
refreshDropdownMultiSelect('queryAsinCountries');
renderQueryAsinInputs();
// 保留分组/店铺/国家,方便连续录入同一店铺的多个 ASIN
asinInput.value = '';
asinInput.focus();
msgEl.textContent = res.msg || '保存成功';
msgEl.className = 'msg ok';
loadQueryAsin(1);
@@ -5272,14 +5042,12 @@
msgEl.className = 'msg err';
});
};
initDropdownMultiSelect('queryAsinCountries', '请选择国家');
document.getElementById('btnImportQueryAsin').onclick = function () {
uploadQueryAsinImport(false);
};
document.getElementById('btnDeleteImportQueryAsin').onclick = function () {
uploadQueryAsinImport(true);
};
renderQueryAsinInputs();
var productCategoryItems = [];
var productCategoryNodeMap = {};
+38 -136
View File
@@ -3687,10 +3687,7 @@
.table-ellipsis { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.asin-cell-content { min-width: 0; overflow: hidden; }
.asin-cell-meta { display: block; margin-top: 3px; color: #73869a; font-size: 12px; white-space: nowrap; }
.asin-empty-value, .asin-empty-hint { color: #8b9aaa; }
.skip-price-entry { display: grid !important; grid-template-columns: 58px minmax(0, 1fr) 140px; align-items: center; gap: 8px !important; }
.skip-price-country { color: var(--c-text-2); font-weight: 600; }
.skip-price-entry input { min-width: 0 !important; width: auto !important; }
.asin-empty-value { color: #8b9aaa; }
.asin-copy-text { display: block; max-width: 100%; margin: -3px 0 -3px -7px; padding: 3px 7px; border: 1px solid transparent; border-radius: var(--radius-sm); background: none; color: inherit; font: inherit; font-size: 13.5px; font-variant-numeric: tabular-nums; line-height: 1.5; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; }
.asin-copy-text:hover { border-color: #c7d7e5; background: #eef4fa; color: var(--c-primary-strong); }
.asin-copy-text:focus-visible { outline: 2px solid #4f78a5; outline-offset: 1px; }
@@ -3699,11 +3696,6 @@
th.asin-col-actions { z-index: 3; background: #f7f8fb; }
tbody tr:hover td.asin-col-actions { background: #f6f8fd; }
/* 表单里「输入框 + 附属按钮」组合,避免按钮溢出所在列挤掉列间距 */
.field-with-action { display: flex; align-items: center; gap: 8px; min-width: 0; }
.field-with-action > input, .field-with-action > select { flex: 1 1 auto; width: auto; }
.field-with-action > .btn { flex: 0 0 auto; white-space: nowrap; margin-top: 0; }
.visually-hidden { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0; overflow: hidden; white-space: nowrap; clip: rect(0 0 0 0); clip-path: inset(50%); }
/* 菜单列表拖动排序 */
@@ -3747,24 +3739,10 @@
#panel-query-asin .panel-box > .form-row .form-group, #panel-skip-price-asin .panel-box > .form-row .form-group { min-width: 0; }
#panel-query-asin .panel-box > .form-row .btn, #panel-skip-price-asin .panel-box > .form-row .btn { justify-self: start; }
/* 新增 ASIN 表单行:分组/店铺名/国家/ASIN 字段 + 按钮同一行、同一底端基线。
轨道 min ≥ 组内自然宽(含「管理分组」等附属按钮),否则按钮挤出轨道挤乱间距 */
#panel-query-asin .form-box > .form-row:not([style]), #panel-skip-price-asin .form-box > .form-row:not([style]) { display: grid; grid-template-columns: minmax(190px, 1.05fr) minmax(220px, 1.2fr) minmax(150px, 0.85fr) minmax(300px, 1.55fr) auto; gap: 12px; align-items: end; }
#panel-query-asin .form-box > .form-row:not([style]) .form-group, #panel-skip-price-asin .form-box > .form-row:not([style]) .form-group { min-width: 0; width: auto; }
/* 新增按钮与输入框底端对齐(各组都是 label+输入框 等高结构,align-items:end 即对齐) */
#panel-query-asin .form-box > .form-row:not([style]) .btn, #panel-skip-price-asin .form-box > .form-row:not([style]) .btn { align-self: flex-end; margin: 0; white-space: nowrap; }
/* 输入框 + 附属按钮组合:允许在 grid 轨道内收缩(内联 min-width 会撑破轨道) */
#panel-query-asin .form-box > .form-row:not([style]) .field-with-action, #panel-skip-price-asin .form-box > .form-row:not([style]) .field-with-action { min-width: 0; }
#panel-query-asin .form-box > .form-row:not([style]) .field-with-action > input, #panel-skip-price-asin .form-box > .form-row:not([style]) .field-with-action > input,
#panel-query-asin .form-box > .form-row:not([style]) .field-with-action > select, #panel-skip-price-asin .form-box > .form-row:not([style]) .field-with-action > select { min-width: 0; }
@media (max-width: 1400px) {
/* 轨道最小值之和超出内容宽度时折两列(同 .dedupe-filter-row 的窄屏策略),ASIN 组与按钮跨整行 */
/* 轨道最小值之和超出内容宽度时折两列(同 .dedupe-filter-row 的窄屏策略),按钮跨整行 */
#panel-query-asin .panel-box > .form-row, #panel-skip-price-asin .panel-box > .form-row { grid-template-columns: repeat(2, minmax(150px, 1fr)); }
#panel-query-asin .form-box > .form-row:not([style]), #panel-skip-price-asin .form-box > .form-row:not([style]) { grid-template-columns: repeat(2, minmax(190px, 1fr)); }
#panel-query-asin .form-box > .form-row:not([style]) .form-group:nth-child(4), #panel-skip-price-asin .form-box > .form-row:not([style]) .form-group:nth-child(4) { grid-column: 1 / -1; }
#panel-query-asin .panel-box > .form-row .btn, #panel-skip-price-asin .panel-box > .form-row .btn,
#panel-query-asin .form-box > .form-row:not([style]) .btn, #panel-skip-price-asin .form-box > .form-row:not([style]) .btn { grid-column: 1 / -1; justify-self: start; }
#panel-query-asin .panel-box > .form-row .btn, #panel-skip-price-asin .panel-box > .form-row .btn { grid-column: 1 / -1; justify-self: start; }
}
.category-table-scroll .category-path-value { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -3812,10 +3790,7 @@
.admin-content { padding: 18px 14px 32px; }
.table-scroll { border-radius: 9px; }
.shop-manage-table-scroll > table, .category-table-scroll > table, .query-asin-table-scroll > table, .skip-price-asin-table-scroll > table { min-width: 920px; }
#panel-query-asin .panel-box > .form-row, #panel-skip-price-asin .panel-box > .form-row,
#panel-query-asin .form-box > .form-row:not([style]), #panel-skip-price-asin .form-box > .form-row:not([style]) { grid-template-columns: minmax(0, 1fr); align-items: stretch; }
.skip-price-entry { grid-template-columns: 56px minmax(0, 1fr); }
.skip-price-entry .skip-price-minimum-input { grid-column: 2; }
#panel-query-asin .panel-box > .form-row, #panel-skip-price-asin .panel-box > .form-row { grid-template-columns: minmax(0, 1fr); align-items: stretch; }
}
@@ -4927,45 +4902,6 @@
</div>
</div>
<!-- 编辑店铺管理弹窗 -->
<div class="modal-mask" id="chooseSkipPriceAsinShopModal" style="z-index:1100;">
<div class="modal" style="max-width:900px;width:90%;max-height:80vh;display:flex;flex-direction:column;">
<h3>选择店铺</h3>
<div class="form-row">
<div class="form-group" style="min-width:180px;">
<label>分组</label>
<select id="chooseSkipPriceAsinShopGroupId">
<option value="">全部分组</option>
</select>
</div>
<div class="form-group" style="min-width:220px;">
<label>店铺名</label>
<input type="text" id="chooseSkipPriceAsinShopKeyword" placeholder="请输入店铺名">
</div>
<button class="btn" id="btnSearchChooseSkipPriceAsinShop" type="button">查询</button>
</div>
<div style="flex:1;min-height:0;overflow:auto;">
<table>
<thead>
<tr>
<th>序号</th>
<th>分组</th>
<th>店铺名</th>
<th>店铺商城名</th>
<th>账号</th>
<th>操作</th>
</tr>
</thead>
<tbody id="chooseSkipPriceAsinShopListBody"></tbody>
</table>
</div>
<div class="pagination" id="chooseSkipPriceAsinShopPagination"></div>
<div style="margin-top:16px;display:flex;gap:8px;justify-content:flex-end;">
<button class="btn btn-secondary" id="btnCloseChooseSkipPriceAsinShopModal" type="button">关闭</button>
</div>
</div>
</div>
<div class="drawer-mask" id="skipPriceAsinDrawer">
<div class="drawer" role="dialog" aria-modal="true" aria-labelledby="skipPriceAsinDrawerTitle">
<div class="drawer-header">
@@ -4988,44 +4924,6 @@
</div>
</div>
<div class="modal-mask" id="chooseQueryAsinShopModal" style="z-index:1100;">
<div class="modal" style="max-width:900px;width:90%;max-height:80vh;display:flex;flex-direction:column;">
<h3>选择店铺</h3>
<div class="form-row">
<div class="form-group" style="min-width:180px;">
<label>分组</label>
<select id="chooseQueryAsinShopGroupId">
<option value="">全部分组</option>
</select>
</div>
<div class="form-group" style="min-width:220px;">
<label>店铺名</label>
<input type="text" id="chooseQueryAsinShopKeyword" placeholder="请输入店铺名">
</div>
<button class="btn" id="btnSearchChooseQueryAsinShop" type="button">查询</button>
</div>
<div style="flex:1;min-height:0;overflow:auto;">
<table>
<thead>
<tr>
<th>序号</th>
<th>分组</th>
<th>店铺名</th>
<th>店铺商城名</th>
<th>账号</th>
<th>操作</th>
</tr>
</thead>
<tbody id="chooseQueryAsinShopListBody"></tbody>
</table>
</div>
<div class="pagination" id="chooseQueryAsinShopPagination"></div>
<div style="margin-top:16px;display:flex;gap:8px;justify-content:flex-end;">
<button class="btn btn-secondary" id="btnCloseChooseQueryAsinShopModal" type="button">关闭</button>
</div>
</div>
</div>
<div class="drawer-mask" id="queryAsinDrawer">
<div class="drawer" role="dialog" aria-modal="true" aria-labelledby="queryAsinDrawerTitle">
<div class="drawer-header">
@@ -5349,22 +5247,23 @@
<div class="modal-mask" id="createSkipPriceAsinModal">
<div class="modal">
<h3>新增 ASIN</h3>
<div class="form-row">
<div class="form-group">
<label>分组</label>
<select id="skipPriceAsinGroupSelect" style="min-width:200px;">
<label for="skipPriceAsinGroupSelect">分组</label>
<select id="skipPriceAsinGroupSelect">
<option value="">请选择分组</option>
</select>
</div>
<div class="form-group">
<label>店铺名</label>
<div class="field-with-action">
<input type="text" id="skipPriceAsinShopName" placeholder="请输入店铺名称">
<button class="btn btn-secondary" id="btnChooseSkipPriceAsinShop" type="button">选择店铺</button>
</div>
<label for="skipPriceAsinShopName">店铺名</label>
<select id="skipPriceAsinShopName" disabled>
<option value="">请先选择分组</option>
</select>
</div>
<div class="form-group">
<label>国家</label>
<select id="skipPriceAsinCountries" class="js-dropdown-multi" multiple>
<label for="skipPriceAsinCountry">国家</label>
<select id="skipPriceAsinCountry">
<option value="">请选择国家</option>
<option value="DE">德国</option>
<option value="UK">英国</option>
<option value="FR">法国</option>
@@ -5372,16 +5271,20 @@
<option value="ES">西班牙</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label id="skipPriceAsinInputsLabel">ASIN / 最低价</label>
<div id="skipPriceAsinInputs" role="group" aria-labelledby="skipPriceAsinInputsLabel"
style="display:flex;flex-direction:column;gap:8px;">
<div style="color:var(--c-text-3);font-size:13px;">请选择国家后输入对应 ASIN 和最低价</div>
<label for="skipPriceAsinValue">ASIN</label>
<input type="text" id="skipPriceAsinValue" placeholder="请输入 ASIN">
</div>
<div class="form-group" style="flex:0 0 180px;">
<label for="skipPriceAsinMinimumPrice">最低价</label>
<input type="number" id="skipPriceAsinMinimumPrice" min="0" step="0.01" placeholder="选填">
</div>
</div>
<p class="msg" id="msgSkipPriceAsin"></p>
<div style="margin-top:16px;display:flex;gap:8px;">
<button class="btn" id="btnCreateSkipPriceAsin">新增 ASIN</button>
<button class="btn" id="btnCreateSkipPriceAsin">保存</button>
<button class="btn btn-secondary" id="btnCloseCreateSkipPriceAsinModal" type="button">取消</button>
</div>
</div>
@@ -5447,22 +5350,23 @@
<div class="modal-mask" id="createQueryAsinModal">
<div class="modal">
<h3>新增查询 ASIN</h3>
<div class="form-row">
<div class="form-group">
<label>分组</label>
<select id="queryAsinGroupSelect" style="min-width:200px;">
<label for="queryAsinGroupSelect">分组</label>
<select id="queryAsinGroupSelect">
<option value="">请选择分组</option>
</select>
</div>
<div class="form-group">
<label>店铺名</label>
<div class="field-with-action">
<input type="text" id="queryAsinShopName" placeholder="请输入店铺名称">
<button class="btn btn-secondary" id="btnChooseQueryAsinShop" type="button">选择店铺</button>
</div>
<label for="queryAsinShopName">店铺名</label>
<select id="queryAsinShopName" disabled>
<option value="">请先选择分组</option>
</select>
</div>
<div class="form-group">
<label>国家</label>
<select id="queryAsinCountries" class="js-dropdown-multi" multiple>
<label for="queryAsinCountry">国家</label>
<select id="queryAsinCountry">
<option value="">请选择国家</option>
<option value="DE">德国</option>
<option value="UK">英国</option>
<option value="FR">法国</option>
@@ -5470,16 +5374,14 @@
<option value="ES">西班牙</option>
</select>
</div>
<div class="form-group">
<label id="queryAsinInputsLabel">ASIN</label>
<div id="queryAsinInputs" role="group" aria-labelledby="queryAsinInputsLabel"
style="display:flex;flex-direction:column;gap:8px;">
<div style="color:var(--c-text-3);font-size:13px;">请选择国家后输入对应 ASIN</div>
</div>
<div class="form-group">
<label for="queryAsinValue">ASIN</label>
<input type="text" id="queryAsinValue" placeholder="请输入 ASIN">
</div>
<p class="msg" id="msgQueryAsin"></p>
<div style="margin-top:16px;display:flex;gap:8px;">
<button class="btn" id="btnCreateQueryAsin">新增 ASIN</button>
<button class="btn" id="btnCreateQueryAsin">保存</button>
<button class="btn btn-secondary" id="btnCloseCreateQueryAsinModal" type="button">取消</button>
</div>
</div>
@@ -5654,7 +5556,7 @@
window.__initAdminMenuCollapse();
})();
</script>
<script src="/static/admin.js?v=admin-menu-v3"></script>
<script src="/static/admin.js?v=asin-create-v4"></script>
<div class="admin-toast-region" id="adminToastRegion" role="status" aria-live="polite" aria-atomic="true"></div>
<div class="admin-confirm-mask" id="adminConfirmModal" aria-hidden="true">
<section class="admin-confirm-dialog" role="alertdialog" aria-modal="true" aria-labelledby="adminConfirmTitle" aria-describedby="adminConfirmMessage">