Merge branch 'main' of http://113.45.157.195:3003/insiinc/re-mooc
This commit is contained in:
commit
adefebae99
|
@ -3,6 +3,5 @@ import { useParams } from "react-router-dom";
|
|||
|
||||
export function CourseDetailPage() {
|
||||
const { id, lectureId } = useParams();
|
||||
console.log("Course ID:", id);
|
||||
return <CourseDetail id={id} lectureId={lectureId}></CourseDetail>;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import React, { useState, useMemo } from "react";
|
||||
import React, { useState, useMemo, ReactNode } from "react";
|
||||
import { Typography, Skeleton } from "antd";
|
||||
import { TaxonomySlug, TermDto } from "@nice/common";
|
||||
import { api } from "@nice/client";
|
||||
import { CoursesSectionTag } from "./CoursesSectionTag";
|
||||
import LookForMore from "./LookForMore";
|
||||
import PostList from "@web/src/components/models/course/list/PostList";
|
||||
import PostCard from "@web/src/components/models/post/PostCard";
|
||||
import CourseCard from "@web/src/components/models/post/SubPost/CourseCard";
|
||||
interface GetTaxonomyProps {
|
||||
categories: string[];
|
||||
isLoading: boolean;
|
||||
|
@ -35,11 +33,17 @@ interface CoursesSectionProps {
|
|||
title: string;
|
||||
description: string;
|
||||
initialVisibleCoursesCount?: number;
|
||||
postType:string;
|
||||
render?:(post)=>ReactNode;
|
||||
to:string
|
||||
}
|
||||
const CoursesSection: React.FC<CoursesSectionProps> = ({
|
||||
title,
|
||||
description,
|
||||
initialVisibleCoursesCount = 8,
|
||||
postType,
|
||||
render,
|
||||
to
|
||||
}) => {
|
||||
const [selectedCategory, setSelectedCategory] = useState<string>("全部");
|
||||
const gateGory: GetTaxonomyProps = useGetTaxonomy({
|
||||
|
@ -83,7 +87,7 @@ const CoursesSection: React.FC<CoursesSectionProps> = ({
|
|||
)}
|
||||
</div>
|
||||
<PostList
|
||||
renderItem={(post) => <CourseCard post={post}></CourseCard>}
|
||||
renderItem={(post) => render(post)}
|
||||
params={{
|
||||
page: 1,
|
||||
pageSize: initialVisibleCoursesCount,
|
||||
|
@ -95,11 +99,12 @@ const CoursesSection: React.FC<CoursesSectionProps> = ({
|
|||
},
|
||||
}
|
||||
: {},
|
||||
type: postType
|
||||
},
|
||||
}}
|
||||
showPagination={false}
|
||||
cols={4}></PostList>
|
||||
<LookForMore to={"/courses"}></LookForMore>
|
||||
<LookForMore to={to}></LookForMore>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
|
|
@ -57,7 +57,7 @@ const HeroSection = () => {
|
|||
{
|
||||
icon: <EyeOutlined />,
|
||||
value: statistics.reads,
|
||||
label: "观看次数",
|
||||
label: "播放次数",
|
||||
},
|
||||
];
|
||||
}, [statistics]);
|
||||
|
|
|
@ -11,7 +11,10 @@ export default function LookForMore({to}:{to:string}) {
|
|||
<div className="flex justify-end">
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() => navigate(to)}
|
||||
onClick={() => {
|
||||
navigate(to)
|
||||
window.scrollTo({top: 0,behavior: "smooth"});
|
||||
}}
|
||||
className="flex items-center gap-2 text-gray-600 hover:text-blue-600 font-medium transition-colors duration-300">
|
||||
查看更多
|
||||
<ArrowRightOutlined />
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import HeroSection from "./components/HeroSection";
|
||||
import CategorySection from "./components/CategorySection";
|
||||
import CoursesSection from "./components/CoursesSection";
|
||||
import { PostType } from "@nice/common";
|
||||
import PathCard from "@web/src/components/models/post/SubPost/PathCard";
|
||||
import CourseCard from "@web/src/components/models/post/SubPost/CourseCard";
|
||||
|
||||
|
||||
const HomePage = () => {
|
||||
|
@ -8,10 +11,21 @@ const HomePage = () => {
|
|||
return (
|
||||
<div className="min-h-screen">
|
||||
<HeroSection />
|
||||
<CoursesSection
|
||||
title="最受欢迎的思维导图"
|
||||
description="深受追捧的思维导图,点亮你的智慧人生"
|
||||
postType={PostType.PATH}
|
||||
render={(post)=><PathCard post={post}></PathCard>}
|
||||
to={"path"}
|
||||
/>
|
||||
<CoursesSection
|
||||
title="推荐课程"
|
||||
description="最受欢迎的精品课程,助你快速成长"
|
||||
postType={PostType.COURSE}
|
||||
render={(post)=> <CourseCard post={post}></CourseCard>}
|
||||
to={"/courses"}
|
||||
/>
|
||||
|
||||
<CategorySection />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -14,7 +14,7 @@ export default function FilterSection() {
|
|||
});
|
||||
};
|
||||
return (
|
||||
<div className=" flex z-0 p-6 flex-col rounded-lg mt-4 space-y-6 h-[820px] overscroll-contain overflow-x-hidden">
|
||||
<div className=" flex z-0 p-6 flex-col mt-4 space-y-6 overscroll-contain overflow-x-hidden">
|
||||
{showSearchMode && <SearchModeRadio></SearchModeRadio>}
|
||||
{taxonomies?.map((tax, index) => {
|
||||
const items = Object.entries(selectedTerms).find(
|
||||
|
|
|
@ -11,14 +11,14 @@ export default function SearchModeRadio() {
|
|||
|
||||
return (
|
||||
<Space direction="vertical" align="start" className="mb-2">
|
||||
<h3 className="text-lg font-medium mb-4">搜索模式</h3>
|
||||
<h3 className="text-lg font-medium mb-4">只搜索</h3>
|
||||
<Radio.Group
|
||||
value={searchMode}
|
||||
onChange={handleModeChange}
|
||||
buttonStyle="solid">
|
||||
<Radio.Button value={PostType.COURSE}>课程</Radio.Button>
|
||||
<Radio.Button value={PostType.PATH}>路径</Radio.Button>
|
||||
<Radio.Button value="both">全部</Radio.Button>
|
||||
<Radio.Button value={PostType.COURSE}>视频课程</Radio.Button>
|
||||
<Radio.Button value={PostType.PATH}>思维导图</Radio.Button>
|
||||
<Radio.Button value="both">所有资源</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Space>
|
||||
);
|
||||
|
|
|
@ -30,8 +30,10 @@ export function MainHeader() {
|
|||
<NavigationMenu />
|
||||
</div>
|
||||
|
||||
{/* 中间搜索区域 - 允许适当收缩但保持可用性 */}
|
||||
<div className="mx-4 flex-shrink md:flex-shrink-0 md:w-auto w-auto">
|
||||
|
||||
{/* 右侧区域 - 可以灵活收缩 */}
|
||||
<div className="flex justify-end gap-2 md:gap-4 flex-shrink">
|
||||
<div className="flex items-center gap-2 md:gap-4">
|
||||
<Input
|
||||
size="large"
|
||||
prefix={
|
||||
|
@ -60,14 +62,12 @@ export function MainHeader() {
|
|||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 右侧区域 - 可以灵活收缩 */}
|
||||
<div className="flex justify-end gap-2 md:gap-4 flex-shrink">
|
||||
<div className="flex items-center gap-2 md:gap-4">
|
||||
{isAuthenticated && (
|
||||
<>
|
||||
<Button
|
||||
size="large"
|
||||
shape="round"
|
||||
icon={<PlusOutlined></PlusOutlined>}
|
||||
onClick={() => {
|
||||
const url = id
|
||||
? `/course/${id}/editor`
|
||||
|
@ -82,19 +82,25 @@ export function MainHeader() {
|
|||
)}
|
||||
{isAuthenticated && (
|
||||
<Button
|
||||
size="large"
|
||||
shape="round"
|
||||
onClick={() => {
|
||||
window.location.href = "/path/editor";
|
||||
}}
|
||||
ghost type="primary"
|
||||
icon={<PlusOutlined></PlusOutlined>}>
|
||||
创建学习路径
|
||||
创建思维导图
|
||||
</Button>
|
||||
)}
|
||||
{isAuthenticated ? (
|
||||
<UserMenu />
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
shape="round"
|
||||
onClick={() => navigate("/login")}
|
||||
className="flex items-center space-x-1 bg-gradient-to-r from-blue-500 to-blue-600 text-white hover:from-blue-600 hover:to-blue-700 border-none shadow-md hover:shadow-lg transition-all"
|
||||
|
||||
icon={<UserOutlined />}>
|
||||
登录
|
||||
</Button>
|
||||
|
|
|
@ -11,8 +11,8 @@ export const NavigationMenu = () => {
|
|||
const menuItems = useMemo(() => {
|
||||
const baseItems = [
|
||||
{ key: "home", path: "/", label: "首页" },
|
||||
{ key: "path", path: "/path", label: "学习路径" },
|
||||
{ key: "courses", path: "/courses", label: "全部课程" },
|
||||
{ key: "path", path: "/path", label: "全部思维导图" },
|
||||
{ key: "courses", path: "/courses", label: "所有课程" },
|
||||
];
|
||||
|
||||
if (!isAuthenticated) {
|
||||
|
@ -20,9 +20,10 @@ export const NavigationMenu = () => {
|
|||
} else {
|
||||
return [
|
||||
...baseItems,
|
||||
{ key: "my-duty", path: "/my-duty", label: "我的授课" },
|
||||
{ key: "my-learning", path: "/my-learning", label: "我的课程" },
|
||||
{ key: "my-path", path: "/my-path", label: "我的路径" },
|
||||
{ key: "my-duty", path: "/my-duty", label: "我创建的课程" },
|
||||
{ key: "my-learning", path: "/my-learning", label: "我学习的课程" },
|
||||
{ key: "my-duty-path", path: "/my-duty-path", label: "我创建的思维导图" },
|
||||
{ key: "my-path", path: "/my-path", label: "我学习的思维导图" },
|
||||
];
|
||||
}
|
||||
}, [isAuthenticated]);
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import PostList from "@web/src/components/models/course/list/PostList";
|
||||
import { useAuth } from "@web/src/providers/auth-provider";
|
||||
import { useMainContext } from "../../layout/MainProvider";
|
||||
import { PostType } from "@nice/common";
|
||||
import PathCard from "@web/src/components/models/post/SubPost/PathCard";
|
||||
|
||||
export default function MyLearningListContainer() {
|
||||
const { user } = useAuth();
|
||||
const { searchCondition, termsCondition } = useMainContext();
|
||||
return (
|
||||
<>
|
||||
<PostList
|
||||
renderItem={(post) => <PathCard post={post}></PathCard>}
|
||||
params={{
|
||||
pageSize: 12,
|
||||
where: {
|
||||
type: PostType.PATH,
|
||||
students: {
|
||||
some: {
|
||||
id: user?.id,
|
||||
},
|
||||
},
|
||||
...termsCondition,
|
||||
...searchCondition,
|
||||
},
|
||||
}}
|
||||
cols={4}></PostList>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import { useEffect } from "react";
|
||||
import BasePostLayout from "../layout/BasePost/BasePostLayout";
|
||||
import { useMainContext } from "../layout/MainProvider";
|
||||
import { PostType } from "@nice/common";
|
||||
import MyDutyPathContainer from "./components/MyDutyPathContainer";
|
||||
|
||||
export default function MyDutyPathPage() {
|
||||
const { setSearchMode } = useMainContext();
|
||||
useEffect(() => {
|
||||
setSearchMode(PostType.PATH);
|
||||
}, [setSearchMode]);
|
||||
return (
|
||||
<BasePostLayout>
|
||||
<MyDutyPathContainer></MyDutyPathContainer>
|
||||
</BasePostLayout>
|
||||
);
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { Button, Card, Empty, Form, Space, Spin, message, theme } from "antd";
|
||||
import { Button, Empty, Form, Spin } from "antd";
|
||||
import NodeMenu from "./NodeMenu";
|
||||
import { api, usePost } from "@nice/client";
|
||||
import { api, usePost, useVisitor } from "@nice/client";
|
||||
import {
|
||||
ObjectType,
|
||||
PathDto,
|
||||
|
@ -8,6 +8,7 @@ import {
|
|||
PostType,
|
||||
Prisma,
|
||||
RolePerms,
|
||||
VisitType,
|
||||
} from "@nice/common";
|
||||
import TermSelect from "../../models/term/term-select";
|
||||
import DepartmentSelect from "../../models/department/department-select";
|
||||
|
@ -19,17 +20,20 @@ import { useTusUpload } from "@web/src/hooks/useTusUpload";
|
|||
import { useNavigate } from "react-router-dom";
|
||||
import { useAuth } from "@web/src/providers/auth-provider";
|
||||
import { MIND_OPTIONS } from "./constant";
|
||||
import { SaveOutlined } from "@ant-design/icons";
|
||||
export default function MindEditor({ id }: { id?: string }) {
|
||||
//containerRef 容器ref instance 实例
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [instance, setInstance] = useState<MindElixirInstance | null>(null);
|
||||
const { isAuthenticated, user, hasSomePermissions } = useAuth();
|
||||
const { read } = useVisitor()
|
||||
const { data: post, isLoading }: { data: PathDto; isLoading: boolean } =
|
||||
api.post.findFirst.useQuery({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
select: postDetailSelect,
|
||||
});
|
||||
}, { enabled: Boolean(id) });
|
||||
const canEdit: boolean = useMemo(() => {
|
||||
//登录了且是作者、超管、无id新建模式
|
||||
const isAuth = isAuthenticated && user?.id === post?.author?.id;
|
||||
|
@ -42,9 +46,19 @@ export default function MindEditor({ id }: { id?: string }) {
|
|||
});
|
||||
const { handleFileUpload } = useTusUpload();
|
||||
const [form] = Form.useForm();
|
||||
useEffect(() => {
|
||||
if (post?.id && id) {
|
||||
read.mutateAsync({
|
||||
data: {
|
||||
visitorId: user?.id || null,
|
||||
postId: post?.id,
|
||||
type: VisitType.READED,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [post]);
|
||||
useEffect(() => {
|
||||
if (post && form && instance && id) {
|
||||
console.log(post);
|
||||
instance.refresh((post as any).meta);
|
||||
const deptIds = (post?.depts || [])?.map((dept) => dept.id);
|
||||
const formData = {
|
||||
|
@ -52,8 +66,8 @@ export default function MindEditor({ id }: { id?: string }) {
|
|||
deptIds: deptIds,
|
||||
};
|
||||
post.terms?.forEach((term) => {
|
||||
formData[term.taxonomyId] = term.id; // 假设 taxonomyName 是您在 Form.Item 中使用的 name
|
||||
});
|
||||
formData[term.taxonomyId] = term.id // 假设 taxonomyName是您在 Form.Item 中使用的name
|
||||
})
|
||||
form.setFieldsValue(formData);
|
||||
}
|
||||
}, [post, form, instance, id]);
|
||||
|
@ -73,8 +87,9 @@ export default function MindEditor({ id }: { id?: string }) {
|
|||
nodeMenu: canEdit, // 禁用节点右键菜单
|
||||
keypress: canEdit, // 禁用键盘快捷键
|
||||
});
|
||||
mind.init(MindElixir.new("新学习路径"));
|
||||
mind.init(MindElixir.new("新思维导图"));
|
||||
containerRef.current.hidden = true;
|
||||
//挂载实例
|
||||
setInstance(mind);
|
||||
}, [canEdit]);
|
||||
useEffect(() => {
|
||||
|
@ -86,6 +101,7 @@ export default function MindEditor({ id }: { id?: string }) {
|
|||
}
|
||||
}
|
||||
}, [id, post, instance]);
|
||||
//保存 按钮 函数
|
||||
const handleSave = async () => {
|
||||
if (!instance) return;
|
||||
const values = form.getFieldsValue();
|
||||
|
@ -154,9 +170,13 @@ export default function MindEditor({ id }: { id?: string }) {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 flex-col w-[90vw] my-5 h-[80vh] border rounded-lg mx-auto">
|
||||
|
||||
<div className={`grid grid-cols-1 flex-col w-[90vw] my-5 h-[${Math.floor(window.innerHeight/1.25)}px] border rounded-lg mx-auto`}>
|
||||
|
||||
{canEdit && taxonomies && (
|
||||
<Form form={form} className=" bg-white p-4 ">
|
||||
<Form
|
||||
form={form}
|
||||
className=" bg-white p-4 ">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-4">
|
||||
{taxonomies.map((tax, index) => (
|
||||
|
@ -166,6 +186,7 @@ export default function MindEditor({ id }: { id?: string }) {
|
|||
// rules={[{ required: true }]}
|
||||
noStyle>
|
||||
<TermSelect
|
||||
disabled={!canEdit}
|
||||
className=" w-48"
|
||||
placeholder={`请选择${tax.name}`}
|
||||
taxonomyId={tax.id}
|
||||
|
@ -177,19 +198,21 @@ export default function MindEditor({ id }: { id?: string }) {
|
|||
name="deptIds"
|
||||
noStyle>
|
||||
<DepartmentSelect
|
||||
disabled={!canEdit}
|
||||
className="w-96"
|
||||
placeholder="请选择制作单位"
|
||||
multiple
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Button
|
||||
{canEdit && <Button
|
||||
ghost
|
||||
type="primary"
|
||||
icon={<SaveOutlined></SaveOutlined>}
|
||||
onSubmit={(e) => e.preventDefault()}
|
||||
onClick={handleSave}>
|
||||
{id ? "更新" : "保存"}
|
||||
</Button>
|
||||
</Button>}
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
|
@ -199,20 +222,24 @@ export default function MindEditor({ id }: { id?: string }) {
|
|||
onContextMenu={(e) => e.preventDefault()}
|
||||
/>
|
||||
{canEdit && instance && <NodeMenu mind={instance} />}
|
||||
{isLoading && (
|
||||
{
|
||||
isLoading && (
|
||||
<div
|
||||
className="py-64 justify-center flex"
|
||||
style={{ height: "calc(100vh - 287px)" }}>
|
||||
<Spin size="large"></Spin>
|
||||
</div>
|
||||
)}
|
||||
{!post && id && !isLoading && (
|
||||
)
|
||||
}
|
||||
{
|
||||
!post && id && !isLoading && (
|
||||
<div
|
||||
className="py-64"
|
||||
style={{ height: "calc(100vh - 287px)" }}>
|
||||
<Empty></Empty>
|
||||
</div>
|
||||
)}
|
||||
)
|
||||
}
|
||||
</div >
|
||||
);
|
||||
}
|
||||
|
|
|
@ -35,23 +35,18 @@ const NodeMenu: React.FC<NodeMenuProps> = ({ mind }) => {
|
|||
useEffect(() => {
|
||||
const handleSelectNode = (nodeObj: NodeObj) => {
|
||||
setIsOpen(true);
|
||||
|
||||
const style = nodeObj.style || {};
|
||||
setSelectedFontColor(style.color || '');
|
||||
setSelectedBgColor(style.background || '');
|
||||
|
||||
setSelectedSize(style.fontSize || '24');
|
||||
setIsBold(style.fontWeight === 'bold');
|
||||
setUrl(nodeObj.hyperLink || '');
|
||||
};
|
||||
|
||||
const handleUnselectNode = () => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
mind.bus.addListener('selectNode', handleSelectNode);
|
||||
mind.bus.addListener('unselectNode', handleUnselectNode);
|
||||
|
||||
}, [mind]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
interface I18n {
|
||||
addChild: string
|
||||
addParent: string
|
||||
addSibling: string
|
||||
removeNode: string
|
||||
focus: string
|
||||
cancelFocus: string
|
||||
moveUp: string
|
||||
moveDown: string
|
||||
link: string
|
||||
clickTips: string
|
||||
font: string
|
||||
background: string
|
||||
tag: string
|
||||
icon: string
|
||||
tagsSeparate: string
|
||||
iconsSeparate: string
|
||||
url: string
|
||||
memo?: string
|
||||
}
|
||||
|
||||
const cn: I18n = {
|
||||
addChild: '插入子节点',
|
||||
addParent: '插入父节点',
|
||||
addSibling: '插入同级节点',
|
||||
removeNode: '删除节点',
|
||||
focus: '专注',
|
||||
cancelFocus: '取消专注',
|
||||
moveUp: '上移',
|
||||
moveDown: '下移',
|
||||
link: '连接',
|
||||
clickTips: '请点击目标节点',
|
||||
font: '文字',
|
||||
background: '背景',
|
||||
tag: '标签',
|
||||
icon: '图标',
|
||||
tagsSeparate: '多个标签半角逗号分隔',
|
||||
iconsSeparate: '多个图标半角逗号分隔',
|
||||
url: 'URL',
|
||||
}
|
||||
|
||||
interface I18nCollection {
|
||||
cn: I18n
|
||||
zh_CN: I18n
|
||||
zh_TW: I18n
|
||||
en: I18n
|
||||
ru: I18n
|
||||
ja: I18n
|
||||
pt: I18n
|
||||
}
|
||||
|
||||
const i18n: I18nCollection = {
|
||||
cn,
|
||||
zh_CN: cn,
|
||||
zh_TW: {
|
||||
addChild: '插入子節點',
|
||||
addParent: '插入父節點',
|
||||
addSibling: '插入同級節點',
|
||||
removeNode: '刪除節點',
|
||||
focus: '專注',
|
||||
cancelFocus: '取消專注',
|
||||
moveUp: '上移',
|
||||
moveDown: '下移',
|
||||
link: '連接',
|
||||
clickTips: '請點擊目標節點',
|
||||
font: '文字',
|
||||
background: '背景',
|
||||
tag: '標簽',
|
||||
icon: '圖標',
|
||||
tagsSeparate: '多個標簽半角逗號分隔',
|
||||
iconsSeparate: '多個圖標半角逗號分隔',
|
||||
url: 'URL',
|
||||
},
|
||||
en: {
|
||||
addChild: 'Add child',
|
||||
addParent: 'Add parent',
|
||||
addSibling: 'Add sibling',
|
||||
removeNode: 'Remove node',
|
||||
focus: 'Focus Mode',
|
||||
cancelFocus: 'Cancel Focus Mode',
|
||||
moveUp: 'Move up',
|
||||
moveDown: 'Move down',
|
||||
link: 'Link',
|
||||
clickTips: 'Please click the target node',
|
||||
font: 'Font',
|
||||
background: 'Background',
|
||||
tag: 'Tag',
|
||||
icon: 'Icon',
|
||||
tagsSeparate: 'Separate tags by comma',
|
||||
iconsSeparate: 'Separate icons by comma',
|
||||
url: 'URL',
|
||||
},
|
||||
ru: {
|
||||
addChild: 'Добавить дочерний элемент',
|
||||
addParent: 'Добавить родительский элемент',
|
||||
addSibling: 'Добавить на этом уровне',
|
||||
removeNode: 'Удалить узел',
|
||||
focus: 'Режим фокусировки',
|
||||
cancelFocus: 'Отменить режим фокусировки',
|
||||
moveUp: 'Поднять выше',
|
||||
moveDown: 'Опустить ниже',
|
||||
link: 'Ссылка',
|
||||
clickTips: 'Пожалуйста, нажмите на целевой узел',
|
||||
font: 'Цвет шрифта',
|
||||
background: 'Цвет фона',
|
||||
tag: 'Тег',
|
||||
icon: 'Иконка',
|
||||
tagsSeparate: 'Разделяйте теги запятой',
|
||||
iconsSeparate: 'Разделяйте иконки запятой',
|
||||
url: 'URL',
|
||||
},
|
||||
ja: {
|
||||
addChild: '子ノードを追加する',
|
||||
addParent: '親ノードを追加します',
|
||||
addSibling: '兄弟ノードを追加する',
|
||||
removeNode: 'ノードを削除',
|
||||
focus: '集中',
|
||||
cancelFocus: '集中解除',
|
||||
moveUp: '上へ移動',
|
||||
moveDown: '下へ移動',
|
||||
link: 'コネクト',
|
||||
clickTips: 'ターゲットノードをクリックしてください',
|
||||
font: 'フォント',
|
||||
background: 'バックグラウンド',
|
||||
tag: 'タグ',
|
||||
icon: 'アイコン',
|
||||
tagsSeparate: '複数タグはカンマ区切り',
|
||||
iconsSeparate: '複数アイコンはカンマ区切り',
|
||||
url: 'URL',
|
||||
},
|
||||
pt: {
|
||||
addChild: 'Adicionar item filho',
|
||||
addParent: 'Adicionar item pai',
|
||||
addSibling: 'Adicionar item irmao',
|
||||
removeNode: 'Remover item',
|
||||
focus: 'Modo Foco',
|
||||
cancelFocus: 'Cancelar Modo Foco',
|
||||
moveUp: 'Mover para cima',
|
||||
moveDown: 'Mover para baixo',
|
||||
link: 'Link',
|
||||
clickTips: 'Favor clicar no item alvo',
|
||||
font: 'Fonte',
|
||||
background: 'Cor de fundo',
|
||||
tag: 'Tag',
|
||||
icon: 'Icone',
|
||||
tagsSeparate: 'Separe tags por virgula',
|
||||
iconsSeparate: 'Separe icones por virgula',
|
||||
url: 'URL',
|
||||
},
|
||||
}
|
||||
|
||||
export default i18n
|
|
@ -8,11 +8,7 @@ export default function CourseDetail({
|
|||
id?: string;
|
||||
lectureId?: string;
|
||||
}) {
|
||||
const iframeStyle = {
|
||||
width: "50%",
|
||||
height: "100vh",
|
||||
border: "none",
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<CourseDetailProvider editId={id}>
|
||||
|
|
|
@ -7,25 +7,10 @@ import { Course, LectureType, PostType } from "@nice/common";
|
|||
import { CourseDetailContext } from "./CourseDetailContext";
|
||||
import CollapsibleContent from "@web/src/components/common/container/CollapsibleContent";
|
||||
import { Skeleton } from "antd";
|
||||
import { CoursePreview } from "./CoursePreview/CoursePreview";
|
||||
import ResourcesShower from "@web/src/components/common/uploader/ResourceShower";
|
||||
import {
|
||||
BookOutlined,
|
||||
CalendarOutlined,
|
||||
EditTwoTone,
|
||||
EyeOutlined,
|
||||
ReloadOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import dayjs from "dayjs";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import CourseDetailTitle from "./CourseDetailTitle";
|
||||
|
||||
// interface CourseDetailDisplayAreaProps {
|
||||
// // course: Course;
|
||||
// // videoSrc?: string;
|
||||
// // videoPoster?: string;
|
||||
// // isLoading?: boolean;
|
||||
// }
|
||||
|
||||
export const CourseDetailDisplayArea: React.FC = () => {
|
||||
// 创建滚动动画效果
|
||||
|
|
|
@ -19,11 +19,7 @@ export default function CourseDetailLayout() {
|
|||
const [isSyllabusOpen, setIsSyllabusOpen] = useState(true);
|
||||
return (
|
||||
<div className="relative">
|
||||
{/* <CourseDetailHeader /> */}
|
||||
|
||||
{/* 添加 Header 组件 */}
|
||||
{/* 主内容区域 */}
|
||||
{/* 为了防止 Header 覆盖内容,添加上边距 */}
|
||||
<div className="pt-12 px-32">
|
||||
{" "}
|
||||
{/* 添加这个包装 div */}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
import { CheckOutlined } from '@ant-design/icons';
|
||||
import React from 'react';
|
||||
interface CourseObjectivesProps {
|
||||
objectives: string[];
|
||||
title?: string;
|
||||
}
|
||||
const CourseObjectives: React.FC<CourseObjectivesProps> = ({
|
||||
objectives,
|
||||
title = "您将会学到"
|
||||
}) => {
|
||||
return (
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||
<h2 className="text-xl font-bold mb-4">{title}</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{objectives.map((objective, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-start space-x-3"
|
||||
>
|
||||
<CheckOutlined></CheckOutlined>
|
||||
<span className="text-gray-700">{objective}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CourseObjectives;
|
|
@ -82,7 +82,7 @@ export function CourseFormProvider({
|
|||
}, [course, form]);
|
||||
|
||||
const onSubmit = async (values: any) => {
|
||||
console.log(values);
|
||||
|
||||
const sections = values?.sections || [];
|
||||
const deptIds = values?.deptIds || [];
|
||||
const termIds = taxonomies
|
||||
|
@ -153,6 +153,7 @@ export function CourseFormProvider({
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<CourseEditorContext.Provider
|
||||
value={{
|
||||
|
|
|
@ -9,7 +9,7 @@ import DepartmentSelect from "../../../department/department-select";
|
|||
const { TextArea } = Input;
|
||||
|
||||
export function CourseBasicForm() {
|
||||
// 将 CourseLevelLabel 转换为 Ant Design Select 需要的选项格式
|
||||
// 将 CourseLevelLabel 使用 Object.entries 将 CourseLevelLabel 对象转换为键值对数组。
|
||||
const levelOptions = Object.entries(CourseLevelLabel).map(
|
||||
([key, value]) => ({
|
||||
label: value,
|
||||
|
|
|
@ -70,7 +70,9 @@ export const routes: CustomRouteObject[] = [
|
|||
},
|
||||
{
|
||||
path: "editor/:id?",
|
||||
element: <PathEditorPage></PathEditorPage>,
|
||||
element: <WithAuth>
|
||||
<PathEditorPage></PathEditorPage>
|
||||
</WithAuth>,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -86,6 +88,14 @@ export const routes: CustomRouteObject[] = [
|
|||
</WithAuth>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "my-duty-path",
|
||||
element: (
|
||||
<WithAuth>
|
||||
<MyPathPage></MyPathPage>
|
||||
</WithAuth>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "my-duty",
|
||||
element: (
|
||||
|
|
Loading…
Reference in New Issue