diff --git a/__pycache__/auth.cpython-310.pyc b/__pycache__/auth.cpython-310.pyc new file mode 100644 index 0000000..e93c07c Binary files /dev/null and b/__pycache__/auth.cpython-310.pyc differ diff --git a/__pycache__/config.cpython-310.pyc b/__pycache__/config.cpython-310.pyc new file mode 100644 index 0000000..db7dcf9 Binary files /dev/null and b/__pycache__/config.cpython-310.pyc differ diff --git a/__pycache__/llm.cpython-310.pyc b/__pycache__/llm.cpython-310.pyc new file mode 100644 index 0000000..0ac5831 Binary files /dev/null and b/__pycache__/llm.cpython-310.pyc differ diff --git a/__pycache__/utils.cpython-310.pyc b/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000..1a4d97e Binary files /dev/null and b/__pycache__/utils.cpython-310.pyc differ diff --git a/logs/app.log b/logs/app.log new file mode 100644 index 0000000..d510c50 --- /dev/null +++ b/logs/app.log @@ -0,0 +1,16 @@ +================================================== +ParamHub - 参数百科 v1.8.0 +================================================== +模块化重构 + 后台登录认证 +访问地址: http://localhost:16041 +后台管理: http://localhost:16041/admin +默认密码: admin123 (可在 config.json 中修改) +================================================== + * Serving Flask app 'app' + * Debug mode: off +WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. + * Running on all addresses (0.0.0.0) + * Running on http://127.0.0.1:16041 + * Running on http://192.168.0.101:16041 +Press CTRL+C to quit +127.0.0.1 - - [10/Jul/2026 23:59:48] "GET /api/categories/export HTTP/1.1" 200 - diff --git a/modules/__pycache__/__init__.cpython-310.pyc b/modules/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..0e25465 Binary files /dev/null and b/modules/__pycache__/__init__.cpython-310.pyc differ diff --git a/modules/routes/__pycache__/__init__.cpython-310.pyc b/modules/routes/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..c55b598 Binary files /dev/null and b/modules/routes/__pycache__/__init__.cpython-310.pyc differ diff --git a/modules/routes/__pycache__/api_categories.cpython-310.pyc b/modules/routes/__pycache__/api_categories.cpython-310.pyc new file mode 100644 index 0000000..32fc2d1 Binary files /dev/null and b/modules/routes/__pycache__/api_categories.cpython-310.pyc differ diff --git a/modules/routes/__pycache__/api_config.cpython-310.pyc b/modules/routes/__pycache__/api_config.cpython-310.pyc new file mode 100644 index 0000000..edddff2 Binary files /dev/null and b/modules/routes/__pycache__/api_config.cpython-310.pyc differ diff --git a/modules/routes/__pycache__/api_cpus.cpython-310.pyc b/modules/routes/__pycache__/api_cpus.cpython-310.pyc new file mode 100644 index 0000000..0fdea06 Binary files /dev/null and b/modules/routes/__pycache__/api_cpus.cpython-310.pyc differ diff --git a/modules/routes/__pycache__/api_gpus.cpython-310.pyc b/modules/routes/__pycache__/api_gpus.cpython-310.pyc new file mode 100644 index 0000000..511d132 Binary files /dev/null and b/modules/routes/__pycache__/api_gpus.cpython-310.pyc differ diff --git a/modules/routes/__pycache__/api_items.cpython-310.pyc b/modules/routes/__pycache__/api_items.cpython-310.pyc new file mode 100644 index 0000000..24097df Binary files /dev/null and b/modules/routes/__pycache__/api_items.cpython-310.pyc differ diff --git a/modules/routes/__pycache__/api_knowledge.cpython-310.pyc b/modules/routes/__pycache__/api_knowledge.cpython-310.pyc new file mode 100644 index 0000000..1d885e3 Binary files /dev/null and b/modules/routes/__pycache__/api_knowledge.cpython-310.pyc differ diff --git a/modules/routes/__pycache__/api_models.cpython-310.pyc b/modules/routes/__pycache__/api_models.cpython-310.pyc new file mode 100644 index 0000000..9a3af04 Binary files /dev/null and b/modules/routes/__pycache__/api_models.cpython-310.pyc differ diff --git a/modules/routes/__pycache__/api_pin.cpython-310.pyc b/modules/routes/__pycache__/api_pin.cpython-310.pyc new file mode 100644 index 0000000..2548ab4 Binary files /dev/null and b/modules/routes/__pycache__/api_pin.cpython-310.pyc differ diff --git a/modules/routes/__pycache__/api_search.cpython-310.pyc b/modules/routes/__pycache__/api_search.cpython-310.pyc new file mode 100644 index 0000000..fb3af0a Binary files /dev/null and b/modules/routes/__pycache__/api_search.cpython-310.pyc differ diff --git a/modules/routes/__pycache__/api_smart.cpython-310.pyc b/modules/routes/__pycache__/api_smart.cpython-310.pyc new file mode 100644 index 0000000..7bc055e Binary files /dev/null and b/modules/routes/__pycache__/api_smart.cpython-310.pyc differ diff --git a/modules/routes/__pycache__/api_upload.cpython-310.pyc b/modules/routes/__pycache__/api_upload.cpython-310.pyc new file mode 100644 index 0000000..52012eb Binary files /dev/null and b/modules/routes/__pycache__/api_upload.cpython-310.pyc differ diff --git a/modules/routes/__pycache__/pages.cpython-310.pyc b/modules/routes/__pycache__/pages.cpython-310.pyc new file mode 100644 index 0000000..e4f725d Binary files /dev/null and b/modules/routes/__pycache__/pages.cpython-310.pyc differ diff --git a/modules/routes/api_categories.py b/modules/routes/api_categories.py index 3a7ec16..9e83f1e 100644 --- a/modules/routes/api_categories.py +++ b/modules/routes/api_categories.py @@ -2,10 +2,11 @@ 分类管理 API """ import uuid +import json from datetime import datetime -from flask import Blueprint, request, jsonify -from config import CATEGORIES_FILE -from utils import load_data, save_data +from flask import Blueprint, request, jsonify, Response +from config import CATEGORIES_FILE, DATA_DIR +from utils import load_data, save_data, load_config cat_bp = Blueprint('api_categories', __name__) @@ -70,3 +71,271 @@ def api_toggle_category_visible(category_id): category['visible'] = not category.get('visible', True) save_data(CATEGORIES_FILE, categories) return jsonify({'success': True, 'visible': category['visible']}) + + +# ─── 导出分类相关数据 ─────────────────────────────────────────────────────── + +@cat_bp.route('/api/categories/export', methods=['GET']) +def api_export_categories(): + """导出所有分类及其相关数据(子类别、参数字段、关联数据项)""" + try: + # 获取所有分类 + categories = load_data(CATEGORIES_FILE) + + # 获取各类别下的数据 + export_data = { + 'categories': categories, + 'export_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'version': '1.0' + } + + # 加载各类别数据文件 + # 内置分类:AI模型、GPU、CPU + models_file = DATA_DIR / 'models.json' + gpus_file = DATA_DIR / 'gpus.json' + cpus_file = DATA_DIR / 'cpus.json' + + if models_file.exists(): + export_data['models'] = load_data(models_file) + if gpus_file.exists(): + export_data['gpus'] = load_data(gpus_file) + if cpus_file.exists(): + export_data['cpus'] = load_data(cpus_file) + + # 动态分类数据(每个自定义分类有独立的数据文件) + for cat in categories: + cat_id = cat['id'] + # 排除内置分类 + if cat_id in ['ai-models', 'gpus', 'cpus']: + continue + # 查找该分类的数据文件 + cat_data_file = DATA_DIR / f'{cat_id}.json' + if cat_data_file.exists(): + export_data[cat_id] = load_data(cat_data_file) + + # 导出为 JSON 文件 + json_str = json.dumps(export_data, ensure_ascii=False, indent=2) + + response = Response( + json_str, + mimetype='application/json', + headers={ + 'Content-Disposition': f'attachment; filename=param-hub-categories-export-{datetime.now().strftime("%Y%m%d%H%M%S")}.json' + } + ) + return response + except Exception as e: + return jsonify({'error': str(e)}), 500 + + +@cat_bp.route('/api/categories/export/', 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) + if not category: + return jsonify({'error': 'Category not found'}), 404 + + export_data = { + 'category': category, + 'export_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + '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' + } + ) + return response + except Exception as e: + return jsonify({'error': str(e)}), 500 + + +# ─── 导入分类相关数据 ─────────────────────────────────────────────────────── + +@cat_bp.route('/api/categories/import', methods=['POST']) +def api_import_categories(): + """导入分类及其相关数据""" + try: + 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': [] + } + + # 处理分类 + existing_categories = load_data(CATEGORIES_FILE) + imported_categories = import_data['categories'] + + for cat in imported_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']) + else: + # 合并模式:跳过已存在的分类 + result['skipped'].append(cat['id']) + else: + # 新分类:添加 + existing_categories.append(cat) + result['imported_categories'] += 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 + + +@cat_bp.route('/api/categories/import/', methods=['POST']) +def api_import_single_category(category_id): + """导入单个分类数据""" + try: + import_data = request.get_json() + + if not import_data: + return jsonify({'error': '无导入数据'}), 400 + + if 'category' not in import_data: + return jsonify({'error': '缺少 category 字段'}), 400 + + mode = request.args.get('mode', 'merge') + + result = { + 'success': True, + 'imported_items': 0, + 'updated_items': 0 + } + + # 处理分类 + categories = load_data(CATEGORIES_FILE) + imported_cat = import_data['category'] + + existing = next((c for c in categories if c['id'] == category_id), None) + if existing: + if mode == 'replace': + existing.update(imported_cat) + else: + categories.append(imported_cat) + + 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 diff --git a/templates/admin.html b/templates/admin.html index 44353c6..eb01437 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -128,7 +128,11 @@