Template
fix: 修复课程运行时初始化与拖拽测试
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
d77281ff0d
commit
1e02c7835e
6 files changed
+173
-154
No files matched your search
-37
@@ -5,8 +5,6 @@ import { CourseCanvas, useCourseCanvas } from "@/course-runtime/canvas";
|
||||
import { OrientationGuard } from "@/course-runtime/orientation";
|
||||
import { Navigation } from "./components/business";
|
||||
import { registerAppPortalRoot } from "./components/common/canvasPortal";
|
||||
import { useUserStore } from "./stores";
|
||||
import { isVideoAjaxAvailable } from "./utils";
|
||||
|
||||
function CourseShell() {
|
||||
const { portalContainer } = useCourseCanvas();
|
||||
@@ -25,41 +23,6 @@ function CourseShell() {
|
||||
}
|
||||
|
||||
function App() {
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const courseId = params.get("courseId") ?? params.get("courseid");
|
||||
const uid = params.get("uid");
|
||||
const forceYiban = params.get("yiban") === "1";
|
||||
const hasYibanParams = !!(courseId || uid);
|
||||
const hasVideoAjax = isVideoAjaxAvailable();
|
||||
const isYibanEnv = import.meta.env.DEV
|
||||
? hasYibanParams && forceYiban
|
||||
: hasYibanParams;
|
||||
const storageMode = isYibanEnv && hasVideoAjax ? "yiban" : "session";
|
||||
const userStore = useUserStore.getState();
|
||||
|
||||
userStore.setYibanConnectionStatus(
|
||||
storageMode === "yiban" ? "connected" : "disconnected",
|
||||
);
|
||||
if (courseId) userStore.setCourseId(courseId);
|
||||
if (uid) userStore.setUserInfo({ userId: uid });
|
||||
|
||||
console.info(
|
||||
storageMode === "yiban"
|
||||
? "[yiban] initialized"
|
||||
: "[yiban] use session storage fallback",
|
||||
{
|
||||
courseId,
|
||||
uid,
|
||||
isDev: import.meta.env.DEV,
|
||||
forceYiban,
|
||||
hasYibanParams,
|
||||
hasVideoAjax,
|
||||
storageMode,
|
||||
},
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<OrientationGuard />
|
||||
|
||||
@@ -4,6 +4,9 @@ import { RouterProvider } from "react-router-dom";
|
||||
import "./utils/vconsole";
|
||||
import "./styles/index.css";
|
||||
import { router } from "./router/index.tsx";
|
||||
import { initializeYibanSession } from "./utils/yiban";
|
||||
|
||||
initializeYibanSession();
|
||||
|
||||
const rootElement = document.getElementById("root");
|
||||
if (!rootElement) {
|
||||
|
||||
+42
-30
@@ -1,47 +1,59 @@
|
||||
// 图片资源映射 - 使用静态导入确保构建时包含所有图片
|
||||
const imageModules = import.meta.glob(
|
||||
"../assets/images/**/*.{png,jpg,jpeg,gif,svg,webp}",
|
||||
{
|
||||
eager: true,
|
||||
},
|
||||
{ eager: true },
|
||||
);
|
||||
|
||||
// 访问静态图片 - 使用静态导入确保构建后路径正确
|
||||
export const getStaticImage = (path: string) => {
|
||||
try {
|
||||
// 确保路径以正确的格式结尾
|
||||
const imagePath =
|
||||
path.endsWith(".png") ||
|
||||
path.endsWith(".jpg") ||
|
||||
path.endsWith(".jpeg") ||
|
||||
path.endsWith(".gif") ||
|
||||
path.endsWith(".svg") ||
|
||||
path.endsWith(".webp")
|
||||
? `../assets/images/${path}`
|
||||
: `../assets/images/${path}.png`;
|
||||
const IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp"];
|
||||
|
||||
// 使用静态导入的模块
|
||||
const module = imageModules[imagePath];
|
||||
if (module && typeof module === "object" && "default" in module) {
|
||||
return (module as { default: string }).default;
|
||||
}
|
||||
/**
|
||||
* 把传入路径规范化为相对 src/assets/images/ 的路径
|
||||
* 兼容 `src/assets/images/foo.png`、`assets/images/foo.png`、`/foo.png`、`foo.png` 等写法
|
||||
*/
|
||||
function normalizeImagePath(path: string): string {
|
||||
return path
|
||||
.replace(/\\/g, "/")
|
||||
.replace(/^\/+/, "")
|
||||
.replace(/^(?:src\/)?assets\/images\//, "");
|
||||
}
|
||||
|
||||
// 回退到 URL 方式
|
||||
return new URL(imagePath, import.meta.url).href;
|
||||
} catch (error) {
|
||||
console.error("Failed to load image:", path, error);
|
||||
return "";
|
||||
/**
|
||||
* 访问静态图片(src/assets/images/ 下的构建期资源)
|
||||
* 未命中时返回空字符串并告警,调用方据此回退
|
||||
*/
|
||||
export const getStaticImage = (path: string): string => {
|
||||
const normalized = normalizeImagePath(path);
|
||||
const candidate = IMAGE_EXTENSIONS.some((ext) => normalized.endsWith(ext))
|
||||
? normalized
|
||||
: `${normalized}.png`;
|
||||
|
||||
const module = imageModules[`../assets/images/${candidate}`] as
|
||||
| { default: string }
|
||||
| undefined;
|
||||
|
||||
if (module && typeof module.default === "string") {
|
||||
return module.default;
|
||||
}
|
||||
|
||||
console.warn(
|
||||
`[assets] 未找到图片 "${path}",请确认文件位于 src/assets/images/ 下并已提交。`,
|
||||
);
|
||||
return "";
|
||||
};
|
||||
|
||||
// 获取背景图片的专用函数
|
||||
export const getBackgroundImage = (path: string) => {
|
||||
/**
|
||||
* 获取背景图片的 CSS url 值
|
||||
* 找不到时返回 "none"
|
||||
*/
|
||||
export const getBackgroundImage = (path: string): string => {
|
||||
const imageUrl = getStaticImage(path);
|
||||
return imageUrl ? `url(${imageUrl})` : "none";
|
||||
};
|
||||
|
||||
// 获取 public 文件夹中的资源路径(用于 Word 文档等)
|
||||
export const getPublicPath = (path: string) => {
|
||||
/**
|
||||
* 获取 public 文件夹中的资源路径(用于 Word 文档等非构建期资源)
|
||||
*/
|
||||
export const getPublicPath = (path: string): string => {
|
||||
// 移除开头的斜杠(如果有)
|
||||
const cleanPath = path.startsWith("/") ? path.slice(1) : path;
|
||||
// 使用 Vite 的 BASE_URL 环境变量来处理基础路径
|
||||
|
||||
+41
-4
@@ -40,6 +40,45 @@ function warnVideoAjaxUnavailable(description: string) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 URL 参数初始化易班会话状态
|
||||
* 在 React 渲染前调用,确保页面首次读取进度时 courseId / userId 已写入 store
|
||||
*/
|
||||
export function initializeYibanSession() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const courseId = params.get("courseId") ?? params.get("courseid");
|
||||
const uid = params.get("uid");
|
||||
const forceYiban = params.get("yiban") === "1";
|
||||
const hasYibanParams = !!(courseId || uid);
|
||||
const hasVideoAjax = isVideoAjaxAvailable();
|
||||
const isYibanEnv = import.meta.env.DEV
|
||||
? hasYibanParams && forceYiban
|
||||
: hasYibanParams;
|
||||
const storageMode = isYibanEnv && hasVideoAjax ? "yiban" : "session";
|
||||
const userStore = useUserStore.getState();
|
||||
|
||||
userStore.setYibanConnectionStatus(
|
||||
storageMode === "yiban" ? "connected" : "disconnected",
|
||||
);
|
||||
if (courseId) userStore.setCourseId(courseId);
|
||||
if (uid) userStore.setUserInfo({ userId: uid });
|
||||
|
||||
console.info(
|
||||
storageMode === "yiban"
|
||||
? "[yiban] initialized"
|
||||
: "[yiban] use session storage fallback",
|
||||
{
|
||||
courseId,
|
||||
uid,
|
||||
isDev: import.meta.env.DEV,
|
||||
forceYiban,
|
||||
hasYibanParams,
|
||||
hasVideoAjax,
|
||||
storageMode,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 VideoAjax 实例
|
||||
*/
|
||||
@@ -228,10 +267,7 @@ export async function getUserStorage(
|
||||
|
||||
if (backend === "session") {
|
||||
const itemValue = sessionStorage.getItem(getStorageKey(lessonId, key));
|
||||
return itemValue === undefined ||
|
||||
itemValue === null ||
|
||||
itemValue === "null" ||
|
||||
itemValue === ""
|
||||
return itemValue === null || itemValue === "null" || itemValue === ""
|
||||
? { status: "empty", content: "" }
|
||||
: { status: "success", content: itemValue };
|
||||
}
|
||||
@@ -466,6 +502,7 @@ export async function saveUserAnswer(params: {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 易班 API 以 getUserAnswer 写入答题记录,方法名与「保存」语义相反,属平台既有命名
|
||||
const result = await yibanStatusRequest<unknown>({
|
||||
method: (callback) =>
|
||||
getVideoAjax().getUserAnswer(
|
||||
|
||||
Reference in new issue
Block a user