From fe802b9f348912e391026bcf2bc037ff3fe97f8f Mon Sep 17 00:00:00 2001 From: Nvex Date: Fri, 24 Oct 2025 12:53:22 +0800 Subject: [PATCH] Initial commit --- README.md | 4 +- src/main.py | 143 +++++++++++------- toolbox-app/src/api.js | 34 +++++ .../src/components/tools/CourseWithdrawal.vue | 19 ++- toolbox-app/src/main.js | 4 + toolbox-app/src/store/settings.js | 10 +- toolbox-app/src/views/SettingsView.vue | 10 +- 7 files changed, 158 insertions(+), 66 deletions(-) create mode 100644 toolbox-app/src/api.js diff --git a/README.md b/README.md index 7ff0f24..92e04ad 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,4 @@ A: 确保 Excel 文件包含 "学生ID" 和 "课程班ID" 列,且数据格式 - 📧 提交 Issue: [项目 Issues](https://git.vcck.cn/nvex/toolbox/issues) - 💬 项目讨论: [项目讨论区](https://git.vcck.cn/nvex/toolbox/discussions) ---- - -*最后更新: 2024年12月* \ No newline at end of file +--- \ No newline at end of file diff --git a/src/main.py b/src/main.py index 1326206..1502fb4 100644 --- a/src/main.py +++ b/src/main.py @@ -10,31 +10,22 @@ load_dotenv() app = Flask(__name__) CORS(app) -# --- 从 .env 文件加载配置 --- -BASE_URL = os.getenv("BASE_URL") -AUTHORIZATION = os.getenv("AUTHORIZATION") -SEMESTER_ID = os.getenv("SEMESTER_ID") -X_ROLE = os.getenv("X_ROLE") -X_SCHOOL_ID = os.getenv("X_SCHOOL_ID") -X_REFLECTION_ID = os.getenv("X_REFLECTION_ID") - # Use absolute path for the template directory TEMPLATE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'toolbox-app', 'public')) TEMPLATE_FILENAME = '退课列表.xlsx' -def get_student_info_from_api(student_id): +def get_student_info_from_api(base_url, authorization, x_role, x_school_id, x_reflection_id, student_id): """通过 API 查询学生姓名和班级""" - if not all([BASE_URL, AUTHORIZATION, X_ROLE, X_SCHOOL_ID, X_REFLECTION_ID]): - return "配置缺失", None, "Error: Missing API configuration" + if not all([base_url, authorization, x_role, x_school_id, x_reflection_id]): + return None, None, f"缺少一个或多个 API 配置参数" + url = f"{base_url}/chalk/role/reflection/students" headers = { - "Authorization": AUTHORIZATION, - "X-Role": X_ROLE, - "X-School-ID": X_SCHOOL_ID, - "X-Reflection-ID": X_REFLECTION_ID + 'Authorization': authorization, + 'X-Role': x_role, + 'X-School-ID': x_school_id, + 'X-Reflection-ID': x_reflection_id } - - url = f"{BASE_URL}/chalk/role/reflection/students" params = { "id_in": student_id, "policy": "admin" @@ -57,20 +48,19 @@ def get_student_info_from_api(student_id): app.logger.error(f"API request failed for student_id {student_id}: {e}") return "查询失败", None, f"Error fetching info for student {student_id}" -def get_class_info_from_api(student_id, class_ids): +def get_class_info_from_api(base_url, authorization, x_role, x_school_id, x_reflection_id, semester_id, student_id): """通过 API 查询课程班信息,返回ID和名称的字典""" - if not all([BASE_URL, AUTHORIZATION, SEMESTER_ID, X_ROLE, X_SCHOOL_ID, X_REFLECTION_ID]): - return None, "错误: 缺少 API 配置" + if not all([base_url, authorization, x_role, x_school_id, x_reflection_id, semester_id]): + return None, f"缺少一个或多个 API 配置参数" + url = f"{base_url}/scms/class/students/{student_id}/classes" headers = { - "Authorization": AUTHORIZATION, - "X-Role": X_ROLE, - "X-School-ID": X_SCHOOL_ID, - "X-Reflection-ID": X_REFLECTION_ID + 'Authorization': authorization, + 'X-Role': x_role, + 'X-School-ID': x_school_id, + 'X-Reflection-ID': x_reflection_id } - - url = f"{BASE_URL}/scms/class/students/{student_id}/classes" - params = {"semester_id": SEMESTER_ID, "paginated": "1", "expand": "course"} + params = {"semester_id": semester_id, "paginated": "1", "expand": "course"} try: resp = requests.get(url, headers=headers, params=params) @@ -92,18 +82,18 @@ def get_class_info_from_api(student_id, class_ids): app.logger.error(f"API request failed for student_id {student_id}: {e}") return None, f"为学生 {student_id} 获取名称时出错" -def get_school_name_from_api(): +def get_school_name_from_api(base_url, authorization, x_role, x_school_id): """通过 API 查询学校名称""" - if not all([BASE_URL, AUTHORIZATION, X_ROLE, X_SCHOOL_ID]): + if not all([base_url, authorization, x_role, x_school_id]): return "配置缺失", "Error: Missing API configuration" headers = { - "Authorization": AUTHORIZATION, - "X-Role": X_ROLE, - "X-School-ID": X_SCHOOL_ID + "Authorization": authorization, + "X-Role": x_role, + "X-School-ID": x_school_id } - - url = f"{BASE_URL}/chalk/system/schools/{X_SCHOOL_ID}?expand=custom_constraints" + + url = f"{base_url}/chalk/system/schools/{x_school_id}?expand=custom_constraints" try: resp = requests.get(url, headers=headers) @@ -113,13 +103,27 @@ def get_school_name_from_api(): return school_name, None except requests.exceptions.RequestException as e: - app.logger.error(f"API request failed for school_id {X_SCHOOL_ID}: {e}") - return "查询失败", f"Error fetching info for school {X_SCHOOL_ID}" + app.logger.error(f"API request failed for school_id {x_school_id}: {e}") + return "查询失败", f"Error fetching info for school {x_school_id}" @app.route('/api/school-name', methods=['GET']) def get_school_name(): - """Provides the school name.""" - school_name, error = get_school_name_from_api() + """Provides the school name based on headers.""" + auth_header = request.headers.get('Authorization') + school_id_header = request.headers.get('X-School-ID') + base_url_header = request.headers.get('X-Base-URL') + role_header = request.headers.get('X-Role') + + if not all([auth_header, school_id_header, base_url_header, role_header]): + return jsonify({'error': 'Missing required headers: Authorization, X-School-ID, X-Base-URL, X-Role'}), 400 + + school_name, error = get_school_name_from_api( + base_url=base_url_header, + authorization=auth_header, + x_role=role_header, + x_school_id=school_id_header + ) + if error: return jsonify({'error': error}), 500 return jsonify({'school_name': school_name}) @@ -158,6 +162,16 @@ def preview_file(): if file.filename == '': return jsonify({'error': 'No selected file'}), 400 + auth_header = request.headers.get('Authorization') + school_id_header = request.headers.get('X-School-ID') + base_url_header = request.headers.get('X-Base-URL') + role_header = request.headers.get('X-Role') + reflection_id_header = request.headers.get('X-Reflection-ID') + semester_id_header = request.headers.get('X-Semester-ID') + + if not all([auth_header, school_id_header, base_url_header, role_header, reflection_id_header, semester_id_header]): + return jsonify({'error': 'Missing required headers: Authorization, X-School-ID, X-Base-URL, X-Role, X-Reflection-ID, X-Semester-ID'}), 400 + try: df = pd.read_excel(file) df = df.where(pd.notnull(df), None) @@ -170,8 +184,24 @@ def preview_file(): student_id = row['学生ID'] class_ids_str = row['课程班ID'] - student_name, admin_classes, info_error = get_student_info_from_api(student_id) - class_details, class_error = get_class_info_from_api(student_id, class_ids_str) + student_name, admin_classes, info_error = get_student_info_from_api( + base_url=base_url_header, + authorization=auth_header, + x_role=role_header, + x_school_id=school_id_header, + x_reflection_id=reflection_id_header, + student_id=student_id + ) + class_details, class_error = get_class_info_from_api( + base_url=base_url_header, + authorization=auth_header, + x_role=role_header, + x_school_id=school_id_header, + x_reflection_id=reflection_id_header, + semester_id=semester_id_header, + student_id=student_id, + class_ids=class_ids_str + ) if class_error: class_ids = [id.strip() for id in str(class_ids_str).split(',')] @@ -218,11 +248,20 @@ def withdraw_courses(): if not withdraw_list: return jsonify({'error': 'Withdrawal list is required.'}), 400 + auth_header = request.headers.get('Authorization') + school_id_header = request.headers.get('X-School-ID') + base_url_header = request.headers.get('X-Base-URL') + role_header = request.headers.get('X-Role') + reflection_id_header = request.headers.get('X-Reflection-ID') + + if not all([auth_header, school_id_header, base_url_header, role_header, reflection_id_header]): + return jsonify({'error': 'Missing required headers: Authorization, X-School-ID, X-Base-URL, X-Role, X-Reflection-ID'}), 400 + headers = { - "Authorization": AUTHORIZATION, - "X-Role": X_ROLE, - "X-School-ID": X_SCHOOL_ID, - "X-Reflection-ID": X_REFLECTION_ID + "Authorization": auth_header, + "X-Role": role_header, + "X-School-ID": school_id_header, + "X-Reflection-ID": reflection_id_header } student_withdrawals = {} @@ -247,17 +286,17 @@ def withdraw_courses(): payload = { "reflection_ids": [student_id], - "class_ids": [int(cid) for cid in class_ids] + "class_ids": [int(cid) for cid in class_ids] if class_ids else [] } try: - resp = requests.delete(f"{BASE_URL}/scms/class/class-selections", headers=headers, json=payload) - if resp.status_code == 204: - success_count += 1 - details.append(f"✅ 学生 {student_id} 已成功退课") - else: - failure_count += 1 - details.append(f"❌ 学生 {student_id} 退课失败:{resp.status_code} - {resp.text}") + resp = requests.delete(f"{base_url_header}/scms/class/class-selections", headers=headers, json=payload) + resp.raise_for_status() + success_count += 1 + details.append(f"✅ 学生 {student_id} 已成功退课") + except requests.exceptions.HTTPError as e: + failure_count += 1 + details.append(f"❌ 学生 {student_id} 退课失败:{e.response.status_code} - {e.response.text}") except requests.exceptions.RequestException as e: failure_count += 1 details.append(f"❌ 学生 {student_id} 退课失败: {e}") diff --git a/toolbox-app/src/api.js b/toolbox-app/src/api.js new file mode 100644 index 0000000..9be4428 --- /dev/null +++ b/toolbox-app/src/api.js @@ -0,0 +1,34 @@ +import axios from 'axios'; +import { useSettingsStore } from './store/settings'; + +const api = axios.create({ + baseURL: 'https://api.seiue.com', + timeout: 60000, +}); + +api.interceptors.request.use( + (config) => { + const settingsStore = useSettingsStore(); + if (settingsStore.authorization) { + config.headers['Authorization'] = settingsStore.authorization; + } + if (settingsStore.xSchoolId) { + config.headers['X-School-ID'] = settingsStore.xSchoolId; + } + if (settingsStore.xRole) { + config.headers['X-Role'] = settingsStore.xRole; + } + if (settingsStore.xReflectionId) { + config.headers['X-Reflection-ID'] = settingsStore.xReflectionId; + } + if (settingsStore.semesterId) { + config.headers['X-Semester-ID'] = settingsStore.semesterId; + } + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +export default api; \ No newline at end of file diff --git a/toolbox-app/src/components/tools/CourseWithdrawal.vue b/toolbox-app/src/components/tools/CourseWithdrawal.vue index 6dea233..da23a5f 100644 --- a/toolbox-app/src/components/tools/CourseWithdrawal.vue +++ b/toolbox-app/src/components/tools/CourseWithdrawal.vue @@ -1,6 +1,6 @@