|
|
@@ -0,0 +1,412 @@
|
|
|
+// 文件: hpu.js
|
|
|
+// 河南理工大学 树维教务系统课程表导入脚本
|
|
|
+// 适配目标: zhjw.hpu.edu.cn (树维教务系统, Struts2 .action, /eams/ 路径) + uia.hpu.edu.cn 统一身份认证(CAS)
|
|
|
+// 数据链路(已对 HPU 线上服务器实测):
|
|
|
+// 1. GET courseTableForStd.action?sf_request_type=ajax → 解析 ids(学生) + tagId(学期栏)
|
|
|
+// 2. POST dataQuery.action (dataType=semesterCalendar) → 学期列表
|
|
|
+// 3. POST courseTableForStd!courseTable.action (semester.id) → 课表 HTML
|
|
|
+// 课表 HTML 内课程以 TaskActivity JS 块内嵌(unitCount=11, bitmap周次), 表格骨架含节次/时间
|
|
|
+// 4. 解析 TaskActivity → 课程; 解析 #manualArrangeCourseTable 节次格 → 时间段
|
|
|
+// 兜底: 若当前页面已是渲染后的课表页(#manualArrangeCourseTable + .infoTitle), 直接解析 DOM
|
|
|
+// 解析参考: HIIT/hiit_01.js (同款 eams 架构系统)
|
|
|
+(function () {
|
|
|
+ function showToast(message) {
|
|
|
+ if (typeof window.shiguangBridge !== "undefined" && window.shiguangBridge.showToast) {
|
|
|
+ window.shiguangBridge.showToast(String(message || ""));
|
|
|
+ } else {
|
|
|
+ console.log("[HPU适配]", message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async function request(url, options) {
|
|
|
+ const res = await fetch(url, { credentials: "include", ...(options || {}) });
|
|
|
+ if (!res.ok) throw new Error("网络请求失败(" + res.status + "): " + url);
|
|
|
+ return await res.text();
|
|
|
+ }
|
|
|
+
|
|
|
+ // ================= 通用小工具 =================
|
|
|
+ const CN_DIGIT = { "一": 1, "二": 2, "三": 3, "四": 4, "五": 5, "六": 6, "七": 7, "八": 8, "九": 9 };
|
|
|
+ function cnDigit(c) { return CN_DIGIT[c] || 0; }
|
|
|
+ function cnToInt(s) {
|
|
|
+ if (!s) return 0;
|
|
|
+ if (s === "十") return 10;
|
|
|
+ const i = s.indexOf("十");
|
|
|
+ if (i >= 0) {
|
|
|
+ const tens = i > 0 ? cnDigit(s.charAt(0)) : 1;
|
|
|
+ const ones = s.length > i + 1 ? cnDigit(s.charAt(i + 1)) : 0;
|
|
|
+ return tens * 10 + ones;
|
|
|
+ }
|
|
|
+ return cnDigit(s);
|
|
|
+ }
|
|
|
+
|
|
|
+ function splitJsArgs(argsText) {
|
|
|
+ const args = [];
|
|
|
+ let current = "";
|
|
|
+ let quote = "";
|
|
|
+ let escaped = false;
|
|
|
+ for (let i = 0; i < argsText.length; i++) {
|
|
|
+ const ch = argsText[i];
|
|
|
+ if (escaped) { current += ch; escaped = false; continue; }
|
|
|
+ if (ch === "\\") { current += ch; escaped = true; continue; }
|
|
|
+ if (quote) { current += ch; if (ch === quote) quote = ""; continue; }
|
|
|
+ if (ch === "'" || ch === "\"") { current += ch; quote = ch; continue; }
|
|
|
+ if (ch === ",") { args.push(current.trim()); current = ""; continue; }
|
|
|
+ current += ch;
|
|
|
+ }
|
|
|
+ if (current.trim()) args.push(current.trim());
|
|
|
+ return args;
|
|
|
+ }
|
|
|
+
|
|
|
+ function unquoteJsLiteral(token) {
|
|
|
+ const text = String(token || "").trim();
|
|
|
+ if (!text || text === "null" || text === "undefined") return "";
|
|
|
+ if ((text.startsWith("\"") && text.endsWith("\"")) || (text.startsWith("'") && text.endsWith("'"))) {
|
|
|
+ const quote = text[0];
|
|
|
+ return text.slice(1, -1)
|
|
|
+ .replace(/\\\\/g, "\\")
|
|
|
+ .replace(new RegExp("\\\\" + quote, "g"), quote)
|
|
|
+ .replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, "\t");
|
|
|
+ }
|
|
|
+ return text;
|
|
|
+ }
|
|
|
+
|
|
|
+ function parseValidWeeksBitmap(bitmap) {
|
|
|
+ const weeks = [];
|
|
|
+ const text = String(bitmap || "");
|
|
|
+ for (let i = 0; i < text.length; i++) {
|
|
|
+ if (text[i] === "1" && i >= 1) weeks.push(i);
|
|
|
+ }
|
|
|
+ return weeks;
|
|
|
+ }
|
|
|
+
|
|
|
+ function normalizeWeeks(weeks) {
|
|
|
+ return Array.from(new Set((weeks || []).filter(function (w) { return Number.isInteger(w) && w > 0; }))).sort(function (a, b) { return a - b; });
|
|
|
+ }
|
|
|
+
|
|
|
+ function cleanCourseName(name) {
|
|
|
+ return String(name || "").replace(/\s*\([^()]*\)\s*$/, "").trim();
|
|
|
+ }
|
|
|
+
|
|
|
+ function resolveTeachersForTaskActivityBlock(fullText, blockStartIndex) {
|
|
|
+ const start = Math.max(0, blockStartIndex - 2500);
|
|
|
+ const segment = fullText.slice(start, blockStartIndex);
|
|
|
+ const teachersRegex = /var\s+teachers\s*=\s*\[([^]*?)\];/g;
|
|
|
+ let lastTeachersBlock = "";
|
|
|
+ let match;
|
|
|
+ while ((match = teachersRegex.exec(segment)) !== null) lastTeachersBlock = match[1] || "";
|
|
|
+ if (!lastTeachersBlock) return "";
|
|
|
+ const names = [];
|
|
|
+ const nameRegex = /name\s*:\s*(?:"([^"]*)"|'([^']*)')/g;
|
|
|
+ let nameMatch;
|
|
|
+ while ((nameMatch = nameRegex.exec(lastTeachersBlock)) !== null) {
|
|
|
+ const name = (nameMatch[1] || nameMatch[2] || "").trim();
|
|
|
+ if (name) names.push(name);
|
|
|
+ }
|
|
|
+ return Array.from(new Set(names)).join(",");
|
|
|
+ }
|
|
|
+
|
|
|
+ // ================= 课程解析 (TaskActivity, 从 AJAX HTML) =================
|
|
|
+ function parseCoursesFromTaskActivityScript(htmlText) {
|
|
|
+ const text = String(htmlText || "");
|
|
|
+ const unitCountMatch = text.match(/\bvar\s+unitCount\s*=\s*(\d+)\s*;/);
|
|
|
+ const unitCount = unitCountMatch ? Number(unitCountMatch[1]) : 0;
|
|
|
+ if (!unitCount) return [];
|
|
|
+
|
|
|
+ const courses = [];
|
|
|
+ const blockRegex = /activity\s*=\s*new\s+TaskActivity\(([^]*?)\)\s*;([\s\S]*?)(?=activity\s*=\s*new\s+TaskActivity\(|table\d+\.marshalTable|$)/g;
|
|
|
+ let match;
|
|
|
+ while ((match = blockRegex.exec(text)) !== null) {
|
|
|
+ const args = splitJsArgs(match[1] || "");
|
|
|
+ if (args.length < 7) continue;
|
|
|
+
|
|
|
+ let teacher = unquoteJsLiteral(args[1]);
|
|
|
+ if (/join\s*\(/.test(String(args[1] || ""))) {
|
|
|
+ teacher = resolveTeachersForTaskActivityBlock(text, match.index) || teacher;
|
|
|
+ }
|
|
|
+ const name = cleanCourseName(unquoteJsLiteral(args[3]));
|
|
|
+ const position = String(unquoteJsLiteral(args[5]) || "").replace(/\s+/g, " ").trim();
|
|
|
+ const weeks = normalizeWeeks(parseValidWeeksBitmap(unquoteJsLiteral(args[6])));
|
|
|
+ if (!name) continue;
|
|
|
+
|
|
|
+ const indexBlock = match[2] || "";
|
|
|
+ const indexRegex = /index\s*=\s*(?:(\d+)\s*\*\s*unitCount\s*\+\s*(\d+)|(\d+))\s*;/g;
|
|
|
+ let indexMatch;
|
|
|
+ while ((indexMatch = indexRegex.exec(indexBlock)) !== null) {
|
|
|
+ let linearIndex = -1;
|
|
|
+ if (indexMatch[1] != null && indexMatch[2] != null) {
|
|
|
+ linearIndex = Number(indexMatch[1]) * unitCount + Number(indexMatch[2]);
|
|
|
+ } else if (indexMatch[3] != null) {
|
|
|
+ linearIndex = Number(indexMatch[3]);
|
|
|
+ }
|
|
|
+ if (linearIndex < 0) continue;
|
|
|
+ const day = Math.floor(linearIndex / unitCount) + 1;
|
|
|
+ const section = (linearIndex % unitCount) + 1;
|
|
|
+ if (day < 1 || day > 7) continue;
|
|
|
+ courses.push({
|
|
|
+ name: name,
|
|
|
+ teacher: teacher || "未知教师",
|
|
|
+ position: position || "待定",
|
|
|
+ day: day,
|
|
|
+ startSection: section,
|
|
|
+ endSection: section,
|
|
|
+ weeks: weeks
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return mergeContiguousSections(courses);
|
|
|
+ }
|
|
|
+
|
|
|
+ function mergeContiguousSections(courses) {
|
|
|
+ const normalized = (courses || []).map(function (c) {
|
|
|
+ return { ...c, weeks: normalizeWeeks(c.weeks) };
|
|
|
+ });
|
|
|
+ normalized.sort(function (a, b) {
|
|
|
+ const keyA = a.name + "|" + a.teacher + "|" + a.position + "|" + a.day + "|" + a.weeks.join(",");
|
|
|
+ const keyB = b.name + "|" + b.teacher + "|" + b.position + "|" + b.day + "|" + b.weeks.join(",");
|
|
|
+ if (keyA < keyB) return -1;
|
|
|
+ if (keyA > keyB) return 1;
|
|
|
+ return a.startSection - b.startSection;
|
|
|
+ });
|
|
|
+ const merged = [];
|
|
|
+ normalized.forEach(function (course) {
|
|
|
+ const previous = merged[merged.length - 1];
|
|
|
+ const canMerge = previous
|
|
|
+ && previous.name === course.name
|
|
|
+ && previous.teacher === course.teacher
|
|
|
+ && previous.position === course.position
|
|
|
+ && previous.day === course.day
|
|
|
+ && previous.weeks.join(",") === course.weeks.join(",")
|
|
|
+ && previous.endSection + 1 >= course.startSection;
|
|
|
+ if (canMerge) {
|
|
|
+ previous.endSection = Math.max(previous.endSection, course.endSection);
|
|
|
+ } else {
|
|
|
+ merged.push({ ...course });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return merged;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ================= 时间段解析 (从 HTML 表格骨架) =================
|
|
|
+ function parseTimeSlotsFromHtml(htmlText) {
|
|
|
+ const doc = new DOMParser().parseFromString(String(htmlText || ""), "text/html");
|
|
|
+ const slots = [];
|
|
|
+ const slotMap = {};
|
|
|
+ const table = doc.querySelector("#manualArrangeCourseTable");
|
|
|
+ if (!table) return slots;
|
|
|
+ table.querySelectorAll("tbody tr").forEach(function (row) {
|
|
|
+ const cells = Array.from(row.querySelectorAll("td"));
|
|
|
+ cells.forEach(function (cell) {
|
|
|
+ const txt = (cell.textContent || "").replace(/\s+/g, "");
|
|
|
+ const m = txt.match(/第([一二三四五六七八九十]+)节/);
|
|
|
+ if (!m) return;
|
|
|
+ const tm = txt.match(/(\d{1,2}:\d{2})-(\d{1,2}:\d{2})/);
|
|
|
+ if (!tm) return;
|
|
|
+ const num = cnToInt(m[1]);
|
|
|
+ if (num && !slotMap[num]) {
|
|
|
+ slotMap[num] = { number: num, startTime: tm[1], endTime: tm[2] };
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ Object.keys(slotMap).map(Number).sort(function (a, b) { return a - b; })
|
|
|
+ .forEach(function (k) { slots.push(slotMap[k]); });
|
|
|
+ return slots;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ================= 兜底: DOM 解析(已渲染的课表页) =================
|
|
|
+ function parseCourseCellFromInfoTitle(raw) {
|
|
|
+ const clean = String(raw || "").replace(/;;;/g, " ")
|
|
|
+ .replace(/[\t\r\n]+/g, " ").replace(/\s+/g, " ").trim();
|
|
|
+ const m = clean.match(/^(.+?)\(\d[\d.]*\)\s*\(([^)]*)\)\s*\(([^)]+)\)\s*$/);
|
|
|
+ if (!m) return null;
|
|
|
+ const info = m[3];
|
|
|
+ const comma = info.indexOf(",");
|
|
|
+ if (comma < 0) return null;
|
|
|
+ // 周次字符串(可能含"单3-17"式样)直接转为 bitmap 语义
|
|
|
+ const weekStr = info.substring(0, comma).trim();
|
|
|
+ const weeks = [];
|
|
|
+ const segs = weekStr.replace(/周/g, "").split(/[,,;;]/);
|
|
|
+ segs.forEach(function (seg) {
|
|
|
+ const isEven = seg.indexOf("双") >= 0;
|
|
|
+ const isOdd = seg.indexOf("单") >= 0;
|
|
|
+ const cleanSeg = seg.replace(/[单双]/g, "").trim();
|
|
|
+ if (cleanSeg.indexOf("-") >= 0) {
|
|
|
+ const p = cleanSeg.split("-");
|
|
|
+ const s = parseInt(p[0], 10), e = parseInt(p[1], 10);
|
|
|
+ if (!isNaN(s) && !isNaN(e)) {
|
|
|
+ for (let w = s; w <= e; w++) {
|
|
|
+ if (isEven && w % 2 !== 0) continue;
|
|
|
+ if (isOdd && w % 2 === 0) continue;
|
|
|
+ weeks.push(w);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ const n = parseInt(cleanSeg, 10);
|
|
|
+ if (!isNaN(n) && n > 0) weeks.push(n);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ const finalWeeks = Array.from(new Set(weeks)).sort(function (a, b) { return a - b; });
|
|
|
+ if (!finalWeeks.length) return null;
|
|
|
+ return {
|
|
|
+ name: m[1].trim(),
|
|
|
+ teacher: m[2].trim() || "未知教师",
|
|
|
+ position: info.substring(comma + 1).replace(/\s+/g, " ").trim() || "待定",
|
|
|
+ weeks: finalWeeks
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ function parseCoursesFromCurrentDom() {
|
|
|
+ const table = document.querySelector("#manualArrangeCourseTable");
|
|
|
+ if (!table) return [];
|
|
|
+ const courses = [];
|
|
|
+ const seen = {};
|
|
|
+ table.querySelectorAll("tbody tr").forEach(function (tr) {
|
|
|
+ const tds = Array.from(tr.querySelectorAll("td"));
|
|
|
+ let labelIdx = -1, section = 0;
|
|
|
+ tds.forEach(function (td, i) {
|
|
|
+ const txt = (td.textContent || "").replace(/\s+/g, "");
|
|
|
+ const m = txt.match(/第([一二三四五六七八九十]+)节/);
|
|
|
+ if (m) { labelIdx = i; section = cnToInt(m[1]); }
|
|
|
+ });
|
|
|
+ if (labelIdx < 0) return;
|
|
|
+ tds.forEach(function (td, i) {
|
|
|
+ if (i <= labelIdx) return;
|
|
|
+ if ((td.className || "").indexOf("infoTitle") < 0) return;
|
|
|
+ const raw = td.getAttribute("title") || td.textContent || "";
|
|
|
+ if (!raw.trim()) return;
|
|
|
+ const day = i - labelIdx - 1;
|
|
|
+ if (day < 0 || day > 6) return;
|
|
|
+ const parsed = parseCourseCellFromInfoTitle(raw);
|
|
|
+ if (!parsed) return;
|
|
|
+ const rowspan = td.rowSpan > 1 ? td.rowSpan : 1;
|
|
|
+ const key = [day, section, rowspan, parsed.name, parsed.teacher, parsed.position, parsed.weeks.join(",")].join("|");
|
|
|
+ if (seen[key]) return;
|
|
|
+ seen[key] = 1;
|
|
|
+ courses.push({
|
|
|
+ name: parsed.name, teacher: parsed.teacher, position: parsed.position,
|
|
|
+ day: day + 1, startSection: section, endSection: section + rowspan - 1,
|
|
|
+ weeks: parsed.weeks
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ return courses;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ================= AJAX 链路 =================
|
|
|
+ async function detectParams() {
|
|
|
+ if (/cas\/login|loginExt/i.test(window.location.href)) throw new Error("请先登录教务系统后再执行导入");
|
|
|
+ const html = await request(window.location.origin + "/eams/courseTableForStd.action?sf_request_type=ajax", {
|
|
|
+ headers: { "x-requested-with": "XMLHttpRequest" }
|
|
|
+ });
|
|
|
+ const idsM = html.match(/bg\.form\.addInput\(form,"ids","(\d+)"\)/);
|
|
|
+ const tagM = html.match(/id="(semesterBar\d+Semester)"/);
|
|
|
+ return (idsM && tagM) ? { ids: idsM[1], tagId: tagM[1] } : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ async function fetchSemesters(tagId) {
|
|
|
+ const raw = await request(window.location.origin + "/eams/dataQuery.action?sf_request_type=ajax", {
|
|
|
+ method: "POST",
|
|
|
+ headers: { "content-type": "application/x-www-form-urlencoded; charset=UTF-8" },
|
|
|
+ body: "tagId=" + encodeURIComponent(tagId) + "&dataType=semesterCalendar"
|
|
|
+ });
|
|
|
+ const data = Function("return (" + String(raw).trim() + ");")();
|
|
|
+ const list = [];
|
|
|
+ if (data && data.semesters && typeof data.semesters === "object") {
|
|
|
+ Object.keys(data.semesters).forEach(function (k) {
|
|
|
+ (data.semesters[k] || []).forEach(function (s) {
|
|
|
+ if (!s || !s.id) return;
|
|
|
+ const term = String(s.name || "").trim();
|
|
|
+ const termName = term === "1" ? "第一学期" : term === "2" ? "第二学期" : "第" + term + "学期";
|
|
|
+ list.push({
|
|
|
+ id: String(s.id),
|
|
|
+ schoolYear: String(s.schoolYear || "").trim(),
|
|
|
+ term: term,
|
|
|
+ name: (String(s.schoolYear || "") + "学年" + termName).trim()
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按当前日期猜测所在学期(用于默认选中): 8-12月→该学年第一学期; 2-7月→上学年第二学期; 1月→上学年第一学期
|
|
|
+ function guessCurrentSemesterIndex(semesters) {
|
|
|
+ const now = new Date();
|
|
|
+ const y = now.getFullYear();
|
|
|
+ const m = now.getMonth() + 1;
|
|
|
+ let targetYear, targetTerm;
|
|
|
+ if (m >= 8) { targetYear = y; targetTerm = "1"; }
|
|
|
+ else if (m >= 2) { targetYear = y - 1; targetTerm = "2"; }
|
|
|
+ else { targetYear = y - 1; targetTerm = "1"; }
|
|
|
+ for (let i = 0; i < semesters.length; i++) {
|
|
|
+ const s = semesters[i];
|
|
|
+ if (s.schoolYear === targetYear + "-" + (targetYear + 1) && s.term === targetTerm) return i;
|
|
|
+ }
|
|
|
+ return semesters.length - 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ async function fetchCourseHtml(params, semesterId) {
|
|
|
+ return await request(window.location.origin + "/eams/courseTableForStd!courseTable.action?sf_request_type=ajax", {
|
|
|
+ method: "POST",
|
|
|
+ headers: {
|
|
|
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
|
+ "x-requested-with": "XMLHttpRequest"
|
|
|
+ },
|
|
|
+ body: "ignoreHead=1&setting.kind=std&startWeek=&semester.id=" + encodeURIComponent(semesterId) + "&ids=" + encodeURIComponent(params.ids)
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // ================= 保存 =================
|
|
|
+ async function save(courses, timeSlots) {
|
|
|
+ const okCourses = await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
|
|
|
+ if (okCourses !== true) throw new Error("课程保存失败: " + String(okCourses));
|
|
|
+ if (timeSlots && timeSlots.length) {
|
|
|
+ await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ================= 主流程 =================
|
|
|
+ async function runImportFlow() {
|
|
|
+ // ① 已在渲染后的课表页 → DOM 直解
|
|
|
+ let courses = parseCoursesFromCurrentDom();
|
|
|
+ let timeSlots = [];
|
|
|
+ if (courses.length) {
|
|
|
+ showToast("已从当前课表页解析到 " + courses.length + " 门课");
|
|
|
+ } else {
|
|
|
+ // ② AJAX(可自选学期)
|
|
|
+ showToast("正在识别课表参数...");
|
|
|
+ const params = await detectParams();
|
|
|
+ if (!params) throw new Error("未能自动识别课表参数,请登录后在任意教务页面重试");
|
|
|
+ showToast("正在获取学期列表...");
|
|
|
+ const semesters = await fetchSemesters(params.tagId);
|
|
|
+ if (!semesters.length) throw new Error("未获取到学期列表");
|
|
|
+ const showList = semesters.slice(-12); // 只展示最近6年, 避免列表过长
|
|
|
+ const defaultIdx = Math.min(guessCurrentSemesterIndex(semesters), semesters.length - 1);
|
|
|
+ const showDefault = Math.max(0, defaultIdx - (semesters.length - showList.length));
|
|
|
+ const picked = await window.shiguangBridgePromise.showSingleSelection(
|
|
|
+ "选择要导入的学期",
|
|
|
+ JSON.stringify(showList.map(function (s) { return s.name; })),
|
|
|
+ showDefault
|
|
|
+ );
|
|
|
+ if (picked === null || picked < 0 || picked >= showList.length) throw new Error("已取消导入");
|
|
|
+ showToast("正在获取 " + showList[picked].name + " 课表...");
|
|
|
+ const html = await fetchCourseHtml(params, showList[picked].id);
|
|
|
+ courses = parseCoursesFromTaskActivityScript(html);
|
|
|
+ timeSlots = parseTimeSlotsFromHtml(html);
|
|
|
+ if (!courses.length) throw new Error("该学期未解析到课程,请确认有课或尝试其他学期");
|
|
|
+ }
|
|
|
+ await save(courses, timeSlots);
|
|
|
+ showToast("导入成功,共 " + courses.length + " 门课程");
|
|
|
+ if (typeof window.shiguangBridge !== "undefined" && window.shiguangBridge.notifyTaskCompletion) {
|
|
|
+ window.shiguangBridge.notifyTaskCompletion();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ (async function bootstrap() {
|
|
|
+ try {
|
|
|
+ await runImportFlow();
|
|
|
+ } catch (e) {
|
|
|
+ console.error("[HPU适配]", e);
|
|
|
+ showToast("导入失败:" + (e && e.message ? e.message : String(e)));
|
|
|
+ }
|
|
|
+ })();
|
|
|
+})();
|