task-116: 后台管理新增/导入表单弹窗化 + 分组管理独立菜单与UI优化 + 轻量进度端点
Build Backend JAR / build (push) Has been cancelled

- 后台管理页(admin)所有面板的新增/导入表单改为弹窗操作,原有字段 ID 全部保留、提交逻辑不变;导入删除入口不再触发二次确认拦截
- 分组管理升级为独立菜单(V102 + schema initializer),移除 5 个面板内的管理分组按钮;分组列表改为蓝白主题、增加权限分组横幅
- Python 侧 group-manage 权限守卫(_ensure_backend_menu_access 补充 group-manage)
- 引入 V101(biz_task_file_job 复合索引)+ 新增 TaskProgressLight/TaskFileJob 轻量端点与进度聚合作
- 前端 progress-light / page-separated-loads / dispatch-guard 共享模块及单元测试
This commit is contained in:
2026-09-01 12:54:13 +08:00
parent 759f0b15d8
commit fa5a59e5cd
139 changed files with 10540 additions and 8834 deletions
+48 -4
View File
@@ -64,6 +64,11 @@ ADMIN_MENU_ACCESS_CONFIG = {
'route_path': 'dedupe-total-data',
'error': '无权访问数据去重汇总数据',
},
'group-manage': {
'column_key': 'admin_group_manage',
'route_path': 'group-manage',
'error': '无权访问分组管理模块',
},
'shop-manage': {
'column_key': 'admin_shop_manage',
'route_path': 'shop-manage',
@@ -1523,6 +1528,7 @@ def list_shop_data_crawl_tasks():
page_size = min(100, max(10, int(request.args.get('page_size', 20))))
shop_name = (request.args.get('shop_name') or request.args.get('shop') or '').strip()
group_name = (request.args.get('group_name') or request.args.get('group') or '').strip()
country = (request.args.get('country') or '').strip().upper()
created_from = _parse_admin_datetime_arg('created_from')
created_to = _parse_admin_datetime_arg('created_to')
@@ -1544,6 +1550,16 @@ def list_shop_data_crawl_tasks():
"AND COALESCE(NULLIF(g.group_name, ''), NULLIF(sm.group_name, '')) LIKE %s)"
)
params.append('%' + group_name + '%')
if country:
if country not in ('DE', 'UK', 'FR', 'IT', 'ES'):
raise ValueError('不支持的国家代码: ' + country)
conditions.append(
'JSON_CONTAINS('
'COALESCE(df.country_codes_json, '
'JSON_EXTRACT(t.request_json, \'$.countryCodes\'), '
'JSON_EXTRACT(t.request_json, \'$.country_codes\'), \'[]\'), %s)'
)
params.append('"' + country + '"')
if created_from:
conditions.append('t.created_at >= %s')
params.append(created_from)
@@ -3352,12 +3368,16 @@ def list_invalid_asin_data():
page = max(1, int(request.args.get('page', 1)))
page_size = min(100, max(1, int(request.args.get('page_size', 15))))
keyword = (request.args.get('keyword') or '').strip()
data_value = (request.args.get('data_value') or '').strip()
brand = (request.args.get('brand') or '').strip()
group_id_raw = (request.args.get('group_id') or '').strip()
group_id = int(group_id_raw) if group_id_raw.isdigit() and int(group_id_raw) > 0 else None
params = {
'page': page,
'pageSize': page_size,
'keyword': keyword,
'dataValue': data_value,
'brand': brand,
}
if group_id is not None:
params['groupId'] = group_id
@@ -3766,7 +3786,7 @@ def delete_shop_manage(item_id):
@login_required
def list_shop_manage_groups():
role, current_row, denied = _ensure_backend_menu_access(
'shop-manage', 'skip-price-asin', 'query-asin', 'dedupe-total-data', 'invalid-asin-data'
'group-manage', 'shop-manage', 'skip-price-asin', 'query-asin', 'dedupe-total-data', 'invalid-asin-data'
)
if denied:
return denied
@@ -3788,7 +3808,7 @@ def list_shop_manage_groups():
@login_required
def create_shop_manage_group():
role, current_row, denied = _ensure_backend_menu_access(
'shop-manage', 'skip-price-asin', 'query-asin', 'dedupe-total-data', 'invalid-asin-data'
'group-manage', 'shop-manage', 'skip-price-asin', 'query-asin', 'dedupe-total-data', 'invalid-asin-data'
)
if denied:
return denied
@@ -3835,7 +3855,7 @@ def create_shop_manage_group():
@login_required
def update_shop_manage_group(item_id):
role, current_row, denied = _ensure_backend_menu_access(
'shop-manage', 'skip-price-asin', 'query-asin', 'dedupe-total-data', 'invalid-asin-data'
'group-manage', 'shop-manage', 'skip-price-asin', 'query-asin', 'dedupe-total-data', 'invalid-asin-data'
)
if denied:
return denied
@@ -3888,6 +3908,9 @@ def list_skip_price_asins():
group_id_raw = (request.args.get('group_id') or '').strip()
shop_name = (request.args.get('shop_name') or '').strip()
asin = (request.args.get('asin') or '').strip()
country = (request.args.get('country') or '').strip()
minimum_price_from = (request.args.get('minimum_price_from') or '').strip()
minimum_price_to = (request.args.get('minimum_price_to') or '').strip()
params = {'page': page, 'page_size': page_size, 'pageSize': page_size}
params.update(_skip_price_asin_auth_params(role, current_row))
if group_id_raw:
@@ -3903,6 +3926,12 @@ def list_skip_price_asins():
params['shopName'] = shop_name
if asin:
params['asin'] = asin
if country:
params['country'] = country
if minimum_price_from:
params['minimum_price_from'] = minimum_price_from
if minimum_price_to:
params['minimum_price_to'] = minimum_price_to
result, error_response, status = _proxy_backend_java(
'GET',
'/api/admin/skip-price-asins',
@@ -3962,6 +3991,9 @@ def export_skip_price_asins():
group_id_raw = (request.args.get('group_id') or request.args.get('groupId') or '').strip()
shop_name = (request.args.get('shop_name') or request.args.get('shopName') or '').strip()
asin = (request.args.get('asin') or '').strip()
country = (request.args.get('country') or '').strip()
minimum_price_from = (request.args.get('minimum_price_from') or '').strip()
minimum_price_to = (request.args.get('minimum_price_to') or '').strip()
if group_id_raw:
params['group_id'] = group_id_raw
params['groupId'] = group_id_raw
@@ -3970,6 +4002,12 @@ def export_skip_price_asins():
params['shopName'] = shop_name
if asin:
params['asin'] = asin
if country:
params['country'] = country
if minimum_price_from:
params['minimum_price_from'] = minimum_price_from
if minimum_price_to:
params['minimum_price_to'] = minimum_price_to
url = f"{backend_java_base_url}/api/admin/skip-price-asins/export"
try:
@@ -4082,7 +4120,7 @@ def delete_skip_price_asin_country(item_id, country):
@login_required
def delete_shop_manage_group(item_id):
role, current_row, denied = _ensure_backend_menu_access(
'shop-manage', 'skip-price-asin', 'query-asin', 'dedupe-total-data', 'invalid-asin-data'
'group-manage', 'shop-manage', 'skip-price-asin', 'query-asin', 'dedupe-total-data', 'invalid-asin-data'
)
if denied:
return denied
@@ -4276,6 +4314,7 @@ def list_query_asins():
group_id_raw = (request.args.get('group_id') or '').strip()
shop_name = (request.args.get('shop_name') or '').strip()
asin = (request.args.get('asin') or '').strip()
country = (request.args.get('country') or '').strip()
params = {'page': page, 'pageSize': page_size}
if current_row and current_row.get('id'):
params['operatorId'] = current_row.get('id')
@@ -4291,6 +4330,8 @@ def list_query_asins():
params['shopName'] = shop_name
if asin:
params['asin'] = asin
if country:
params['country'] = country
result, error_response, status = _proxy_backend_java(
'GET',
'/api/admin/query-asins',
@@ -4321,12 +4362,15 @@ def export_query_asins():
group_id_raw = (request.args.get('group_id') or request.args.get('groupId') or '').strip()
shop_name = (request.args.get('shop_name') or request.args.get('shopName') or '').strip()
asin = (request.args.get('asin') or '').strip()
country = (request.args.get('country') or '').strip()
if group_id_raw:
params['groupId'] = group_id_raw
if shop_name:
params['shopName'] = shop_name
if asin:
params['asin'] = asin
if country:
params['country'] = country
url = f"{backend_java_base_url}/api/admin/query-asins/export"
try:
+3 -3
View File
@@ -24,13 +24,13 @@
var guides = {
users: { text: '先用筛选定位账号,再编辑角色和菜单权限;删除账号会要求二次确认。', steps: ['筛选账号', '编辑权限', '确认保存'] },
columns: { text: '菜单会影响后台和软件端的可见范围。建议先填写名称、标识和页面路径,再设置上级菜单。', steps: ['新增或调整菜单', '设置层级', '检查权限'] },
columns: { text: '菜单会影响后台和软件端的可见范围。先填写名称并选择对应页面,再设置上级菜单;顺序直接拖动列表左侧手柄调整。', steps: ['新增或调整菜单', '设置层级', '拖动排序'] },
'dedupe-total-data': { text: '支持按关键词、用户、分组和国家筛选;导入前先确认所选分组,导出会按日期范围生成文件。', steps: ['选择分组', '筛选或导入', '核对并导出'] },
'invalid-asin-data': { text: '维护不符合规则的 ASIN 或品牌。添加后可使用上方筛选快速回查。', steps: ['填写 ASIN/品牌', '选择分组', '保存并回查'] },
'shop-keys': { text: '紫鸟令牌属于敏感配置。白名单状态可悬停查看检测详情,编辑前请先核对账号名称。', steps: ['新增或筛选密钥', '查看白名单状态', '编辑或删除'] },
'shop-manage': { text: '店铺信息按分组管理。长商城名会自动缩略,悬停即可查看完整内容。', steps: ['选择分组', '维护店铺信息', '筛选核对结果'] },
'skip-price-asin': { text: '最低价 ASIN 以店铺和国家为单位维护。每个国家可单独编辑 ASIN 与最低价。', steps: ['选择店铺和国家', '填写 ASIN/最低价', '保存或批量导入'] },
'query-asin': { text: '查询 ASIN 店铺和国家独立维护。空国家列可直接编辑,已有记录可修改或删除。', steps: ['选择店铺和国家', '维护各国 ASIN', '筛选或导出'] },
'skip-price-asin': { text: '最低价 ASIN 以店铺为单位维护。列表里点击 ASIN 即可复制,点右侧「配置」可一次维护该店铺所有站点的 ASIN 与最低价。', steps: ['筛选店铺', '打开配置抽屉', '保存或批量导入'] },
'query-asin': { text: '查询 ASIN 店铺为单位维护。列表里点击 ASIN 即可复制,点右侧「配置」可一次维护该店铺所有站点的 ASIN。', steps: ['筛选店铺', '打开配置抽屉', '保存或导出'] },
'product-categories': { text: '类目树支持展开查看层级。搜索、编辑和删除都在同一列表中完成。', steps: ['搜索类目', '展开层级', '新增或编辑'] },
'image-video-tasks': { text: '可先使用筛选缩小任务范围,再查看任务状态、结果和权限范围。', steps: ['设置筛选', '查看任务结果', '按需处理任务'] },
'shop-data-crawl-tasks': { text: '店铺数据任务按状态和时间筛选。批量操作前请核对已选任务。', steps: ['筛选任务', '检查状态', '执行批量操作'] },
+821 -363
View File
File diff suppressed because it is too large Load Diff
@@ -30,7 +30,7 @@ class AdminInvalidAsinDataTest(unittest.TestCase):
'pageSize': 15,
},
}
with self.app.test_request_context('/api/admin/invalid-asin-data?page=1&page_size=15&group_id=3'):
with self.app.test_request_context('/api/admin/invalid-asin-data?page=1&page_size=15&data_value=B01&brand=acme&group_id=3'):
with patch.object(
admin_api,
'_ensure_backend_menu_access',
@@ -58,6 +58,8 @@ class AdminInvalidAsinDataTest(unittest.TestCase):
'page': 1,
'pageSize': 15,
'keyword': '',
'dataValue': 'B01',
'brand': 'acme',
'groupId': 3,
},
)
File diff suppressed because it is too large Load Diff