minimumPriceMappings) {
+ SkipPriceAsinCreateRequest request = new SkipPriceAsinCreateRequest();
+ request.setGroupId(10L);
+ request.setShopName("shop-a");
+ request.setCountries(countries);
+ request.setAsinMappings(asinMappings);
+ request.setMinimumPriceMappings(minimumPriceMappings);
+ return request;
+ }
+
+ private File importWorkbook(String asin, String minimumPrice) throws Exception {
+ File file = File.createTempFile("skip-price-asin-test-", ".xlsx");
+ try (Workbook workbook = new XSSFWorkbook();
+ FileOutputStream outputStream = new FileOutputStream(file)) {
+ Sheet sheet = workbook.createSheet("import");
+ sheet.createRow(0).createCell(0).setCellValue("英国");
+ sheet.getRow(0).createCell(1).setCellValue("英国");
+ sheet.createRow(1).createCell(0).setCellValue("ASIN");
+ sheet.getRow(1).createCell(1).setCellValue("最低价");
+ sheet.createRow(2).createCell(0).setCellValue(asin);
+ sheet.getRow(2).createCell(1).setCellValue(minimumPrice);
+ workbook.write(outputStream);
+ }
+ return file;
+ }
}
diff --git a/backend/blueprints/admin_api.py b/backend/blueprints/admin_api.py
index 6ee436c3..95c8dcd1 100644
--- a/backend/blueprints/admin_api.py
+++ b/backend/blueprints/admin_api.py
@@ -1356,19 +1356,7 @@ _SHOP_DATA_CRAWL_ADMIN_COLUMNS = f"""
t.task_no, t.status AS task_status, t.request_json, t.result_json,
t.error_message AS task_error, t.created_at, t.updated_at, t.finished_at,
{_SHOP_DATA_CRAWL_LATEST_TIME_SQL} AS latest_file_updated_at,
- u.username,
- (SELECT j.id FROM biz_task_file_job j
- WHERE j.module_type = 'SHOP_DATA_CRAWL' AND j.result_id = r.id
- AND j.job_type = 'ASSEMBLE_RESULT'
- ORDER BY j.id DESC LIMIT 1) AS file_job_id,
- (SELECT j.status FROM biz_task_file_job j
- WHERE j.module_type = 'SHOP_DATA_CRAWL' AND j.result_id = r.id
- AND j.job_type = 'ASSEMBLE_RESULT'
- ORDER BY j.id DESC LIMIT 1) AS file_status,
- (SELECT j.error_message FROM biz_task_file_job j
- WHERE j.module_type = 'SHOP_DATA_CRAWL' AND j.result_id = r.id
- AND j.job_type = 'ASSEMBLE_RESULT'
- ORDER BY j.id DESC LIMIT 1) AS file_error
+ u.username
"""
@@ -1599,7 +1587,34 @@ def list_shop_data_crawl_tasks():
'ORDER BY ranked.latest_file_updated_at DESC, ranked.result_id DESC',
tuple(params + selected_shop_names),
)
- for row in cur.fetchall():
+ all_result_rows = cur.fetchall()
+
+ result_ids = [int(row['result_id']) for row in all_result_rows if row.get('result_id')]
+ file_job_map = {}
+ if result_ids:
+ fj_placeholders = ','.join(['%s'] * len(result_ids))
+ cur.execute(
+ 'SELECT fj.result_id, fj.id AS file_job_id, fj.status AS file_status, '
+ 'fj.error_message AS file_error '
+ 'FROM biz_task_file_job fj '
+ f'INNER JOIN (SELECT result_id, MAX(id) AS max_id '
+ f'FROM biz_task_file_job '
+ f"WHERE module_type = 'SHOP_DATA_CRAWL' AND job_type = 'ASSEMBLE_RESULT' "
+ f'AND result_id IN (' + fj_placeholders + ') '
+ f'GROUP BY result_id) latest '
+ 'ON fj.id = latest.max_id',
+ tuple(result_ids),
+ )
+ for fj_row in cur.fetchall():
+ file_job_map[int(fj_row['result_id'])] = fj_row
+
+ for row in all_result_rows:
+ rid = int(row.get('result_id') or 0)
+ fj = file_job_map.get(rid)
+ if fj:
+ row['file_job_id'] = fj.get('file_job_id')
+ row['file_status'] = fj.get('file_status')
+ row['file_error'] = fj.get('file_error')
shop_key = _shop_data_crawl_shop_key(row.get('shop_name'))
result_rows_by_shop.setdefault(shop_key, []).append(row)
finally:
@@ -2997,7 +3012,7 @@ def export_dedupe_total_data():
params=params,
headers={'X-Internal-Token': _resolve_internal_token()},
stream=True,
- timeout=60,
+ timeout=(10, 1800),
)
except requests.RequestException:
return jsonify({'success': False, 'error': 'backend-java 服务不可用'}), 502
@@ -3362,6 +3377,7 @@ def _format_shop_manage_item(item):
'group_name': item.get('groupName') or '',
'shop_name': item.get('shopName') or '',
'mall_name': item.get('mallName') or '',
+ 'zn_username': item.get('znUsername') or '',
'account': item.get('account') or '',
'password': item.get('passwordMasked') or '',
'created_at': (item.get('createdAt') or '').replace('T', ' ')[:16],
@@ -3493,7 +3509,11 @@ def get_shop_manage_credential(item_id):
credential = credential_result.get('data') or {}
if str(credential.get('id')) != str(item_id):
return jsonify({'success': False, 'error': '店铺凭据不匹配'}), 409
- response = jsonify({'success': True, 'password': credential.get('password') or ''})
+ response = jsonify({
+ 'success': True,
+ 'zn_username': credential.get('znUsername') or '',
+ 'password': credential.get('password') or '',
+ })
response.headers['Cache-Control'] = 'no-store'
return response
@@ -3509,6 +3529,7 @@ def create_shop_manage():
'groupId': data.get('group_id'),
'shopName': (data.get('shop_name') or '').strip(),
'mallName': (data.get('mall_name') or '').strip(),
+ 'znUsername': (data.get('zn_username') or '').strip(),
'account': (data.get('account') or '').strip(),
'password': (data.get('password') or '').strip(),
'createdById': current_row.get('id') if current_row else None,
@@ -3534,6 +3555,7 @@ def create_shop_manage():
'group_name': item.get('groupName') or '',
'shop_name': item.get('shopName') or '',
'mall_name': item.get('mallName') or '',
+ 'zn_username': item.get('znUsername') or '',
'account': item.get('account') or '',
'password': item.get('password') or '',
'created_at': (item.get('createdAt') or '').replace('T', ' ')[:16],
@@ -3556,6 +3578,8 @@ def update_shop_manage(item_id):
'account': (data.get('account') or '').strip(),
'password': (data.get('password') or '').strip(),
}
+ if 'zn_username' in data:
+ payload['znUsername'] = (data.get('zn_username') or '').strip()
result, error_response, status = _proxy_backend_java(
'PUT',
f'/api/admin/shop-manages/{item_id}',
@@ -3576,6 +3600,7 @@ def update_shop_manage(item_id):
'group_name': item.get('groupName') or '',
'shop_name': item.get('shopName') or '',
'mall_name': item.get('mallName') or '',
+ 'zn_username': item.get('znUsername') or '',
'account': item.get('account') or '',
'password': item.get('password') or '',
'created_at': (item.get('createdAt') or '').replace('T', ' ')[:16],
diff --git a/backend/static/admin.js b/backend/static/admin.js
index 8e15778a..74f82951 100644
--- a/backend/static/admin.js
+++ b/backend/static/admin.js
@@ -2245,7 +2245,27 @@
});
}
document.getElementById('btnSearchDedupeTotalData').onclick = function () { loadDedupeTotalData(1); };
- document.getElementById('btnExportDedupeTotalData').onclick = function () {
+ var dedupeTotalDataExportButton = document.getElementById('btnExportDedupeTotalData');
+ var dedupeTotalDataExportWait = document.getElementById('dedupeTotalDataExportWait');
+ var dedupeTotalDataExportWaitSeconds = document.getElementById('dedupeTotalDataExportWaitSeconds');
+ var dedupeTotalDataExportWaitTimer = null;
+ function showDedupeTotalDataExportWait() {
+ var startedAt = Date.now();
+ dedupeTotalDataExportWaitSeconds.textContent = '0';
+ dedupeTotalDataExportWait.classList.add('show');
+ dedupeTotalDataExportWait.setAttribute('aria-hidden', 'false');
+ dedupeTotalDataExportWaitTimer = setInterval(function () {
+ dedupeTotalDataExportWaitSeconds.textContent = String(Math.floor((Date.now() - startedAt) / 1000));
+ }, 1000);
+ }
+ function hideDedupeTotalDataExportWait() {
+ clearInterval(dedupeTotalDataExportWaitTimer);
+ dedupeTotalDataExportWaitTimer = null;
+ dedupeTotalDataExportWait.classList.remove('show');
+ dedupeTotalDataExportWait.setAttribute('aria-hidden', 'true');
+ }
+ dedupeTotalDataExportButton.onclick = function () {
+ if (dedupeTotalDataExportButton.disabled) return;
var username = (document.getElementById('searchDedupeTotalDataUsername').value || '').trim();
var groupId = (document.getElementById('dedupeTotalDataGroupFilterId').value || '').trim();
var dateRange = getDedupeTotalDataDateRange();
@@ -2255,6 +2275,11 @@
if (groupId) params.push('group_id=' + encodeURIComponent(groupId));
if (dateRange.startDate) params.push('start_date=' + encodeURIComponent(dateRange.startDate));
if (dateRange.endDate) params.push('end_date=' + encodeURIComponent(dateRange.endDate));
+ var originalButtonText = dedupeTotalDataExportButton.textContent;
+ dedupeTotalDataExportButton.disabled = true;
+ dedupeTotalDataExportButton.setAttribute('aria-busy', 'true');
+ dedupeTotalDataExportButton.textContent = '导出中...';
+ showDedupeTotalDataExportWait();
fetch('/api/admin/dedupe-total-data/export' + (params.length ? ('?' + params.join('&')) : ''))
.then(function (response) {
var contentType = response.headers.get('content-type') || '';
@@ -2278,6 +2303,12 @@
})
.catch(function (err) {
alert((err && err.message) || '导出失败');
+ })
+ .finally(function () {
+ dedupeTotalDataExportButton.disabled = false;
+ dedupeTotalDataExportButton.removeAttribute('aria-busy');
+ dedupeTotalDataExportButton.textContent = originalButtonText;
+ hideDedupeTotalDataExportWait();
});
};
var dedupeImportPollTimer = null;
@@ -2907,16 +2938,16 @@
.then(function (res) {
var tbody = document.getElementById('shopManageListBody');
if (!res.success) {
- tbody.innerHTML = '| 加载失败: ' + (res.error || '') + ' |
';
+ tbody.innerHTML = '| 加载失败: ' + (res.error || '') + ' |
';
return;
}
var items = res.items || [];
if (items.length === 0) {
- tbody.innerHTML = '| 暂无店铺 |
';
+ tbody.innerHTML = '| 暂无店铺 |
';
} else {
tbody.innerHTML = items.map(function (item, index) {
var rowNo = (shopManagePage - 1) * shopManagePageSize + index + 1;
- return '| ' + rowNo + ' | ' + (item.group_name || '') + ' | ' + (item.shop_name || '') + ' | ' + (item.mall_name || '') + ' | ' + (item.account || '') + ' | ' + renderShopPasswordCell(item) + ' | ' + (item.created_at || '') + ' | ' + (item.updated_at || '') + ' | ' +
+ return ' |
| ' + rowNo + ' | ' + (item.group_name || '') + ' | ' + (item.shop_name || '') + ' | ' + (item.mall_name || '') + ' | ' + (item.zn_username || '') + ' | ' + (item.account || '') + ' | ' + renderShopPasswordCell(item) + ' | ' + (item.created_at || '') + ' | ' + (item.updated_at || '') + ' | ' +
' ' +
'' +
' |
';
@@ -2926,7 +2957,7 @@
bindShopManageActions();
})
.catch(function () {
- document.getElementById('shopManageListBody').innerHTML = '| 请求失败 |
';
+ document.getElementById('shopManageListBody').innerHTML = '| 请求失败 |
';
});
}
@@ -2974,6 +3005,7 @@
document.getElementById('editShopManageId').value = item.id || '';
document.getElementById('editShopManageShopName').value = item.shop_name || '';
document.getElementById('editShopManageMallName').value = item.mall_name || '';
+ document.getElementById('editShopManageZnUsername').value = item.zn_username || '';
document.getElementById('editShopManageAccount').value = item.account || '';
document.getElementById('editShopManagePassword').value = item.password || '';
document.getElementById('msgEditShopManage').textContent = '';
@@ -3367,6 +3399,7 @@
var groupId = (document.getElementById('shopManageGroupSelect').value || '').trim();
var shopName = (document.getElementById('shopManageShopName').value || '').trim();
var mallName = (document.getElementById('shopManageMallName').value || '').trim();
+ var znUsername = (document.getElementById('shopManageZnUsername').value || '').trim();
var account = (document.getElementById('shopManageAccount').value || '').trim();
var password = (document.getElementById('shopManagePassword').value || '').trim();
var msgEl = document.getElementById('msgShopManage');
@@ -3380,7 +3413,7 @@
fetch('/api/admin/shop-manage', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ group_id: Number(groupId), shop_name: shopName, mall_name: mallName, account: account, password: password })
+ body: JSON.stringify({ group_id: Number(groupId), shop_name: shopName, mall_name: mallName, zn_username: znUsername, account: account, password: password })
})
.then(function (r) { return r.json(); })
.then(function (res) {
@@ -3388,6 +3421,7 @@
document.getElementById('shopManageGroupSelect').value = '';
document.getElementById('shopManageShopName').value = '';
document.getElementById('shopManageMallName').value = '';
+ document.getElementById('shopManageZnUsername').value = '';
document.getElementById('shopManageAccount').value = '';
document.getElementById('shopManagePassword').value = '';
msgEl.textContent = res.msg || '创建成功';
@@ -3409,6 +3443,7 @@
var groupId = (document.getElementById('editShopManageGroupSelect').value || '').trim();
var shopName = (document.getElementById('editShopManageShopName').value || '').trim();
var mallName = (document.getElementById('editShopManageMallName').value || '').trim();
+ var znUsername = (document.getElementById('editShopManageZnUsername').value || '').trim();
var account = (document.getElementById('editShopManageAccount').value || '').trim();
var password = (document.getElementById('editShopManagePassword').value || '').trim();
var msgEl = document.getElementById('msgEditShopManage');
@@ -3422,7 +3457,7 @@
fetch('/api/admin/shop-manage/' + itemId, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ group_id: Number(groupId), shop_name: shopName, mall_name: mallName, account: account, password: password })
+ body: JSON.stringify({ group_id: Number(groupId), shop_name: shopName, mall_name: mallName, zn_username: znUsername, account: account, password: password })
})
.then(function (r) { return r.json(); })
.then(function (res) {
diff --git a/backend/tests/test_admin_dedupe_total_data.py b/backend/tests/test_admin_dedupe_total_data.py
index a0d34adf..40693950 100644
--- a/backend/tests/test_admin_dedupe_total_data.py
+++ b/backend/tests/test_admin_dedupe_total_data.py
@@ -152,6 +152,7 @@ class AdminDedupeTotalDataTest(unittest.TestCase):
})
self.assertEqual(session.kwargs['headers'], {'X-Internal-Token': 'token'})
self.assertTrue(session.kwargs['stream'])
+ self.assertEqual(session.kwargs['timeout'], (10, 1800))
def test_import_requires_group(self):
with self.app.test_request_context(
diff --git a/backend/web_source/admin.html b/backend/web_source/admin.html
index ce01b3ba..0d3f4959 100644
--- a/backend/web_source/admin.html
+++ b/backend/web_source/admin.html
@@ -291,6 +291,52 @@
display: flex;
}
+ .dedupe-export-wait-mask {
+ position: fixed;
+ inset: 0;
+ z-index: 3100;
+ display: none;
+ align-items: center;
+ justify-content: center;
+ padding: 20px;
+ background: rgba(17, 24, 39, 0.38);
+ }
+
+ .dedupe-export-wait-mask.show {
+ display: flex;
+ }
+
+ .dedupe-export-wait {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ width: min(440px, calc(100vw - 40px));
+ padding: 20px;
+ border-radius: 8px;
+ background: #fff;
+ box-shadow: 0 16px 40px rgba(0, 0, 0, 0.2);
+ }
+
+ .dedupe-export-wait .request-spinner {
+ flex: 0 0 auto;
+ width: 24px;
+ height: 24px;
+ border-width: 3px;
+ }
+
+ .dedupe-export-wait-title {
+ margin-bottom: 4px;
+ color: #1f2937;
+ font-size: 15px;
+ font-weight: 600;
+ }
+
+ .dedupe-export-wait-detail {
+ color: #687386;
+ font-size: 13px;
+ line-height: 1.5;
+ }
+
.request-spinner {
width: 16px;
height: 16px;
@@ -1721,6 +1767,18 @@
请求处理中...
+
+
+
+
+
正在生成导出文件
+
+ 数据量较大,请耐心等待并保持页面打开。已等待 0 秒
+
+
+
+
管理后台
@@ -2188,6 +2246,10 @@
+
+
+
+
@@ -2222,6 +2284,7 @@
分组 |
店铺名 |
店铺商城名 |
+ 自动化账号 |
账号 |
密码 |
创建时间 |
@@ -3003,6 +3066,10 @@
+
+
+
+
@@ -3108,7 +3175,7 @@
-
+