Merge branch 'main' of http://113.45.157.195:3003/insiinc/re-mooc
This commit is contained in:
commit
b7a2dad7a5
|
@ -155,7 +155,7 @@ export class UserProfileService {
|
||||||
where: { id },
|
where: { id },
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
avatar:true,
|
avatar: true,
|
||||||
deptId: true,
|
deptId: true,
|
||||||
department: true,
|
department: true,
|
||||||
domainId: true,
|
domainId: true,
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { useContext, useEffect, useState } from "react";
|
||||||
import { Button, Form, Input, message, theme } from "antd";
|
import { Button, Form, Input, message, theme } from "antd";
|
||||||
import { useAppConfig } from "@nice/client";
|
import { useAppConfig } from "@nice/client";
|
||||||
import { useAuth } from "@web/src/providers/auth-provider";
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
|
import MultiImageUploader from "@web/src/components/common/uploader/MultiImageUploader";
|
||||||
|
|
||||||
import FixedHeader from "@web/src/components/layout/fix-header";
|
import FixedHeader from "@web/src/components/layout/fix-header";
|
||||||
import { useForm } from "antd/es/form/Form";
|
import { useForm } from "antd/es/form/Form";
|
||||||
|
@ -26,6 +27,7 @@ export default function BaseSettingPage() {
|
||||||
const { user, hasSomePermissions } = useAuth();
|
const { user, hasSomePermissions } = useAuth();
|
||||||
const context = useContext(MainLayoutContext);
|
const context = useContext(MainLayoutContext);
|
||||||
const pageWidth = context?.pageWidth;
|
const pageWidth = context?.pageWidth;
|
||||||
|
// const [meta,setMeta ] = useState<>(baseSetting);
|
||||||
function handleFieldsChange() {
|
function handleFieldsChange() {
|
||||||
setIsFormChanged(true);
|
setIsFormChanged(true);
|
||||||
}
|
}
|
||||||
|
@ -129,7 +131,7 @@ export default function BaseSettingPage() {
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="运维单位"
|
label="运维单位"
|
||||||
name={["appConfig", "slides"]}>
|
name={["appConfig", "slides"]}>
|
||||||
<Input></Input>
|
<MultiImageUploader></MultiImageUploader>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
{/* <div
|
{/* <div
|
||||||
|
@ -172,7 +174,7 @@ export default function BaseSettingPage() {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
type="primary"
|
type="primary"
|
||||||
ghost>
|
ghost>
|
||||||
清除行模型缓存
|
清除行模型缓存
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -69,6 +69,8 @@ const CategorySection = () => {
|
||||||
const [displayedCategories,setDisplayedCategories] = useState<TermDto[]>([])
|
const [displayedCategories,setDisplayedCategories] = useState<TermDto[]>([])
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(courseCategoriesData);
|
console.log(courseCategoriesData);
|
||||||
|
// 如果 showAll 为 true,则显示所有分类数据,
|
||||||
|
// 如果 showAll 为 false,则只显示前 8 个分类数据,
|
||||||
if(!isLoading){
|
if(!isLoading){
|
||||||
if(showAll){
|
if(showAll){
|
||||||
setDisplayedCategories(courseCategoriesData)
|
setDisplayedCategories(courseCategoriesData)
|
||||||
|
|
|
@ -6,7 +6,12 @@ import {
|
||||||
TeamOutlined,
|
TeamOutlined,
|
||||||
ArrowRightOutlined,
|
ArrowRightOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { CourseDto, TaxonomySlug, TermDto } from "@nice/common";
|
import {
|
||||||
|
courseDetailSelect,
|
||||||
|
CourseDto,
|
||||||
|
TaxonomySlug,
|
||||||
|
TermDto,
|
||||||
|
} from "@nice/common";
|
||||||
import { api } from "@nice/client";
|
import { api } from "@nice/client";
|
||||||
import CourseCard from "../../courses/components/CourseCard";
|
import CourseCard from "../../courses/components/CourseCard";
|
||||||
import { CoursesSectionTag } from "./CoursesSectionTag";
|
import { CoursesSectionTag } from "./CoursesSectionTag";
|
||||||
|
@ -51,10 +56,7 @@ function useFetchCoursesByCategory(category: string) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
take: 8,
|
take: 8,
|
||||||
include: {
|
select: courseDetailSelect,
|
||||||
terms: true,
|
|
||||||
depts: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return { data, isLoading };
|
return { data, isLoading };
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Upload, Modal, message } from 'antd';
|
||||||
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { useTusUpload } from "@web/src/hooks/useTusUpload";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
|
|
||||||
|
export interface MultiImageUploadProps {
|
||||||
|
value?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
className?: string;
|
||||||
|
onChange?: (value: string) => void;
|
||||||
|
compressed?: boolean;
|
||||||
|
style?: React.CSSProperties; // 添加style属性
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UploadFile {
|
||||||
|
name: string;
|
||||||
|
progress: number;
|
||||||
|
status: "uploading" | "done" | "error";
|
||||||
|
url?: string;
|
||||||
|
fileKey?: string;
|
||||||
|
uid: string;
|
||||||
|
}
|
||||||
|
const MultiImageUpload: React.FC<MultiImageUploadProps> = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
compressed = false,
|
||||||
|
className,
|
||||||
|
placeholder = "点击上传",
|
||||||
|
style, // 解构style属性
|
||||||
|
}) => {
|
||||||
|
const [fileList, setFileList] = useState<UploadFile[] | null>(null); // 存储已上传的文件列表
|
||||||
|
const [previewVisible, setPreviewVisible] = useState(false); // 控制预览模态框的显示
|
||||||
|
const [previewImage, setPreviewImage] = useState(''); // 当前预览的图片URL
|
||||||
|
const { handleFileUpload, uploadProgress } = useTusUpload();
|
||||||
|
const [compressedUrl, setCompressedUrl] = useState<string>(value || "");
|
||||||
|
const [url, setUrl] = useState<string>(value || "");
|
||||||
|
const [uploading, setUploading] = useState(false);
|
||||||
|
|
||||||
|
// 处理文件上传前的校验
|
||||||
|
const beforeUpload = (file) => {
|
||||||
|
const isImage = file.type.startsWith('image/');
|
||||||
|
if (!isImage) {
|
||||||
|
message.error('只能上传图片文件!');
|
||||||
|
}
|
||||||
|
const isLt10M = file.size / 1024 / 1024 < 10;
|
||||||
|
if (!isLt10M) {
|
||||||
|
message.error('图片大小不能超过10MB!');
|
||||||
|
}
|
||||||
|
return isImage && isLt10M;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理文件列表变化
|
||||||
|
const handleChange = async ({ fileList }) => {
|
||||||
|
//setFileList(fileList);
|
||||||
|
console.log(fileList);
|
||||||
|
const imageUrls = fileList.map(file => {
|
||||||
|
return URL.createObjectURL(file.originFileObj)
|
||||||
|
});
|
||||||
|
console.log("imageUrls", imageUrls);
|
||||||
|
|
||||||
|
const newFileList = fileList.map(file => {
|
||||||
|
return {
|
||||||
|
name: file.name,
|
||||||
|
progress: 0,
|
||||||
|
status: "uploading",
|
||||||
|
//uid: file.uid,
|
||||||
|
fileKey: `${file.name}-${Date.now()}`,
|
||||||
|
//url: file.url,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log("newFileList", newFileList);
|
||||||
|
setUploading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resFileList = newFileList.map(async (file, index) => {
|
||||||
|
const uploadedUrl = await new Promise<string>((resolve, reject) => {
|
||||||
|
handleFileUpload(
|
||||||
|
fileList[index].originFileObj,
|
||||||
|
(result) => {
|
||||||
|
() => {
|
||||||
|
return {
|
||||||
|
...newFileList[index],
|
||||||
|
progress: 100,
|
||||||
|
status: "done",
|
||||||
|
fileId: result.fileId,
|
||||||
|
url: result.url,
|
||||||
|
compressedUrl: result.compressedUrl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// setFile((prev) => ({
|
||||||
|
// ...prev!,
|
||||||
|
// progress: 100,
|
||||||
|
// status: "done",
|
||||||
|
// fileId: result.fileId,
|
||||||
|
// url: result.url,
|
||||||
|
// compressedUrl: result.compressedUrl,
|
||||||
|
// }));
|
||||||
|
setUrl(result.url);
|
||||||
|
setCompressedUrl(result.compressedUrl);
|
||||||
|
// 直接使用 result 中的最新值
|
||||||
|
resolve(compressed ? result.compressedUrl : result.url);
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
reject(error);
|
||||||
|
},
|
||||||
|
newFileList[index]?.fileKey
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
// await new Promise((resolve) => setTimeout(resolve,4999)); // 方法1:使用 await 暂停执行
|
||||||
|
// 使用 resolved 的最新值调用 onChange
|
||||||
|
// 强制刷新 Avatar 组件
|
||||||
|
// setAvatarKey((prev) => prev + 1); // 修改 key 强制重新挂载
|
||||||
|
onChange?.(resFileList);
|
||||||
|
console.log(resFileList);
|
||||||
|
toast.success("图片上传成功");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("上传错误:", error);
|
||||||
|
toast.error("图片上传失败");
|
||||||
|
// setFile((prev) => ({ ...prev!, status: "error" }));
|
||||||
|
} finally {
|
||||||
|
setUploading(false);
|
||||||
|
}
|
||||||
|
};uploadProgress
|
||||||
|
|
||||||
|
// 处理预览
|
||||||
|
const handlePreview = async (file) => {
|
||||||
|
if (!file.url && !file.preview) {
|
||||||
|
file.preview = await getBase64(file.originFileObj);
|
||||||
|
}
|
||||||
|
setPreviewImage(file.url || file.preview);
|
||||||
|
setPreviewVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭预览模态框
|
||||||
|
const handleCancel = () => setPreviewVisible(false);
|
||||||
|
|
||||||
|
// 将文件转换为Base64格式(用于预览)
|
||||||
|
const getBase64 = (file) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.onload = () => resolve(reader.result);
|
||||||
|
reader.onerror = (error) => reject(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Upload.Dragger
|
||||||
|
listType="picture-card" // 卡片样式
|
||||||
|
fileList={fileList} // 已上传的文件列表
|
||||||
|
beforeUpload={beforeUpload} // 上传前的校验
|
||||||
|
onChange={handleChange} // 文件列表变化时的回调
|
||||||
|
onPreview={handlePreview} // 预览回调
|
||||||
|
multiple // 支持多选
|
||||||
|
// style={{ width: '200px' }}
|
||||||
|
>
|
||||||
|
<p className="ant-upload-drag-icon">
|
||||||
|
</p>
|
||||||
|
<p className="ant-upload-text">点击或拖拽文件到此区域上传</p>
|
||||||
|
<p className="ant-upload-hint">支持单个或批量上传</p>
|
||||||
|
</Upload.Dragger>
|
||||||
|
<Modal visible={previewVisible} footer={null} onCancel={handleCancel}>
|
||||||
|
<img alt="预览" style={{ width: '100%' }} src={previewImage} />
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MultiImageUpload;
|
|
@ -0,0 +1,18 @@
|
||||||
|
import AvatarUploader from "./AvatarUploader"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
interface TestUploaderProps {
|
||||||
|
value?: string[],
|
||||||
|
onChange?: (value: string[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TestUploader({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
}: TestUploaderProps) {
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<AvatarUploader></AvatarUploader>
|
||||||
|
</>
|
||||||
|
}
|
|
@ -77,7 +77,7 @@ export type Course = Post & {
|
||||||
export type CourseDto = Course & {
|
export type CourseDto = Course & {
|
||||||
enrollments?: Enrollment[];
|
enrollments?: Enrollment[];
|
||||||
sections?: SectionDto[];
|
sections?: SectionDto[];
|
||||||
terms: Term[];
|
terms: TermDto[];
|
||||||
lectureCount?: number;
|
lectureCount?: number;
|
||||||
depts:Department[]
|
depts:Department[]
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue