add
This commit is contained in:
parent
6805aba768
commit
c1c57612e1
|
@ -33,6 +33,7 @@ export default function CourseCard({ course }: CourseCardProps) {
|
||||||
backgroundImage: `url(${course?.meta?.thumbnail})`,
|
backgroundImage: `url(${course?.meta?.thumbnail})`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-80 group-hover:opacity-60 transition-opacity duration-300" />
|
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-80 group-hover:opacity-60 transition-opacity duration-300" />
|
||||||
<PlayCircleOutlined className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-6xl text-white/90 opacity-0 group-hover:opacity-100 transition-all duration-300" />
|
<PlayCircleOutlined className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-6xl text-white/90 opacity-0 group-hover:opacity-100 transition-all duration-300" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,10 +47,10 @@ export default function CourseCard({ course }: CourseCardProps) {
|
||||||
// color={term.taxonomy.slug===TaxonomySlug.CATEGORY? "blue" : "green"}
|
// color={term.taxonomy.slug===TaxonomySlug.CATEGORY? "blue" : "green"}
|
||||||
color={
|
color={
|
||||||
term?.taxonomy?.slug ===
|
term?.taxonomy?.slug ===
|
||||||
TaxonomySlug.CATEGORY
|
TaxonomySlug.CATEGORY
|
||||||
? "blue"
|
? "blue"
|
||||||
: term?.taxonomy?.slug ===
|
: term?.taxonomy?.slug ===
|
||||||
TaxonomySlug.LEVEL
|
TaxonomySlug.LEVEL
|
||||||
? "green"
|
? "green"
|
||||||
: "orange"
|
: "orange"
|
||||||
}
|
}
|
||||||
|
@ -77,6 +78,7 @@ export default function CourseCard({ course }: CourseCardProps) {
|
||||||
{course.terms?.[1].name}
|
{course.terms?.[1].name}
|
||||||
</Tag> */}
|
</Tag> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Title
|
<Title
|
||||||
level={4}
|
level={4}
|
||||||
className="mb-4 line-clamp-2 font-bold leading-snug text-gray-800 hover:text-blue-600 transition-colors duration-300 group-hover:scale-[1.02] transform origin-left">
|
className="mb-4 line-clamp-2 font-bold leading-snug text-gray-800 hover:text-blue-600 transition-colors duration-300 group-hover:scale-[1.02] transform origin-left">
|
||||||
|
@ -87,15 +89,17 @@ export default function CourseCard({ course }: CourseCardProps) {
|
||||||
<TeamOutlined className="text-blue-500 text-lg transform group-hover:scale-110 transition-transform duration-300" />
|
<TeamOutlined className="text-blue-500 text-lg transform group-hover:scale-110 transition-transform duration-300" />
|
||||||
<div className="ml-2 flex items-center flex-grow">
|
<div className="ml-2 flex items-center flex-grow">
|
||||||
<Text className="font-medium text-blue-500 hover:text-blue-600 transition-colors duration-300 truncate max-w-[120px]">
|
<Text className="font-medium text-blue-500 hover:text-blue-600 transition-colors duration-300 truncate max-w-[120px]">
|
||||||
{
|
{course?.depts?.length > 1
|
||||||
course?.depts.length > 1 ?`${course.depts[0].name}等`:course.depts[0].name
|
? `${course.depts[0].name}等`
|
||||||
}
|
: course?.depts?.[0]?.name}
|
||||||
{/* {course?.depts?.map((dept) => {return dept.name.length > 1 ?`${dept.name.slice}等`: dept.name})} */}
|
{/* {course?.depts?.map((dept) => {return dept.name.length > 1 ?`${dept.name.slice}等`: dept.name})} */}
|
||||||
{/* {course?.depts?.map((dept)=>{return dept.name})} */}
|
{/* {course?.depts?.map((dept)=>{return dept.name})} */}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs font-medium text-gray-500">
|
<span className="text-xs font-medium text-gray-500">
|
||||||
{course?.meta?.views ? `观看次数 ${course?.meta?.views}` : null}
|
{course?.meta?.views
|
||||||
|
? `观看次数 ${course?.meta?.views}`
|
||||||
|
: null}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ const CoursesSection: React.FC<CoursesSectionProps> = ({
|
||||||
className="font-bold text-5xl mb-6 bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
className="font-bold text-5xl mb-6 bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
||||||
{title}
|
{title}
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
<Text
|
<Text
|
||||||
type="secondary"
|
type="secondary"
|
||||||
className="text-xl font-light text-gray-600">
|
className="text-xl font-light text-gray-600">
|
||||||
|
|
|
@ -83,6 +83,7 @@ export function CourseFormProvider({
|
||||||
const onSubmit = async (values: any) => {
|
const onSubmit = async (values: any) => {
|
||||||
console.log(values);
|
console.log(values);
|
||||||
const sections = values?.sections || [];
|
const sections = values?.sections || [];
|
||||||
|
const deptIds = values?.deptIds || [];
|
||||||
const termIds = taxonomies
|
const termIds = taxonomies
|
||||||
.map((tax) => values[tax.id]) // 获取每个 taxonomy 对应的选中值
|
.map((tax) => values[tax.id]) // 获取每个 taxonomy 对应的选中值
|
||||||
.filter((id) => id); // 过滤掉空值
|
.filter((id) => id); // 过滤掉空值
|
||||||
|
@ -95,12 +96,16 @@ export function CourseFormProvider({
|
||||||
terms: {
|
terms: {
|
||||||
connect: termIds.map((id) => ({ id })), // 转换成 connect 格式
|
connect: termIds.map((id) => ({ id })), // 转换成 connect 格式
|
||||||
},
|
},
|
||||||
|
depts: {
|
||||||
|
connect: deptIds.map((id) => ({ id })),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
// 删除原始的 taxonomy 字段
|
// 删除原始的 taxonomy 字段
|
||||||
taxonomies.forEach((tax) => {
|
taxonomies.forEach((tax) => {
|
||||||
delete formattedValues[tax.id];
|
delete formattedValues[tax.id];
|
||||||
});
|
});
|
||||||
delete formattedValues.sections;
|
delete formattedValues.sections;
|
||||||
|
delete formattedValues.deptIds;
|
||||||
if (course) {
|
if (course) {
|
||||||
formattedValues.meta = {
|
formattedValues.meta = {
|
||||||
...(course?.meta as CourseMeta),
|
...(course?.meta as CourseMeta),
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { convertToOptions } from "@nice/client";
|
||||||
import TermSelect from "../../../term/term-select";
|
import TermSelect from "../../../term/term-select";
|
||||||
import { useCourseEditor } from "../context/CourseEditorContext";
|
import { useCourseEditor } from "../context/CourseEditorContext";
|
||||||
import AvatarUploader from "@web/src/components/common/uploader/AvatarUploader";
|
import AvatarUploader from "@web/src/components/common/uploader/AvatarUploader";
|
||||||
|
import DepartmentSelect from "../../../department/department-select";
|
||||||
|
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
|
|
||||||
|
@ -48,6 +49,9 @@ export function CourseBasicForm() {
|
||||||
autoSize={{ minRows: 3, maxRows: 6 }}
|
autoSize={{ minRows: 3, maxRows: 6 }}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item name="deptIds" label="参与单位">
|
||||||
|
<DepartmentSelect multiple />
|
||||||
|
</Form.Item>
|
||||||
{taxonomies &&
|
{taxonomies &&
|
||||||
taxonomies.map((tax, index) => (
|
taxonomies.map((tax, index) => (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
|
|
@ -54,6 +54,9 @@ export default function CourseList({
|
||||||
setCurrentPage(page);
|
setCurrentPage(page);
|
||||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
}
|
}
|
||||||
|
if (isLoading) {
|
||||||
|
return <Skeleton paragraph={{ rows: 10 }}></Skeleton>;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{courses.length > 0 ? (
|
{courses.length > 0 ? (
|
||||||
|
|
|
@ -48,7 +48,7 @@ export default function TermSelect({
|
||||||
const [listTreeData, setListTreeData] = useState<
|
const [listTreeData, setListTreeData] = useState<
|
||||||
Omit<DefaultOptionType, "label">[]
|
Omit<DefaultOptionType, "label">[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
const fetchParentTerms = useCallback(
|
const fetchParentTerms = useCallback(
|
||||||
async (termIds: string | string[], taxonomyId?: string) => {
|
async (termIds: string | string[], taxonomyId?: string) => {
|
||||||
const idsArray = Array.isArray(termIds)
|
const idsArray = Array.isArray(termIds)
|
||||||
|
|
|
@ -1,199 +0,0 @@
|
||||||
import React, { useEffect, useState, useCallback } from "react";
|
|
||||||
import { Tree } from "antd";
|
|
||||||
import type { DataNode, TreeProps } from "antd/es/tree";
|
|
||||||
import { getUniqueItems } from "@nice/common";
|
|
||||||
import { api } from "@nice/client";
|
|
||||||
|
|
||||||
interface TermData {
|
|
||||||
value?: string;
|
|
||||||
children?: TermData[];
|
|
||||||
key?: string;
|
|
||||||
hasChildren?: boolean;
|
|
||||||
isLeaf?: boolean;
|
|
||||||
pId?: string;
|
|
||||||
title?: React.ReactNode;
|
|
||||||
data?: any;
|
|
||||||
order?: string;
|
|
||||||
id?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TermTreeProps {
|
|
||||||
defaultValue?: string | string[];
|
|
||||||
value?: string | string[];
|
|
||||||
onChange?: (value: string | string[]) => void;
|
|
||||||
multiple?: boolean;
|
|
||||||
taxonomyId?: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
className?: string;
|
|
||||||
domainId?: string;
|
|
||||||
style?: React.CSSProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TermTree: React.FC<TermTreeProps> = ({
|
|
||||||
defaultValue,
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
className,
|
|
||||||
multiple = false,
|
|
||||||
taxonomyId,
|
|
||||||
domainId,
|
|
||||||
disabled = false,
|
|
||||||
style,
|
|
||||||
}) => {
|
|
||||||
const utils = api.useUtils();
|
|
||||||
const [treeData, setTreeData] = useState<TermData[]>([]);
|
|
||||||
|
|
||||||
const processTermData = (terms: TermData[]): TermData[] => {
|
|
||||||
return terms.map((term) => ({
|
|
||||||
...term,
|
|
||||||
key: term.key || term.id || "",
|
|
||||||
title: term.title || term.value,
|
|
||||||
children: term.children
|
|
||||||
? processTermData(term.children)
|
|
||||||
: undefined,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchParentTerms = useCallback(
|
|
||||||
async (termIds: string | string[], taxonomyId?: string) => {
|
|
||||||
const idsArray = Array.isArray(termIds)
|
|
||||||
? termIds
|
|
||||||
: [termIds].filter(Boolean);
|
|
||||||
try {
|
|
||||||
const result = await utils.term.getParentSimpleTree.fetch({
|
|
||||||
termIds: idsArray,
|
|
||||||
taxonomyId,
|
|
||||||
domainId,
|
|
||||||
});
|
|
||||||
return processTermData(result);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
"Error fetching parent terms for termIds",
|
|
||||||
idsArray,
|
|
||||||
":",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[utils, domainId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const fetchTerms = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const rootTerms = await utils.term.getChildSimpleTree.fetch({
|
|
||||||
taxonomyId,
|
|
||||||
domainId,
|
|
||||||
});
|
|
||||||
let combinedTerms = processTermData(rootTerms);
|
|
||||||
if (defaultValue) {
|
|
||||||
const defaultTerms = await fetchParentTerms(
|
|
||||||
defaultValue,
|
|
||||||
taxonomyId
|
|
||||||
);
|
|
||||||
combinedTerms = getUniqueItems(
|
|
||||||
[...treeData, ...combinedTerms, ...defaultTerms],
|
|
||||||
"key"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (value) {
|
|
||||||
const valueTerms = await fetchParentTerms(value, taxonomyId);
|
|
||||||
combinedTerms = getUniqueItems(
|
|
||||||
[...treeData, ...combinedTerms, ...valueTerms],
|
|
||||||
"key"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
setTreeData(combinedTerms);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching terms:", error);
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
defaultValue,
|
|
||||||
value,
|
|
||||||
taxonomyId,
|
|
||||||
utils,
|
|
||||||
fetchParentTerms,
|
|
||||||
domainId,
|
|
||||||
treeData,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchTerms();
|
|
||||||
}, [fetchTerms]);
|
|
||||||
|
|
||||||
const onLoadData = async ({ key }: any) => {
|
|
||||||
try {
|
|
||||||
const result = await utils.term.getChildSimpleTree.fetch({
|
|
||||||
termIds: [key],
|
|
||||||
taxonomyId,
|
|
||||||
domainId,
|
|
||||||
});
|
|
||||||
const processedResult = processTermData(result);
|
|
||||||
const newItems = getUniqueItems(
|
|
||||||
[...treeData, ...processedResult],
|
|
||||||
"key"
|
|
||||||
);
|
|
||||||
setTreeData(newItems);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
"Error loading data for node with key",
|
|
||||||
key,
|
|
||||||
":",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCheck: TreeProps["onCheck"] = (checkedKeys, info) => {
|
|
||||||
if (onChange) {
|
|
||||||
if (multiple) {
|
|
||||||
onChange(checkedKeys as string[]);
|
|
||||||
} else {
|
|
||||||
onChange((checkedKeys as string[])[0] || "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleExpand = async (expandedKeys: React.Key[]) => {
|
|
||||||
try {
|
|
||||||
const allKeyIds = expandedKeys
|
|
||||||
.map((key) => key.toString())
|
|
||||||
.filter(Boolean);
|
|
||||||
const expandedNodes = await utils.term.getChildSimpleTree.fetch({
|
|
||||||
termIds: allKeyIds,
|
|
||||||
taxonomyId,
|
|
||||||
domainId,
|
|
||||||
});
|
|
||||||
const processedNodes = processTermData(expandedNodes);
|
|
||||||
const newItems = getUniqueItems(
|
|
||||||
[...treeData, ...processedNodes],
|
|
||||||
"key"
|
|
||||||
);
|
|
||||||
setTreeData(newItems);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
"Error expanding nodes with keys",
|
|
||||||
expandedKeys,
|
|
||||||
":",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tree
|
|
||||||
checkable={multiple}
|
|
||||||
disabled={disabled}
|
|
||||||
className={className}
|
|
||||||
style={style}
|
|
||||||
treeData={treeData as DataNode[]}
|
|
||||||
checkedKeys={Array.isArray(value) ? value : value ? [value] : []}
|
|
||||||
onCheck={handleCheck}
|
|
||||||
loadData={onLoadData}
|
|
||||||
onExpand={handleExpand}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TermTree;
|
|
Loading…
Reference in New Issue