02211610
This commit is contained in:
parent
44a57ce0bb
commit
92983baf52
|
@ -1,7 +1,5 @@
|
||||||
VITE_APP_TUS_URL=http://localhost:8080
|
|
||||||
VITE_APP_API_URL=http://localhost:3000
|
|
||||||
VITE_APP_SERVER_IP=192.168.252.239
|
VITE_APP_SERVER_IP=192.168.252.239
|
||||||
VITE_APP_SERVER_PORT=3000
|
VITE_APP_SERVER_PORT=3000
|
||||||
VITE_APP_UPLOAD_PORT=80
|
VITE_APP_FILE_PORT=80
|
||||||
VITE_APP_VERSION=0.3.0
|
VITE_APP_VERSION=0.3.0
|
||||||
VITE_APP_APP_NAME=MOOC
|
VITE_APP_APP_NAME=MOOC
|
||||||
|
|
|
@ -1,11 +1,41 @@
|
||||||
|
/**
|
||||||
|
* 错误处理模块 - 全局路由级错误展示组件
|
||||||
|
* 功能: 捕获React Router路由层级错误并展示可视化错误信息
|
||||||
|
* 特性:
|
||||||
|
* - 自动解析路由错误对象
|
||||||
|
* - 自适应错误信息展示
|
||||||
|
* - 响应式布局设计
|
||||||
|
*/
|
||||||
import { useRouteError } from "react-router-dom";
|
import { useRouteError } from "react-router-dom";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误展示页面组件
|
||||||
|
* @核心功能 呈现标准化错误界面,用于处理应用程序的路由层级错误
|
||||||
|
* @设计模式 采用展示型组件模式,完全解耦业务逻辑实现纯UI展示
|
||||||
|
* @使用示例 在React Router的RouterProvider中配置errorElement={<ErrorPage/>}
|
||||||
|
*/
|
||||||
export default function ErrorPage() {
|
export default function ErrorPage() {
|
||||||
|
// 使用React Router提供的Hook获取路由错误对象
|
||||||
|
// 类型定义为any以兼容React Router不同版本的类型差异
|
||||||
const error: any = useRouteError();
|
const error: any = useRouteError();
|
||||||
return <div className=" flex justify-center items-center pt-64 ">
|
|
||||||
<div className=" flex flex-col gap-4">
|
return (
|
||||||
<div className=" text-xl font-bold text-primary">哦?页面似乎出错了...</div>
|
// 主容器: 基于Flex的垂直水平双居中布局
|
||||||
<div className=" text-tertiary" >{error?.statusText || error?.message}</div>
|
// pt-64: 顶部留白实现视觉层次结构
|
||||||
|
<div className="flex justify-center items-center pt-64">
|
||||||
|
{/* 内容区块: 采用纵向弹性布局控制内部元素间距 */}
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
{/* 主标题: 强调性文字样式配置 */}
|
||||||
|
<div className="text-xl font-bold text-primary">
|
||||||
|
哦?页面似乎出错了...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 错误详情: 动态渲染错误信息,实现优雅降级策略 */}
|
||||||
|
{/* 使用可选链操作符防止未定义错误,信息优先级: statusText > message */}
|
||||||
|
<div className="text-tertiary">
|
||||||
|
{error?.statusText || error?.message}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@ import HeroSection from './components/HeroSection';
|
||||||
import CategorySection from './components/CategorySection';
|
import CategorySection from './components/CategorySection';
|
||||||
import CoursesSection from './components/CoursesSection';
|
import CoursesSection from './components/CoursesSection';
|
||||||
import FeaturedTeachersSection from './components/FeaturedTeachersSection';
|
import FeaturedTeachersSection from './components/FeaturedTeachersSection';
|
||||||
|
import { TusUploader } from '@web/src/components/common/uploader/TusUploader';
|
||||||
const HomePage = () => {
|
const HomePage = () => {
|
||||||
const mockCourses = [
|
const mockCourses = [
|
||||||
{
|
{
|
||||||
|
@ -105,6 +106,7 @@ const HomePage = () => {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen">
|
<div className="min-h-screen">
|
||||||
<HeroSection />
|
<HeroSection />
|
||||||
|
<TusUploader></TusUploader>
|
||||||
<CoursesSection
|
<CoursesSection
|
||||||
title="推荐课程"
|
title="推荐课程"
|
||||||
description="最受欢迎的精品课程,助你快速成长"
|
description="最受欢迎的精品课程,助你快速成长"
|
||||||
|
|
|
@ -2,5 +2,5 @@ import MindEditor from "@web/src/components/common/editor/MindEditor";
|
||||||
// import MindElixir, { MindElixirInstance } from "mind-elixir";
|
// import MindElixir, { MindElixirInstance } from "mind-elixir";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
export default function PathsPage() {
|
export default function PathsPage() {
|
||||||
// return <MindEditor></MindEditor>
|
return <MindEditor></MindEditor>
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,8 @@ import {
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { Upload, Progress, Button } from "antd";
|
import { Upload, Progress, Button } from "antd";
|
||||||
import type { UploadFile } from "antd";
|
|
||||||
import { useTusUpload } from "@web/src/hooks/useTusUpload";
|
import { useTusUpload } from "@web/src/hooks/useTusUpload";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { getCompressedImageUrl } from "@nice/utils";
|
|
||||||
import { api } from "@nice/client";
|
|
||||||
|
|
||||||
export interface TusUploaderProps {
|
export interface TusUploaderProps {
|
||||||
value?: string[];
|
value?: string[];
|
||||||
onChange?: (value: string[]) => void;
|
onChange?: (value: string[]) => void;
|
||||||
|
@ -30,16 +26,7 @@ export const TusUploader = ({
|
||||||
onChange,
|
onChange,
|
||||||
multiple = true,
|
multiple = true,
|
||||||
}: TusUploaderProps) => {
|
}: TusUploaderProps) => {
|
||||||
const { data: files } = api.resource.findMany.useQuery({
|
|
||||||
where: {
|
|
||||||
fileId: { in: value },
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
fileId: true,
|
|
||||||
title: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const { handleFileUpload, uploadProgress } = useTusUpload();
|
const { handleFileUpload, uploadProgress } = useTusUpload();
|
||||||
const [uploadingFiles, setUploadingFiles] = useState<UploadingFile[]>([]);
|
const [uploadingFiles, setUploadingFiles] = useState<UploadingFile[]>([]);
|
||||||
const [completedFiles, setCompletedFiles] = useState<UploadingFile[]>(
|
const [completedFiles, setCompletedFiles] = useState<UploadingFile[]>(
|
||||||
|
@ -74,6 +61,7 @@ export const TusUploader = ({
|
||||||
|
|
||||||
const handleBeforeUpload = useCallback(
|
const handleBeforeUpload = useCallback(
|
||||||
(file: File) => {
|
(file: File) => {
|
||||||
|
|
||||||
const fileKey = `${file.name}-${Date.now()}`;
|
const fileKey = `${file.name}-${Date.now()}`;
|
||||||
|
|
||||||
setUploadingFiles((prev) => [
|
setUploadingFiles((prev) => [
|
||||||
|
@ -151,7 +139,7 @@ export const TusUploader = ({
|
||||||
name="files"
|
name="files"
|
||||||
multiple={multiple}
|
multiple={multiple}
|
||||||
showUploadList={false}
|
showUploadList={false}
|
||||||
style={{ background: "transparent", borderStyle: "none" }}
|
|
||||||
beforeUpload={handleBeforeUpload}>
|
beforeUpload={handleBeforeUpload}>
|
||||||
<p className="ant-upload-drag-icon">
|
<p className="ant-upload-drag-icon">
|
||||||
<UploadOutlined />
|
<UploadOutlined />
|
||||||
|
|
|
@ -33,15 +33,9 @@ import {
|
||||||
useSortable,
|
useSortable,
|
||||||
verticalListSortingStrategy,
|
verticalListSortingStrategy,
|
||||||
} from "@dnd-kit/sortable";
|
} from "@dnd-kit/sortable";
|
||||||
import { CSS } from "@dnd-kit/utilities";
|
|
||||||
import QuillEditor from "@web/src/components/common/editor/quill/QuillEditor";
|
|
||||||
import { TusUploader } from "@web/src/components/common/uploader/TusUploader";
|
|
||||||
import { Lecture, LectureType, PostType } from "@nice/common";
|
import { Lecture, LectureType, PostType } from "@nice/common";
|
||||||
import { useCourseEditor } from "../../context/CourseEditorContext";
|
import { useCourseEditor } from "../../context/CourseEditorContext";
|
||||||
import { usePost } from "@nice/client";
|
import { usePost } from "@nice/client";
|
||||||
import toast from "react-hot-toast";
|
|
||||||
import { CourseContentFormHeader } from "./CourseContentFormHeader";
|
|
||||||
import { CourseSectionEmpty } from "./CourseSectionEmpty";
|
|
||||||
import { LectureData, SectionData } from "./interface";
|
import { LectureData, SectionData } from "./interface";
|
||||||
import { SortableLecture } from "./SortableLecture";
|
import { SortableLecture } from "./SortableLecture";
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ export const env: {
|
||||||
APP_NAME: string;
|
APP_NAME: string;
|
||||||
SERVER_IP: string;
|
SERVER_IP: string;
|
||||||
VERSION: string;
|
VERSION: string;
|
||||||
UPLOAD_PORT: string;
|
FILE_PORT: string;
|
||||||
SERVER_PORT: string;
|
SERVER_PORT: string;
|
||||||
} = {
|
} = {
|
||||||
APP_NAME: import.meta.env.PROD
|
APP_NAME: import.meta.env.PROD
|
||||||
|
@ -11,9 +11,9 @@ export const env: {
|
||||||
SERVER_IP: import.meta.env.PROD
|
SERVER_IP: import.meta.env.PROD
|
||||||
? (window as any).env.VITE_APP_SERVER_IP
|
? (window as any).env.VITE_APP_SERVER_IP
|
||||||
: import.meta.env.VITE_APP_SERVER_IP,
|
: import.meta.env.VITE_APP_SERVER_IP,
|
||||||
UPLOAD_PORT: import.meta.env.PROD
|
FILE_PORT: import.meta.env.PROD
|
||||||
? (window as any).env.VITE_APP_UPLOAD_PORT
|
? (window as any).env.VITE_APP_FILE_PORT
|
||||||
: import.meta.env.VITE_APP_UPLOAD_PORT,
|
: import.meta.env.VITE_APP_FILE_PORT,
|
||||||
SERVER_PORT: import.meta.env.PROD
|
SERVER_PORT: import.meta.env.PROD
|
||||||
? (window as any).env.VITE_APP_SERVER_PORT
|
? (window as any).env.VITE_APP_SERVER_PORT
|
||||||
: import.meta.env.VITE_APP_SERVER_PORT,
|
: import.meta.env.VITE_APP_SERVER_PORT,
|
||||||
|
|
|
@ -2,11 +2,6 @@ import { useState } from "react";
|
||||||
import * as tus from "tus-js-client";
|
import * as tus from "tus-js-client";
|
||||||
import { env } from "../env";
|
import { env } from "../env";
|
||||||
import { getCompressedImageUrl } from "@nice/utils";
|
import { getCompressedImageUrl } from "@nice/utils";
|
||||||
// useTusUpload.ts
|
|
||||||
interface UploadProgress {
|
|
||||||
fileId: string;
|
|
||||||
progress: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UploadResult {
|
interface UploadResult {
|
||||||
compressedUrl: string;
|
compressedUrl: string;
|
||||||
|
@ -35,9 +30,7 @@ export function useTusUpload() {
|
||||||
if (uploadIndex === -1 || uploadIndex + 4 >= parts.length) {
|
if (uploadIndex === -1 || uploadIndex + 4 >= parts.length) {
|
||||||
throw new Error("Invalid upload URL format");
|
throw new Error("Invalid upload URL format");
|
||||||
}
|
}
|
||||||
console.log(env.UPLOAD_PORT);
|
const resUrl = `http://${env.SERVER_IP}:${env.FILE_PORT}/uploads/${parts.slice(uploadIndex + 1, uploadIndex + 6).join("/")}`;
|
||||||
const resUrl = `http://${env.SERVER_IP}:${env.UPLOAD_PORT}/uploads/${parts.slice(uploadIndex + 1, uploadIndex + 6).join("/")}`;
|
|
||||||
|
|
||||||
return resUrl;
|
return resUrl;
|
||||||
};
|
};
|
||||||
const handleFileUpload = async (
|
const handleFileUpload = async (
|
||||||
|
@ -46,11 +39,13 @@ export function useTusUpload() {
|
||||||
onError: (error: Error) => void,
|
onError: (error: Error) => void,
|
||||||
fileKey: string // 添加文件唯一标识
|
fileKey: string // 添加文件唯一标识
|
||||||
) => {
|
) => {
|
||||||
|
// console.log()
|
||||||
setIsUploading(true);
|
setIsUploading(true);
|
||||||
setUploadProgress((prev) => ({ ...prev, [fileKey]: 0 }));
|
setUploadProgress((prev) => ({ ...prev, [fileKey]: 0 }));
|
||||||
setUploadError(null);
|
setUploadError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log(`http://${env.SERVER_IP}:${env.SERVER_PORT}/upload`)
|
||||||
const upload = new tus.Upload(file, {
|
const upload = new tus.Upload(file, {
|
||||||
endpoint: `http://${env.SERVER_IP}:${env.SERVER_PORT}/upload`,
|
endpoint: `http://${env.SERVER_IP}:${env.SERVER_PORT}/upload`,
|
||||||
retryDelays: [0, 1000, 3000, 5000],
|
retryDelays: [0, 1000, 3000, 5000],
|
||||||
|
@ -97,6 +92,7 @@ export function useTusUpload() {
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
setIsUploading(false);
|
setIsUploading(false);
|
||||||
setUploadError(error.message);
|
setUploadError(error.message);
|
||||||
|
console.log(error)
|
||||||
onError(error);
|
onError(error);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"dev": "pnpm run --parallel dev"
|
"dev": "pnpm run --parallel dev",
|
||||||
|
"db:clear": "pnpm --filter common run db:clear"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "insiinc",
|
"author": "insiinc",
|
||||||
|
|
|
@ -413,7 +413,6 @@ model Animal {
|
||||||
personId String?
|
personId String?
|
||||||
person Person? @relation(fields: [personId], references: [id])
|
person Person? @relation(fields: [personId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Person {
|
model Person {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
|
|
Loading…
Reference in New Issue