后端优化,增加新需求
This commit is contained in:
@@ -2202,6 +2202,7 @@ def list_shop_keys():
|
||||
items = [
|
||||
{
|
||||
'id': item.get('id'),
|
||||
'remark_name': item.get('remarkName') or '',
|
||||
'ziniao_account_name': item.get('ziniaoAccountName') or '',
|
||||
'ziniao_token': item.get('ziniaoToken') or '',
|
||||
'created_at': (item.get('createdAt') or '').replace('T', ' ')[:16],
|
||||
@@ -2226,6 +2227,7 @@ def create_shop_key():
|
||||
return denied
|
||||
data = request.get_json() or {}
|
||||
payload = {
|
||||
'remarkName': (data.get('remark_name') or '').strip(),
|
||||
'ziniaoAccountName': (data.get('ziniao_account_name') or '').strip(),
|
||||
'ziniaoToken': (data.get('ziniao_token') or '').strip(),
|
||||
}
|
||||
@@ -2242,6 +2244,7 @@ def create_shop_key():
|
||||
'msg': result.get('message') or '创建成功',
|
||||
'item': {
|
||||
'id': item.get('id'),
|
||||
'remark_name': item.get('remarkName') or '',
|
||||
'ziniao_account_name': item.get('ziniaoAccountName') or '',
|
||||
'ziniao_token': item.get('ziniaoToken') or '',
|
||||
'created_at': (item.get('createdAt') or '').replace('T', ' ')[:16],
|
||||
@@ -2258,6 +2261,7 @@ def update_shop_key(item_id):
|
||||
return denied
|
||||
data = request.get_json() or {}
|
||||
payload = {
|
||||
'remarkName': (data.get('remark_name') or '').strip(),
|
||||
'ziniaoAccountName': (data.get('ziniao_account_name') or '').strip(),
|
||||
'ziniaoToken': (data.get('ziniao_token') or '').strip(),
|
||||
}
|
||||
@@ -2274,6 +2278,7 @@ def update_shop_key(item_id):
|
||||
'msg': result.get('message') or '更新成功',
|
||||
'item': {
|
||||
'id': item.get('id'),
|
||||
'remark_name': item.get('remarkName') or '',
|
||||
'ziniao_account_name': item.get('ziniaoAccountName') or '',
|
||||
'ziniao_token': item.get('ziniaoToken') or '',
|
||||
'created_at': (item.get('createdAt') or '').replace('T', ' ')[:16],
|
||||
@@ -2345,6 +2350,51 @@ def list_dedupe_total_data():
|
||||
})
|
||||
|
||||
|
||||
@admin_api.route('/dedupe-total-data/export')
|
||||
@login_required
|
||||
def export_dedupe_total_data():
|
||||
_, current_row, denied = _ensure_dedupe_total_data_access()
|
||||
if denied:
|
||||
return denied
|
||||
params = {'operatorId': current_row.get('id')}
|
||||
username = (request.args.get('username') or '').strip()
|
||||
start_date = (request.args.get('start_date') or request.args.get('startDate') or '').strip()
|
||||
end_date = (request.args.get('end_date') or request.args.get('endDate') or '').strip()
|
||||
if username:
|
||||
params['username'] = username
|
||||
if start_date:
|
||||
params['startDate'] = start_date
|
||||
if end_date:
|
||||
params['endDate'] = end_date
|
||||
|
||||
url = f"{backend_java_base_url}/api/admin/dedupe-total-data/export"
|
||||
try:
|
||||
resp = _get_backend_java_session().get(url, params=params, timeout=60)
|
||||
except requests.RequestException:
|
||||
return jsonify({'success': False, 'error': 'backend-java 服务不可用'}), 502
|
||||
if resp.status_code >= 400:
|
||||
try:
|
||||
data = resp.json()
|
||||
error = data.get('message') or data.get('error') or '导出失败'
|
||||
except ValueError:
|
||||
error = '导出失败'
|
||||
return jsonify({'success': False, 'error': error}), resp.status_code
|
||||
|
||||
headers = {}
|
||||
disposition = resp.headers.get('Content-Disposition')
|
||||
if disposition:
|
||||
headers['Content-Disposition'] = disposition
|
||||
return Response(
|
||||
resp.content,
|
||||
status=resp.status_code,
|
||||
headers=headers,
|
||||
content_type=resp.headers.get(
|
||||
'Content-Type',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@admin_api.route('/dedupe-total-data/import/<import_id>')
|
||||
@login_required
|
||||
def dedupe_total_data_import_progress(import_id):
|
||||
|
||||
@@ -1467,6 +1467,43 @@
|
||||
});
|
||||
}
|
||||
document.getElementById('btnSearchDedupeTotalData').onclick = function () { loadDedupeTotalData(1); };
|
||||
document.getElementById('btnExportDedupeTotalData').onclick = function () {
|
||||
var username = (document.getElementById('searchDedupeTotalDataUsername').value || '').trim();
|
||||
var startDate = document.getElementById('exportDedupeTotalDataStartDate').value || '';
|
||||
var endDate = document.getElementById('exportDedupeTotalDataEndDate').value || '';
|
||||
if (startDate && endDate && startDate > endDate) {
|
||||
alert('开始日期不能晚于结束日期');
|
||||
return;
|
||||
}
|
||||
var params = [];
|
||||
if (username) params.push('username=' + encodeURIComponent(username));
|
||||
if (startDate) params.push('start_date=' + encodeURIComponent(startDate));
|
||||
if (endDate) params.push('end_date=' + encodeURIComponent(endDate));
|
||||
fetch('/api/admin/dedupe-total-data/export' + (params.length ? ('?' + params.join('&')) : ''))
|
||||
.then(function (response) {
|
||||
var contentType = response.headers.get('content-type') || '';
|
||||
if (!response.ok || contentType.indexOf('application/json') >= 0) {
|
||||
return response.json().then(function (res) {
|
||||
throw new Error((res && (res.error || res.msg)) || '导出失败');
|
||||
});
|
||||
}
|
||||
return response.blob().then(function (blob) {
|
||||
return {
|
||||
blob: blob,
|
||||
filename: extractDownloadFilename(
|
||||
response.headers.get('content-disposition'),
|
||||
'dedupe-total-data.xlsx'
|
||||
)
|
||||
};
|
||||
});
|
||||
})
|
||||
.then(function (payload) {
|
||||
triggerBrowserDownload(payload.blob, payload.filename);
|
||||
})
|
||||
.catch(function (err) {
|
||||
alert((err && err.message) || '导出失败');
|
||||
});
|
||||
};
|
||||
var dedupeImportPollTimer = null;
|
||||
var dedupeDeleteImportPollTimer = null;
|
||||
function stopDedupeImportProgress() {
|
||||
@@ -1858,11 +1895,11 @@
|
||||
}
|
||||
var items = res.items || [];
|
||||
if (items.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="empty-tip">暂无店铺密钥</td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="7" class="empty-tip">暂无店铺密钥</td></tr>';
|
||||
} else {
|
||||
tbody.innerHTML = items.map(function (item, index) {
|
||||
var rowNo = (shopKeyPage - 1) * shopKeyPageSize + index + 1;
|
||||
return '<tr><td>' + rowNo + '</td><td>' + (item.ziniao_account_name || '') + '</td><td>' + (item.ziniao_token || '') + '</td><td>' + (item.created_at || '') + '</td><td>' + (item.updated_at || '') + '</td><td>' +
|
||||
return '<tr><td>' + rowNo + '</td><td>' + (item.remark_name || '') + '</td><td>' + (item.ziniao_account_name || '') + '</td><td>' + (item.ziniao_token || '') + '</td><td>' + (item.created_at || '') + '</td><td>' + (item.updated_at || '') + '</td><td>' +
|
||||
'<button class="btn btn-sm" data-shop-key-edit="' + item.id + '" data-shop-key="' + (JSON.stringify(item).replace(/"/g, '"')) + '">编辑</button> ' +
|
||||
'<button class="btn btn-sm btn-danger" data-shop-key-delete="' + item.id + '" data-ziniao-account-name="' + (item.ziniao_account_name || '').replace(/"/g, '"') + '">删除</button>' +
|
||||
'</td></tr>';
|
||||
@@ -1881,6 +1918,7 @@
|
||||
var item = {};
|
||||
try { item = JSON.parse((btn.dataset.shopKey || '').replace(/"/g, '"')); } catch (e) { item = {}; }
|
||||
document.getElementById('editShopKeyId').value = item.id || '';
|
||||
document.getElementById('editShopKeyRemarkName').value = item.remark_name || '';
|
||||
document.getElementById('editShopKeyZiniaoAccountName').value = item.ziniao_account_name || '';
|
||||
document.getElementById('editShopKeyZiniaoToken').value = item.ziniao_token || '';
|
||||
document.getElementById('msgEditShopKey').textContent = '';
|
||||
@@ -1902,6 +1940,7 @@
|
||||
});
|
||||
}
|
||||
document.getElementById('btnCreateShopKey').onclick = function () {
|
||||
var remarkName = (document.getElementById('shopKeyRemarkName').value || '').trim();
|
||||
var ziniaoAccountName = (document.getElementById('shopKeyZiniaoAccountName').value || '').trim();
|
||||
var ziniaoToken = (document.getElementById('shopKeyZiniaoToken').value || '').trim();
|
||||
var msgEl = document.getElementById('msgShopKey');
|
||||
@@ -1916,6 +1955,7 @@
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
remark_name: remarkName,
|
||||
ziniao_account_name: ziniaoAccountName,
|
||||
ziniao_token: ziniaoToken
|
||||
})
|
||||
@@ -1923,6 +1963,7 @@
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (res) {
|
||||
if (res.success) {
|
||||
document.getElementById('shopKeyRemarkName').value = '';
|
||||
document.getElementById('shopKeyZiniaoAccountName').value = '';
|
||||
document.getElementById('shopKeyZiniaoToken').value = '';
|
||||
msgEl.textContent = res.msg || '创建成功';
|
||||
@@ -1940,6 +1981,7 @@
|
||||
};
|
||||
document.getElementById('btnSaveShopKey').onclick = function () {
|
||||
var itemId = document.getElementById('editShopKeyId').value;
|
||||
var remarkName = (document.getElementById('editShopKeyRemarkName').value || '').trim();
|
||||
var ziniaoAccountName = (document.getElementById('editShopKeyZiniaoAccountName').value || '').trim();
|
||||
var ziniaoToken = (document.getElementById('editShopKeyZiniaoToken').value || '').trim();
|
||||
var msgEl = document.getElementById('msgEditShopKey');
|
||||
@@ -1954,6 +1996,7 @@
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
remark_name: remarkName,
|
||||
ziniao_account_name: ziniaoAccountName,
|
||||
ziniao_token: ziniaoToken
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import io
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from flask import jsonify
|
||||
|
||||
@@ -72,6 +72,36 @@ class DedupeTotalDataAdminTest(unittest.TestCase):
|
||||
self.assertEqual('import-1', response.get_json()['import_id'])
|
||||
self.assertEqual({'operatorId': 31}, proxy.call_args.kwargs['data'])
|
||||
|
||||
def test_export_forwards_filters_and_session_operator(self):
|
||||
self._login(31)
|
||||
java_response = Mock(
|
||||
status_code=200,
|
||||
content=b'xlsx-data',
|
||||
headers={
|
||||
'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'Content-Disposition': 'attachment; filename="dedupe-total-data.xlsx"',
|
||||
},
|
||||
)
|
||||
java_session = Mock()
|
||||
java_session.get.return_value = java_response
|
||||
with patch('utils.auth.is_session_user_valid', return_value=True), \
|
||||
patch.object(admin_module, '_ensure_dedupe_total_data_access',
|
||||
return_value=('normal', {'id': 31, 'username': 'member-b'}, None)), \
|
||||
patch.object(admin_module, '_get_backend_java_session', return_value=java_session):
|
||||
response = self.client.get(
|
||||
'/api/admin/dedupe-total-data/export'
|
||||
'?username=member&start_date=2026-07-01&end_date=2026-07-20')
|
||||
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.assertEqual(b'xlsx-data', response.data)
|
||||
self.assertEqual({
|
||||
'operatorId': 31,
|
||||
'username': 'member',
|
||||
'startDate': '2026-07-01',
|
||||
'endDate': '2026-07-20',
|
||||
}, java_session.get.call_args.kwargs['params'])
|
||||
self.assertEqual(60, java_session.get.call_args.kwargs['timeout'])
|
||||
|
||||
def test_delete_ignores_client_operator_and_uses_session_user(self):
|
||||
self._login(41)
|
||||
java_response = {'success': True, 'message': '删除成功', 'data': None}
|
||||
|
||||
@@ -162,6 +162,26 @@
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.dedupe-filter-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(160px, 1fr)) auto;
|
||||
gap: 12px;
|
||||
align-items: end;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.dedupe-filter-row .form-group {
|
||||
min-width: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.dedupe-filter-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
background: #667eea;
|
||||
@@ -651,6 +671,14 @@
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.dedupe-filter-row {
|
||||
grid-template-columns: repeat(2, minmax(180px, 1fr));
|
||||
}
|
||||
|
||||
.dedupe-filter-actions {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.dedupe-group-access {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
@@ -1282,6 +1310,15 @@
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.dedupe-filter-row {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.dedupe-filter-actions {
|
||||
grid-column: auto;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.admin-user-box {
|
||||
position: static !important;
|
||||
width: max-content;
|
||||
@@ -1608,16 +1645,27 @@
|
||||
</div>
|
||||
<div class="panel-box">
|
||||
<h3 style="margin-bottom:16px;font-size:15px;">ASIN列表</h3>
|
||||
<div class="form-row" style="margin-bottom:16px;">
|
||||
<div class="form-group" style="min-width:220px;">
|
||||
<div class="dedupe-filter-row">
|
||||
<div class="form-group">
|
||||
<label>数据值(模糊搜索)</label>
|
||||
<input type="text" id="searchDedupeTotalData" placeholder="输入关键字">
|
||||
</div>
|
||||
<div class="form-group" style="min-width:220px;">
|
||||
<div class="form-group">
|
||||
<label>用户名(模糊搜索)</label>
|
||||
<input type="text" id="searchDedupeTotalDataUsername" placeholder="输入用户名">
|
||||
</div>
|
||||
<button class=" btn" id="btnSearchDedupeTotalData">查询</button>
|
||||
<div class="form-group">
|
||||
<label>导出开始日期</label>
|
||||
<input type="date" id="exportDedupeTotalDataStartDate">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>导出结束日期</label>
|
||||
<input type="date" id="exportDedupeTotalDataEndDate">
|
||||
</div>
|
||||
<div class="dedupe-filter-actions">
|
||||
<button class="btn" id="btnSearchDedupeTotalData">查询</button>
|
||||
<button class="btn btn-secondary" id="btnExportDedupeTotalData" type="button">导出 XLSX</button>
|
||||
</div>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
@@ -1682,6 +1730,10 @@
|
||||
<div class="form-box">
|
||||
<h3 style="margin-bottom:16px;font-size:15px;">新增店铺密钥</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-group" style="min-width:180px;">
|
||||
<label>备注名</label>
|
||||
<input type="text" id="shopKeyRemarkName" maxlength="128" placeholder="请输入备注名(可选)">
|
||||
</div>
|
||||
<div class="form-group" style="min-width:220px;">
|
||||
<label>紫鸟账号名称</label>
|
||||
<input type="text" id="shopKeyZiniaoAccountName" placeholder="请输入紫鸟账号名称">
|
||||
@@ -1700,6 +1752,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>序号</th>
|
||||
<th>备注名</th>
|
||||
<th>紫鸟账号名称</th>
|
||||
<th>紫鸟令牌</th>
|
||||
<th>创建时间</th>
|
||||
@@ -2503,6 +2556,10 @@
|
||||
<div class="modal">
|
||||
<h3>编辑店铺密钥</h3>
|
||||
<input type="hidden" id="editShopKeyId">
|
||||
<div class="form-group">
|
||||
<label>备注名</label>
|
||||
<input type="text" id="editShopKeyRemarkName" maxlength="128" placeholder="备注名(可选)">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>紫鸟账号名称</label>
|
||||
<input type="text" id="editShopKeyZiniaoAccountName" placeholder="紫鸟账号名称">
|
||||
@@ -2550,7 +2607,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/admin.js?v=dedupe-groups-1"></script>
|
||||
<script src="/static/admin.js?v=shop-key-remark-1"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user