Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01abe7f86f | ||
|
|
f83bf11669 | ||
|
|
bc2720257d |
+1
-1
@@ -13,4 +13,4 @@ WARNING: This is a development server. Do not use it in a production deployment.
|
||||
* 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 -
|
||||
127.0.0.1 - - [11/Jul/2026 00:34:38] "GET /api/models/export HTTP/1.1" 200 -
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -5,8 +5,8 @@ import uuid
|
||||
import json
|
||||
from datetime import datetime
|
||||
from flask import Blueprint, request, jsonify, Response
|
||||
from config import CATEGORIES_FILE, DATA_DIR
|
||||
from utils import load_data, save_data, load_config
|
||||
from config import CATEGORIES_FILE
|
||||
from utils import load_data, save_data
|
||||
|
||||
cat_bp = Blueprint('api_categories', __name__)
|
||||
|
||||
@@ -73,54 +73,27 @@ def api_toggle_category_visible(category_id):
|
||||
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'
|
||||
'Content-Disposition': f'attachment; filename=param-hub-categories-{datetime.now().strftime("%Y%m%d%H%M%S")}.json'
|
||||
}
|
||||
)
|
||||
return response
|
||||
@@ -130,7 +103,7 @@ def api_export_categories():
|
||||
|
||||
@cat_bp.route('/api/categories/export/<category_id>', 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/<category_id>', 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
|
||||
@@ -2,8 +2,9 @@
|
||||
CPU CRUD API
|
||||
"""
|
||||
import uuid
|
||||
import json
|
||||
from datetime import datetime
|
||||
from flask import Blueprint, request, jsonify
|
||||
from flask import Blueprint, request, jsonify, Response
|
||||
from config import CPUS_FILE
|
||||
from utils import load_data, save_data, parse_date_to_timestamp
|
||||
|
||||
@@ -95,3 +96,63 @@ def api_toggle_cpu_visible(cpu_id):
|
||||
cpu['visible'] = not cpu.get('visible', True)
|
||||
save_data(CPUS_FILE, cpus)
|
||||
return jsonify({'success': True, 'visible': cpu['visible']})
|
||||
|
||||
|
||||
# ─── 导出导入 ───────────────────────────────────────────────────────
|
||||
|
||||
@cpus_bp.route('/api/cpus/export', methods=['GET'])
|
||||
def api_export_cpus():
|
||||
"""导出所有CPU数据"""
|
||||
try:
|
||||
cpus = load_data(CPUS_FILE)
|
||||
export_data = {
|
||||
'type': 'cpus',
|
||||
'items': cpus,
|
||||
'count': len(cpus),
|
||||
'export_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'version': '1.0'
|
||||
}
|
||||
json_str = json.dumps(export_data, ensure_ascii=False, indent=2)
|
||||
response = Response(
|
||||
json_str,
|
||||
mimetype='application/json',
|
||||
headers={'Content-Disposition': f'attachment; filename=cpus-export-{datetime.now().strftime("%Y%m%d%H%M%S")}.json'}
|
||||
)
|
||||
return response
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@cpus_bp.route('/api/cpus/import', methods=['GET', 'POST'])
|
||||
def api_import_cpus():
|
||||
"""导入CPU数据"""
|
||||
try:
|
||||
if request.method == 'GET':
|
||||
return jsonify({'endpoint': '/api/cpus/import', 'method': 'POST', 'params': {'mode': 'merge 或 replace'}})
|
||||
|
||||
import_data = request.get_json()
|
||||
if not import_data or 'items' not in import_data:
|
||||
return jsonify({'error': '缺少 items 字段'}), 400
|
||||
|
||||
mode = request.args.get('mode', 'merge')
|
||||
cpus = load_data(CPUS_FILE)
|
||||
imported_items = import_data['items']
|
||||
|
||||
result = {'success': True, 'imported': 0, 'updated': 0, 'skipped': []}
|
||||
|
||||
for item in imported_items:
|
||||
existing = next((c for c in cpus if c['id'] == item['id']), None)
|
||||
if existing:
|
||||
if mode == 'replace':
|
||||
existing.update(item)
|
||||
result['updated'] += 1
|
||||
else:
|
||||
result['skipped'].append(item['id'])
|
||||
else:
|
||||
cpus.append(item)
|
||||
result['imported'] += 1
|
||||
|
||||
save_data(CPUS_FILE, cpus)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
@@ -2,8 +2,9 @@
|
||||
GPU CRUD API
|
||||
"""
|
||||
import uuid
|
||||
import json
|
||||
from datetime import datetime
|
||||
from flask import Blueprint, request, jsonify
|
||||
from flask import Blueprint, request, jsonify, Response
|
||||
from config import GPUS_FILE
|
||||
from utils import load_data, save_data, parse_date_to_timestamp
|
||||
|
||||
@@ -95,3 +96,63 @@ def api_toggle_gpu_visible(gpu_id):
|
||||
gpu['visible'] = not gpu.get('visible', True)
|
||||
save_data(GPUS_FILE, gpus)
|
||||
return jsonify({'success': True, 'visible': gpu['visible']})
|
||||
|
||||
|
||||
# ─── 导出导入 ───────────────────────────────────────────────────────
|
||||
|
||||
@gpus_bp.route('/api/gpus/export', methods=['GET'])
|
||||
def api_export_gpus():
|
||||
"""导出所有GPU数据"""
|
||||
try:
|
||||
gpus = load_data(GPUS_FILE)
|
||||
export_data = {
|
||||
'type': 'gpus',
|
||||
'items': gpus,
|
||||
'count': len(gpus),
|
||||
'export_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'version': '1.0'
|
||||
}
|
||||
json_str = json.dumps(export_data, ensure_ascii=False, indent=2)
|
||||
response = Response(
|
||||
json_str,
|
||||
mimetype='application/json',
|
||||
headers={'Content-Disposition': f'attachment; filename=gpus-export-{datetime.now().strftime("%Y%m%d%H%M%S")}.json'}
|
||||
)
|
||||
return response
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@gpus_bp.route('/api/gpus/import', methods=['GET', 'POST'])
|
||||
def api_import_gpus():
|
||||
"""导入GPU数据"""
|
||||
try:
|
||||
if request.method == 'GET':
|
||||
return jsonify({'endpoint': '/api/gpus/import', 'method': 'POST', 'params': {'mode': 'merge 或 replace'}})
|
||||
|
||||
import_data = request.get_json()
|
||||
if not import_data or 'items' not in import_data:
|
||||
return jsonify({'error': '缺少 items 字段'}), 400
|
||||
|
||||
mode = request.args.get('mode', 'merge')
|
||||
gpus = load_data(GPUS_FILE)
|
||||
imported_items = import_data['items']
|
||||
|
||||
result = {'success': True, 'imported': 0, 'updated': 0, 'skipped': []}
|
||||
|
||||
for item in imported_items:
|
||||
existing = next((g for g in gpus if g['id'] == item['id']), None)
|
||||
if existing:
|
||||
if mode == 'replace':
|
||||
existing.update(item)
|
||||
result['updated'] += 1
|
||||
else:
|
||||
result['skipped'].append(item['id'])
|
||||
else:
|
||||
gpus.append(item)
|
||||
result['imported'] += 1
|
||||
|
||||
save_data(GPUS_FILE, gpus)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
@@ -2,8 +2,9 @@
|
||||
动态分类数据 API
|
||||
"""
|
||||
import uuid
|
||||
import json
|
||||
from datetime import datetime
|
||||
from flask import Blueprint, request, jsonify
|
||||
from flask import Blueprint, request, jsonify, Response
|
||||
from config import DATA_DIR
|
||||
from utils import load_data, save_data, parse_date_to_timestamp
|
||||
|
||||
@@ -95,3 +96,66 @@ def api_toggle_item_visible(category_id, item_id):
|
||||
item['visible'] = not item.get('visible', True)
|
||||
save_data(items_file, items)
|
||||
return jsonify({'success': True, 'visible': item['visible']})
|
||||
|
||||
|
||||
# ─── 导出导入 ───────────────────────────────────────────────────────
|
||||
|
||||
@items_bp.route('/api/items/<category_id>/export', methods=['GET'])
|
||||
def api_export_items(category_id):
|
||||
"""导出指定分类的所有数据"""
|
||||
try:
|
||||
items_file = DATA_DIR / f'items_{category_id}.json'
|
||||
items = load_data(items_file)
|
||||
export_data = {
|
||||
'type': 'items',
|
||||
'category_id': category_id,
|
||||
'items': items,
|
||||
'count': len(items),
|
||||
'export_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'version': '1.0'
|
||||
}
|
||||
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
|
||||
|
||||
|
||||
@items_bp.route('/api/items/<category_id>/import', methods=['GET', 'POST'])
|
||||
def api_import_items(category_id):
|
||||
"""导入指定分类的数据"""
|
||||
try:
|
||||
if request.method == 'GET':
|
||||
return jsonify({'endpoint': f'/api/items/{category_id}/import', 'method': 'POST', 'params': {'mode': 'merge 或 replace'}})
|
||||
|
||||
import_data = request.get_json()
|
||||
if not import_data or 'items' not in import_data:
|
||||
return jsonify({'error': '缺少 items 字段'}), 400
|
||||
|
||||
mode = request.args.get('mode', 'merge')
|
||||
items_file = DATA_DIR / f'items_{category_id}.json'
|
||||
items = load_data(items_file)
|
||||
imported_items = import_data['items']
|
||||
|
||||
result = {'success': True, 'imported': 0, 'updated': 0, 'skipped': []}
|
||||
|
||||
for item in imported_items:
|
||||
existing = next((i for i in items if i['id'] == item['id']), None)
|
||||
if existing:
|
||||
if mode == 'replace':
|
||||
existing.update(item)
|
||||
result['updated'] += 1
|
||||
else:
|
||||
result['skipped'].append(item['id'])
|
||||
else:
|
||||
items.append(item)
|
||||
result['imported'] += 1
|
||||
|
||||
save_data(items_file, items)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
@@ -2,8 +2,9 @@
|
||||
AI模型 CRUD API
|
||||
"""
|
||||
import uuid
|
||||
import json
|
||||
from datetime import datetime
|
||||
from flask import Blueprint, request, jsonify
|
||||
from flask import Blueprint, request, jsonify, Response
|
||||
from config import MODELS_FILE
|
||||
from utils import load_data, save_data, parse_date_to_timestamp, safe_sort_key
|
||||
|
||||
@@ -88,3 +89,63 @@ def api_toggle_model_visible(model_id):
|
||||
model['visible'] = not model.get('visible', True)
|
||||
save_data(MODELS_FILE, models)
|
||||
return jsonify({'success': True, 'visible': model['visible']})
|
||||
|
||||
|
||||
# ─── 导出导入 ───────────────────────────────────────────────────────
|
||||
|
||||
@models_bp.route('/api/models/export', methods=['GET'])
|
||||
def api_export_models():
|
||||
"""导出所有模型数据"""
|
||||
try:
|
||||
models = load_data(MODELS_FILE)
|
||||
export_data = {
|
||||
'type': 'models',
|
||||
'items': models,
|
||||
'count': len(models),
|
||||
'export_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'version': '1.0'
|
||||
}
|
||||
json_str = json.dumps(export_data, ensure_ascii=False, indent=2)
|
||||
response = Response(
|
||||
json_str,
|
||||
mimetype='application/json',
|
||||
headers={'Content-Disposition': f'attachment; filename=models-export-{datetime.now().strftime("%Y%m%d%H%M%S")}.json'}
|
||||
)
|
||||
return response
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@models_bp.route('/api/models/import', methods=['GET', 'POST'])
|
||||
def api_import_models():
|
||||
"""导入模型数据"""
|
||||
try:
|
||||
if request.method == 'GET':
|
||||
return jsonify({'endpoint': '/api/models/import', 'method': 'POST', 'params': {'mode': 'merge 或 replace'}})
|
||||
|
||||
import_data = request.get_json()
|
||||
if not import_data or 'items' not in import_data:
|
||||
return jsonify({'error': '缺少 items 字段'}), 400
|
||||
|
||||
mode = request.args.get('mode', 'merge')
|
||||
models = load_data(MODELS_FILE)
|
||||
imported_items = import_data['items']
|
||||
|
||||
result = {'success': True, 'imported': 0, 'updated': 0, 'skipped': []}
|
||||
|
||||
for item in imported_items:
|
||||
existing = next((m for m in models if m['id'] == item['id']), None)
|
||||
if existing:
|
||||
if mode == 'replace':
|
||||
existing.update(item)
|
||||
result['updated'] += 1
|
||||
else:
|
||||
result['skipped'].append(item['id'])
|
||||
else:
|
||||
models.append(item)
|
||||
result['imported'] += 1
|
||||
|
||||
save_data(MODELS_FILE, models)
|
||||
return jsonify(result)
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
+205
-113
@@ -160,6 +160,8 @@
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800" id="dynamic-title">数据管理</h1>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="exportCategoryData(dynamicCategoryId)" class="px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700"><i class="ri-download-line mr-2"></i>导出</button>
|
||||
<button onclick="openDataImportModal(dynamicCategoryId)" class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700"><i class="ri-upload-line mr-2"></i>导入</button>
|
||||
<button onclick="openSmartAddModal('dynamic')" class="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700"><i class="ri-magic-line mr-2"></i>智能添加</button>
|
||||
<button onclick="openAddModal('dynamic')" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700"><i class="ri-add-line mr-2"></i>手动添加</button>
|
||||
</div>
|
||||
@@ -179,6 +181,8 @@
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800">大模型管理</h1>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="exportModels()" class="px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700"><i class="ri-download-line mr-2"></i>导出</button>
|
||||
<button onclick="openDataImportModal('models')" class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700"><i class="ri-upload-line mr-2"></i>导入</button>
|
||||
<button onclick="openSmartAddModal('model')" class="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700"><i class="ri-magic-line mr-2"></i>智能添加</button>
|
||||
<button onclick="openAddModal('model')" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700"><i class="ri-add-line mr-2"></i>手动添加</button>
|
||||
</div>
|
||||
@@ -200,6 +204,8 @@
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800">GPU管理</h1>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="exportGpus()" class="px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700"><i class="ri-download-line mr-2"></i>导出</button>
|
||||
<button onclick="openDataImportModal('gpus')" class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700"><i class="ri-upload-line mr-2"></i>导入</button>
|
||||
<button onclick="openSmartAddModal('gpu')" class="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700"><i class="ri-magic-line mr-2"></i>智能添加</button>
|
||||
<button onclick="openAddModal('gpu')" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700"><i class="ri-add-line mr-2"></i>手动添加</button>
|
||||
</div>
|
||||
@@ -221,6 +227,8 @@
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800">CPU管理</h1>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="exportCpus()" class="px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700"><i class="ri-download-line mr-2"></i>导出</button>
|
||||
<button onclick="openDataImportModal('cpus')" class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700"><i class="ri-upload-line mr-2"></i>导入</button>
|
||||
<button onclick="openSmartAddModal('cpu')" class="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700"><i class="ri-magic-line mr-2"></i>智能添加</button>
|
||||
<button onclick="openAddModal('cpu')" class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700"><i class="ri-add-line mr-2"></i>手动添加</button>
|
||||
</div>
|
||||
@@ -501,20 +509,18 @@
|
||||
<div id="importModal" class="fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center">
|
||||
<div class="bg-white rounded-xl max-w-2xl w-full mx-4">
|
||||
<div class="p-6 border-b flex justify-between items-center">
|
||||
<h2 class="text-xl font-bold text-gray-800"><i class="ri-upload-line mr-2 text-purple-600"></i>导入分类数据</h2>
|
||||
<h2 class="text-xl font-bold text-gray-800"><i class="ri-upload-line mr-2 text-purple-600"></i>导入分类配置</h2>
|
||||
<button onclick="closeImportModal()" class="text-gray-400 hover:text-gray-600"><i class="ri-close-line text-2xl"></i></button>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="bg-purple-50 rounded-lg p-4 mb-4">
|
||||
<p class="text-sm text-purple-700"><i class="ri-information-line mr-1"></i>导入分类数据时,可选择<strong>合并模式</strong>(保留现有数据,只添加新数据)或<strong>替换模式</strong>(覆盖现有数据)。</p>
|
||||
<p class="text-sm text-purple-700"><i class="ri-information-line mr-1"></i>导入分类配置。可选择<strong>合并模式</strong>(保留现有数据)或<strong>替换模式</strong>(覆盖现有数据)。</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="text-sm text-gray-600 mb-2 block">选择导入文件(JSON格式)</label>
|
||||
<input type="file" id="importFileInput" accept=".json" class="w-full px-4 py-2 border rounded-lg" onchange="handleImportFile(event)">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-sm text-gray-600 mb-2 block">导入模式</label>
|
||||
<select id="importMode" class="w-full px-4 py-2 border rounded-lg">
|
||||
@@ -522,8 +528,6 @@
|
||||
<option value="replace">替换模式 - 覆盖已存在的数据</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 预览区域 -->
|
||||
<div id="importPreview" class="hidden border rounded-lg p-4 bg-gray-50">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-3"><i class="ri-eye-line mr-1"></i>导入预览</h3>
|
||||
<div id="importPreviewContent"></div>
|
||||
@@ -537,6 +541,42 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据导入弹窗 -->
|
||||
<div id="dataImportModal" class="fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center">
|
||||
<div class="bg-white rounded-xl max-w-2xl w-full mx-4">
|
||||
<div class="p-6 border-b flex justify-between items-center">
|
||||
<h2 class="text-xl font-bold text-gray-800"><i class="ri-upload-line mr-2 text-teal-600"></i>导入数据</h2>
|
||||
<button onclick="closeDataImportModal()" class="text-gray-400 hover:text-gray-600"><i class="ri-close-line text-2xl"></i></button>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="bg-teal-50 rounded-lg p-4 mb-4">
|
||||
<p class="text-sm text-teal-700"><i class="ri-information-line mr-1"></i>导入数据条目。可选择<strong>合并模式</strong>(保留现有数据)或<strong>替换模式</strong>(覆盖现有数据)。</p>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="text-sm text-gray-600 mb-2 block">选择导入文件(JSON格式)</label>
|
||||
<input type="file" id="dataImportFileInput" accept=".json" class="w-full px-4 py-2 border rounded-lg" onchange="handleDataImportFile(event)">
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-gray-600 mb-2 block">导入模式</label>
|
||||
<select id="dataImportMode" class="w-full px-4 py-2 border rounded-lg">
|
||||
<option value="merge">合并模式 - 保留现有数据,只添加新数据</option>
|
||||
<option value="replace">替换模式 - 覆盖已存在的数据</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="dataImportPreview" class="hidden border rounded-lg p-4 bg-gray-50">
|
||||
<h3 class="text-sm font-semibold text-gray-700 mb-3"><i class="ri-eye-line mr-1"></i>导入预览</h3>
|
||||
<div id="dataImportPreviewContent"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6 border-t flex justify-end gap-4">
|
||||
<button onclick="closeDataImportModal()" class="px-4 py-2 bg-gray-200 text-gray-600 rounded-lg hover:bg-gray-300">取消</button>
|
||||
<button onclick="doDataImport()" class="px-4 py-2 bg-teal-600 text-white rounded-lg hover:bg-teal-700"><i class="ri-upload-line mr-1"></i>确认导入</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentType = '';
|
||||
let currentId = '';
|
||||
@@ -2729,176 +2769,228 @@
|
||||
async function exportAllCategories() {
|
||||
try {
|
||||
const res = await fetch('/api/categories/export');
|
||||
if (!res.ok) {
|
||||
const err = await res.json();
|
||||
alert('导出失败: ' + err.error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取文件并下载
|
||||
if (!res.ok) { const err = await res.json(); alert('导出失败: ' + err.error); return; }
|
||||
const blob = await res.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `param-hub-categories-export-${new Date().toISOString().slice(0,19).replace(/[:-]/g,'')}.json`;
|
||||
a.download = `categories-${new Date().toISOString().slice(0,19).replace(/[:-]/g,'')}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
} catch (e) {
|
||||
alert('导出失败: ' + e.message);
|
||||
}
|
||||
} catch (e) { alert('导出失败: ' + e.message); }
|
||||
}
|
||||
|
||||
// 导出单个分类
|
||||
// 导出单个分类配置
|
||||
async function exportSingleCategory(categoryId) {
|
||||
try {
|
||||
const res = await fetch(`/api/categories/export/${categoryId}`);
|
||||
if (!res.ok) {
|
||||
const err = await res.json();
|
||||
alert('导出失败: ' + err.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!res.ok) { const err = await res.json(); alert('导出失败: ' + err.error); return; }
|
||||
const blob = await res.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${categoryId}-export-${new Date().toISOString().slice(0,19).replace(/[:-]/g,'')}.json`;
|
||||
a.download = `${categoryId}-${new Date().toISOString().slice(0,19).replace(/[:-]/g,'')}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
} catch (e) {
|
||||
alert('导出失败: ' + e.message);
|
||||
}
|
||||
} catch (e) { alert('导出失败: ' + e.message); }
|
||||
}
|
||||
|
||||
// 打开导入弹窗
|
||||
// 打开分类导入弹窗
|
||||
function openImportModal() {
|
||||
document.getElementById('importModal').classList.remove('hidden');
|
||||
document.getElementById('importPreview').classList.add('hidden');
|
||||
document.getElementById('importFileInput').value = '';
|
||||
importData = null;
|
||||
}
|
||||
|
||||
// 关闭导入弹窗
|
||||
function closeImportModal() {
|
||||
document.getElementById('importModal').classList.add('hidden');
|
||||
}
|
||||
function closeImportModal() { document.getElementById('importModal').classList.add('hidden'); }
|
||||
|
||||
// 处理导入文件选择
|
||||
let importData = null;
|
||||
|
||||
function handleImportFile(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
importData = JSON.parse(e.target.result);
|
||||
showImportPreview();
|
||||
} catch (err) {
|
||||
alert('JSON 解析失败: ' + err.message);
|
||||
}
|
||||
};
|
||||
reader.onload = (e) => { try { importData = JSON.parse(e.target.result); showImportPreview(); } catch (err) { alert('JSON 解析失败: ' + err.message); } };
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
// 显示导入预览
|
||||
function showImportPreview() {
|
||||
if (!importData) return;
|
||||
|
||||
const preview = document.getElementById('importPreview');
|
||||
const content = document.getElementById('importPreviewContent');
|
||||
|
||||
preview.classList.remove('hidden');
|
||||
|
||||
let html = '<div class="space-y-2">';
|
||||
|
||||
// 分类信息
|
||||
if (importData.categories) {
|
||||
html += `<div class="font-medium text-gray-800"><i class="ri-folder-line mr-1"></i>包含 ${importData.categories.length} 个分类</div>`;
|
||||
html += '<div class="flex flex-wrap gap-2 ml-5">';
|
||||
importData.categories.forEach(cat => {
|
||||
html += `<span class="px-2 py-1 bg-blue-100 text-blue-700 rounded text-xs">${cat.name}</span>`;
|
||||
});
|
||||
html += '</div>';}
|
||||
importData.categories.forEach(cat => { html += `<span class="px-2 py-1 bg-blue-100 text-blue-700 rounded text-xs">${cat.name}</span>`; });
|
||||
html += '</div>';
|
||||
} else if (importData.category) {
|
||||
html += `<div class="font-medium text-gray-800"><i class="ri-folder-line mr-1"></i>单个分类: ${importData.category.name}</div>`;
|
||||
}
|
||||
|
||||
// 数据项
|
||||
const itemTypes = ['models', 'gpus', 'cpus', 'items'];
|
||||
let totalItems = 0;
|
||||
itemTypes.forEach(type => {
|
||||
if (importData[type] && importData[type].length > 0) {
|
||||
totalItems += importData[type].length;
|
||||
html += `<div class="text-sm text-gray-600 ml-5"><i class="ri-database-line mr-1"></i>${type}: ${importData[type].length} 条</div>`;
|
||||
}
|
||||
});
|
||||
|
||||
// 动态分类数据
|
||||
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 += `<div class="text-sm text-gray-600 ml-5"><i class="ri-database-line mr-1"></i>${catId}: ${importData[catId].length} 条</div>`;
|
||||
});
|
||||
|
||||
html += `<div class="mt-3 text-sm text-gray-500">导出时间: ${importData.export_time || '未知'}</div>`;
|
||||
html += '</div>';content.innerHTML = html;
|
||||
}
|
||||
|
||||
// 执行导入
|
||||
async function doImport() {
|
||||
if (!importData) {
|
||||
alert('请先选择导入文件');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!importData) { alert('请先选择导入文件'); return; }
|
||||
const mode = document.getElementById('importMode').value;
|
||||
const categoryId = document.getElementById('importCategoryId').value;
|
||||
|
||||
try {
|
||||
let endpoint = importData.category ? `/api/categories/import/${importData.category.id}?mode=${mode}` : `/api/categories/import?mode=${mode}`;
|
||||
const res = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(importData) });
|
||||
const result = await res.json();
|
||||
if (result.error) { alert('导入失败: ' + result.error); } else {
|
||||
let msg = '导入成功!\n新增: ' + (result.imported||0) + '\n更新: ' + (result.updated||0);
|
||||
if (result.skipped?.length) msg += '\n跳过: ' + result.skipped.join(', ');
|
||||
alert(msg);
|
||||
closeImportModal();
|
||||
await loadCategories(); renderSidebar(); loadAdminCategories();
|
||||
}
|
||||
} catch (e) { alert('导入失败: ' + e.message); }
|
||||
}
|
||||
|
||||
// ─── 数据导出导入功能 ─────────────────────────────────────────────────
|
||||
|
||||
let dataImportType = null;
|
||||
let dataImportCategoryId = null;
|
||||
let dataImportData = null;
|
||||
|
||||
// 导出模型
|
||||
async function exportModels() {
|
||||
try {
|
||||
const res = await fetch('/api/models/export');
|
||||
if (!res.ok) { const err = await res.json(); alert('导出失败: ' + err.error); return; }
|
||||
const blob = await res.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `models-${new Date().toISOString().slice(0,19).replace(/[:-]/g,'')}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
} catch (e) { alert('导出失败: ' + e.message); }
|
||||
}
|
||||
|
||||
// 导出GPU
|
||||
async function exportGpus() {
|
||||
try {
|
||||
const res = await fetch('/api/gpus/export');
|
||||
if (!res.ok) { const err = await res.json(); alert('导出失败: ' + err.error); return; }
|
||||
const blob = await res.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `gpus-${new Date().toISOString().slice(0,19).replace(/[:-]/g,'')}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
} catch (e) { alert('导出失败: ' + e.message); }
|
||||
}
|
||||
|
||||
// 导出CPU
|
||||
async function exportCpus() {
|
||||
try {
|
||||
const res = await fetch('/api/cpus/export');
|
||||
if (!res.ok) { const err = await res.json(); alert('导出失败: ' + err.error); return; }
|
||||
const blob = await res.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `cpus-${new Date().toISOString().slice(0,19).replace(/[:-]/g,'')}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
} catch (e) { alert('导出失败: ' + e.message); }
|
||||
}
|
||||
|
||||
// 导出动态分类数据
|
||||
async function exportCategoryData(categoryId) {
|
||||
if (!categoryId) { alert('未选择分类'); return; }
|
||||
try {
|
||||
const res = await fetch(`/api/items/${categoryId}/export`);
|
||||
if (!res.ok) { const err = await res.json(); alert('导出失败: ' + err.error); return; }
|
||||
const blob = await res.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${categoryId}-${new Date().toISOString().slice(0,19).replace(/[:-]/g,'')}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
} catch (e) { alert('导出失败: ' + e.message); }
|
||||
}
|
||||
|
||||
// 打开数据导入弹窗
|
||||
function openDataImportModal(typeOrCategoryId) {
|
||||
dataImportType = typeOrCategoryId;
|
||||
dataImportCategoryId = null;
|
||||
// 如果是动态分类,需要区分类型
|
||||
if (typeOrCategoryId && !['models', 'gpus', 'cpus'].includes(typeOrCategoryId)) {
|
||||
dataImportCategoryId = typeOrCategoryId;
|
||||
dataImportType = 'items';
|
||||
}
|
||||
document.getElementById('dataImportModal').classList.remove('hidden');
|
||||
document.getElementById('dataImportPreview').classList.add('hidden');
|
||||
document.getElementById('dataImportFileInput').value = '';
|
||||
dataImportData = null;
|
||||
}
|
||||
|
||||
function closeDataImportModal() { document.getElementById('dataImportModal').classList.add('hidden'); }
|
||||
|
||||
function handleDataImportFile(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => { try { dataImportData = JSON.parse(e.target.result); showDataImportPreview(); } catch (err) { alert('JSON 解析失败: ' + err.message); } };
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
function showDataImportPreview() {
|
||||
if (!dataImportData) return;
|
||||
const preview = document.getElementById('dataImportPreview');
|
||||
const content = document.getElementById('dataImportPreviewContent');
|
||||
preview.classList.remove('hidden');
|
||||
let html = '<div class="space-y-2">';
|
||||
html += `<div class="font-medium text-gray-800"><i class="ri-database-line mr-1"></i>数据类型: ${dataImportData.type || '未知'}</div>`;
|
||||
html += `<div class="text-sm text-gray-600 ml-5">包含 ${dataImportData.count || dataImportData.items?.length || 0} 条数据</div>`;
|
||||
html += `<div class="mt-3 text-sm text-gray-500">导出时间: ${dataImportData.export_time || '未知'}</div>`;
|
||||
html += '</div>';content.innerHTML = html;
|
||||
}
|
||||
|
||||
async function doDataImport() {
|
||||
if (!dataImportData) { alert('请先选择导入文件'); return; }
|
||||
const mode = document.getElementById('dataImportMode').value;
|
||||
try {
|
||||
let endpoint;
|
||||
if (categoryId && importData.category) {
|
||||
// 导入单个分类
|
||||
endpoint = `/api/categories/import/${categoryId}?mode=${mode}`;
|
||||
} else {
|
||||
// 导入全部
|
||||
endpoint = `/api/categories/import?mode=${mode}`;
|
||||
}
|
||||
|
||||
const res = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(importData)
|
||||
});
|
||||
if (dataImportType === 'models') endpoint = '/api/models/import?mode=' + mode;
|
||||
else if (dataImportType === 'gpus') endpoint = '/api/gpus/import?mode=' + mode;
|
||||
else if (dataImportType === 'cpus') endpoint = '/api/cpus/import?mode=' + mode;
|
||||
else if (dataImportCategoryId) endpoint = `/api/items/${dataImportCategoryId}/import?mode=` + mode;
|
||||
else { alert('未知数据类型'); return; }
|
||||
|
||||
const res = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(dataImportData) });
|
||||
const result = await res.json();
|
||||
|
||||
if (result.error) {
|
||||
alert('导入失败: ' + result.error);
|
||||
} else {
|
||||
let msg = '导入成功!\n';
|
||||
msg += `新增分类: ${result.imported_categories || 0}\n`;msg += `新增数据: ${result.imported_items || 0}\n`;msg += `更新数据: ${result.updated_items || 0}`;
|
||||
if (result.skipped && result.skipped.length > 0) {
|
||||
msg += `\n跳过已存在分类: ${result.skipped.join(', ')}`;
|
||||
}
|
||||
if (result.error) { alert('导入失败: ' + result.error); } else {
|
||||
let msg = '导入成功!\n新增: ' + (result.imported||0) + '\n更新: ' + (result.updated||0);
|
||||
if (result.skipped?.length) msg += '\n跳过: ' + result.skipped.length + ' 条';
|
||||
alert(msg);
|
||||
|
||||
closeImportModal();
|
||||
await loadCategories();
|
||||
renderSidebar();
|
||||
loadAdminCategories();
|
||||
closeDataImportModal();
|
||||
// 刷新当前页面数据
|
||||
if (dataImportType === 'models') loadAdminModels();
|
||||
else if (dataImportType === 'gpus') loadAdminGpus();
|
||||
else if (dataImportType === 'cpus') loadAdminCpus();
|
||||
else if (dataImportCategoryId) loadDynamicCategoryData(dataImportCategoryId);
|
||||
loadOverview();
|
||||
}
|
||||
} catch (e) {
|
||||
alert('导入失败: ' + e.message);
|
||||
}
|
||||
} catch (e) { alert('导入失败: ' + e.message); }
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
Reference in New Issue
Block a user