From f83bf11669c6a5f52a55904e4c9e702580b3787a Mon Sep 17 00:00:00 2001 From: hz4th_coder Date: Sat, 11 Jul 2026 00:14:08 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=80=E5=8C=96=E5=88=86=E7=B1=BB=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E5=AF=BC=E5=85=A5=EF=BC=8C=E4=BB=85=E5=A4=84=E7=90=86?= =?UTF-8?q?=E5=88=86=E7=B1=BB=E6=9C=AC=E8=BA=AB=E4=B8=8D=E5=90=AB=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api_categories.cpython-310.pyc | Bin 8310 -> 6720 bytes modules/routes/api_categories.py | 182 ++++-------------- templates/admin.html | 30 +-- 3 files changed, 38 insertions(+), 174 deletions(-) diff --git a/modules/routes/__pycache__/api_categories.cpython-310.pyc b/modules/routes/__pycache__/api_categories.cpython-310.pyc index 32fc2d1ccb91598a3b3126c919be6b4fd911835d..9c4cdb8e1f1bf4f6b409e1d2285f050ba79edf91 100644 GIT binary patch literal 6720 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 literal 8310 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', methods=['GET']) def api_export_single_category(category_id): - """导出单个分类及其相关数据""" + """导出单个分类配置""" try: categories = load_data(CATEGORIES_FILE) category = next((c for c in categories if c['id'] == category_id), None) @@ -143,32 +116,13 @@ def api_export_single_category(category_id): 'version': '1.0' } - # 加载该分类的数据 - if category_id == 'ai-models': - models_file = DATA_DIR / 'models.json' - if models_file.exists(): - export_data['items'] = load_data(models_file) - elif category_id == 'gpus': - gpus_file = DATA_DIR / 'gpus.json' - if gpus_file.exists(): - export_data['items'] = load_data(gpus_file) - elif category_id == 'cpus': - cpus_file = DATA_DIR / 'cpus.json' - if cpus_file.exists(): - export_data['items'] = load_data(cpus_file) - else: - # 动态分类 - cat_data_file = DATA_DIR / f'{category_id}.json' - if cat_data_file.exists(): - export_data['items'] = load_data(cat_data_file) - json_str = json.dumps(export_data, ensure_ascii=False, indent=2) response = Response( json_str, mimetype='application/json', headers={ - 'Content-Disposition': f'attachment; filename={category_id}-export-{datetime.now().strftime("%Y%m%d%H%M%S")}.json' + 'Content-Disposition': f'attachment; filename={category_id}-{datetime.now().strftime("%Y%m%d%H%M%S")}.json' } ) return response @@ -176,33 +130,42 @@ def api_export_single_category(category_id): return jsonify({'error': str(e)}), 500 -# ─── 导入分类相关数据 ─────────────────────────────────────────────────────── +# ─── 导入分类数据 ─────────────────────────────────────────────────────── -@cat_bp.route('/api/categories/import', methods=['POST']) +@cat_bp.route('/api/categories/import', methods=['GET', 'POST']) def api_import_categories(): - """导入分类及其相关数据""" + """导入分类配置(仅分类本身,不含数据项)""" try: + if request.method == 'GET': + # 返回导入接口说明 + return jsonify({ + 'endpoint': '/api/categories/import', + 'method': 'POST', + 'params': { + 'mode': 'merge(合并)或 replace(替换)' + }, + 'body': { + 'categories': '分类数组' + } + }) + import_data = request.get_json() if not import_data: return jsonify({'error': '无导入数据'}), 400 - # 验证数据结构 if 'categories' not in import_data: return jsonify({'error': '缺少 categories 字段'}), 400 - # 导入模式:merge(合并)或 replace(替换) mode = request.args.get('mode', 'merge') result = { 'success': True, - 'imported_categories': 0, - 'imported_items': 0, - 'skipped': [], - 'updated': [] + 'imported': 0, + 'updated': 0, + 'skipped': [] } - # 处理分类 existing_categories = load_data(CATEGORIES_FILE) imported_categories = import_data['categories'] @@ -210,68 +173,16 @@ def api_import_categories(): existing = next((c for c in existing_categories if c['id'] == cat['id']), None) if existing: if mode == 'replace': - # 替换模式:更新现有分类 existing.update(cat) - result['updated'].append(cat['id']) + result['updated'] += 1 else: - # 合并模式:跳过已存在的分类 result['skipped'].append(cat['id']) else: - # 新分类:添加 existing_categories.append(cat) - result['imported_categories'] += 1 + result['imported'] += 1 save_data(CATEGORIES_FILE, existing_categories) - # 导入各类别数据 - data_mappings = { - 'models': 'models.json', - 'gpus': 'gpus.json', - 'cpus': 'cpus.json' - } - - for key, filename in data_mappings.items(): - if key in import_data: - data_file = DATA_DIR / filename - existing_items = load_data(data_file) if data_file.exists() else [] - imported_items = import_data[key] - - for item in imported_items: - existing = next((i for i in existing_items if i['id'] == item['id']), None) - if existing: - if mode == 'replace': - existing.update(item) - else: - continue # 合并模式跳过 - else: - existing_items.append(item) - result['imported_items'] += 1 - - save_data(data_file, existing_items) - - # 导入动态分类数据 - for cat in imported_categories: - cat_id = cat['id'] - if cat_id in ['ai-models', 'gpus', 'cpus']: - continue - if cat_id in import_data: - data_file = DATA_DIR / f'{cat_id}.json' - existing_items = load_data(data_file) if data_file.exists() else [] - imported_items = import_data[cat_id] - - for item in imported_items: - existing = next((i for i in existing_items if i['id'] == item['id']), None) - if existing: - if mode == 'replace': - existing.update(item) - else: - continue - else: - existing_items.append(item) - result['imported_items'] += 1 - - save_data(data_file, existing_items) - return jsonify(result) except Exception as e: return jsonify({'error': str(e)}), 500 @@ -279,7 +190,7 @@ def api_import_categories(): @cat_bp.route('/api/categories/import/', methods=['POST']) def api_import_single_category(category_id): - """导入单个分类数据""" + """导入单个分类配置""" try: import_data = request.get_json() @@ -293,11 +204,10 @@ def api_import_single_category(category_id): result = { 'success': True, - 'imported_items': 0, - 'updated_items': 0 + 'imported': False, + 'updated': False } - # 处理分类 categories = load_data(CATEGORIES_FILE) imported_cat = import_data['category'] @@ -305,37 +215,13 @@ def api_import_single_category(category_id): if existing: if mode == 'replace': existing.update(imported_cat) + result['updated'] = True else: categories.append(imported_cat) + result['imported'] = True save_data(CATEGORIES_FILE, categories) - # 处理数据项 - if 'items' in import_data: - if category_id == 'ai-models': - data_file = DATA_DIR / 'models.json' - elif category_id == 'gpus': - data_file = DATA_DIR / 'gpus.json' - elif category_id == 'cpus': - data_file = DATA_DIR / 'cpus.json' - else: - data_file = DATA_DIR / f'{category_id}.json' - - existing_items = load_data(data_file) if data_file.exists() else [] - imported_items = import_data['items'] - - for item in imported_items: - existing = next((i for i in existing_items if i['id'] == item['id']), None) - if existing: - if mode == 'replace': - existing.update(item) - result['updated_items'] += 1 - else: - existing_items.append(item) - result['imported_items'] += 1 - - save_data(data_file, existing_items) - return jsonify(result) except Exception as e: - return jsonify({'error': str(e)}), 500 + return jsonify({'error': str(e)}), 500 \ No newline at end of file diff --git a/templates/admin.html b/templates/admin.html index a734441..e1104d6 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -2828,26 +2828,6 @@ html += `
单个分类: ${importData.category.name}
`; } - // 数据项 - const itemTypes = ['models', 'gpus', 'cpus', 'items']; - let totalItems = 0; - itemTypes.forEach(type => { - if (importData[type] && importData[type].length > 0) { - totalItems += importData[type].length; - html += `
${type}: ${importData[type].length} 条
`; - } - }); - - // 动态分类数据 - const dynamicCats = Object.keys(importData).filter(k => - k !== 'categories' && k !== 'category' && k !== 'export_time' && k !== 'version' && - !itemTypes.includes(k) && Array.isArray(importData[k]) - ); - dynamicCats.forEach(catId => { - totalItems += importData[catId].length; - html += `
${catId}: ${importData[catId].length} 条
`; - }); - html += `
导出时间: ${importData.export_time || '未知'}
`; html += '';content.innerHTML = html; } @@ -2860,15 +2840,14 @@ } const mode = document.getElementById('importMode').value; - const categoryId = document.getElementById('importCategoryId').value; try { let endpoint; - if (categoryId && importData.category) { + if (importData.category) { // 导入单个分类 - endpoint = `/api/categories/import/${categoryId}?mode=${mode}`; + endpoint = `/api/categories/import/${importData.category.id}?mode=${mode}`; } else { - // 导入全部 + // 导入全部分类 endpoint = `/api/categories/import?mode=${mode}`; } @@ -2884,7 +2863,7 @@ alert('导入失败: ' + result.error); } else { let msg = '导入成功!\n'; - msg += `新增分类: ${result.imported_categories || 0}\n`;msg += `新增数据: ${result.imported_items || 0}\n`;msg += `更新数据: ${result.updated_items || 0}`; + msg += `新增分类: ${result.imported || 0}\n`;msg += `更新分类: ${result.updated || 0}`; if (result.skipped && result.skipped.length > 0) { msg += `\n跳过已存在分类: ${result.skipped.join(', ')}`; } @@ -2894,7 +2873,6 @@ await loadCategories(); renderSidebar(); loadAdminCategories(); - loadOverview(); } } catch (e) { alert('导入失败: ' + e.message);