This commit is contained in:
Rao 2025-02-25 10:06:03 +08:00
commit 4d995a24a9
22 changed files with 324 additions and 208 deletions

View File

@ -155,6 +155,7 @@ export class UserProfileService {
where: { id }, where: { id },
select: { select: {
id: true, id: true,
avatar:true,
deptId: true, deptId: true,
department: true, department: true,
domainId: true, domainId: true,

View File

@ -40,7 +40,11 @@ export class VisitService extends BaseService<Prisma.VisitDelegate> {
id: postId, id: postId,
visitType: args.data.type, // 直接复用传入的类型 visitType: args.data.type, // 直接复用传入的类型
}); });
EventBus.emit('updateTotalCourseViewCount', {
visitType: args.data.type, // 直接复用传入的类型
});
} }
return result; return result;
} }
async createMany(args: Prisma.VisitCreateManyArgs, staff?: UserProfile) { async createMany(args: Prisma.VisitCreateManyArgs, staff?: UserProfile) {
@ -138,6 +142,9 @@ export class VisitService extends BaseService<Prisma.VisitDelegate> {
id: args?.where?.postId as string, id: args?.where?.postId as string,
visitType: args.where.type as any, // 直接复用传入的类型 visitType: args.where.type as any, // 直接复用传入的类型
}); });
EventBus.emit('updateTotalCourseViewCount', {
visitType: args.where.type as any, // 直接复用传入的类型
});
} }
} }
return superDetele; return superDetele;

View File

@ -7,11 +7,18 @@ import {
VisitType, VisitType,
} from '@nice/common'; } from '@nice/common';
export async function updateTotalCourseViewCount(type: VisitType) { export async function updateTotalCourseViewCount(type: VisitType) {
const courses = await db.post.findMany({ const posts = await db.post.findMany({
where: { type: PostType.COURSE }, where: {
select: { id: true }, type: { in: [PostType.COURSE, PostType.LECTURE] },
deletedAt: null,
},
select: { id: true, type: true },
}); });
const courseIds = courses.map((course) => course.id);
const courseIds = posts
.filter((post) => post.type === PostType.COURSE)
.map((course) => course.id);
const lectures = posts.filter((post) => post.type === PostType.LECTURE);
const totalViews = await db.visit.aggregate({ const totalViews = await db.visit.aggregate({
_sum: { _sum: {
views: true, views: true,
@ -30,6 +37,10 @@ export async function updateTotalCourseViewCount(type: VisitType) {
meta: true, meta: true,
}, },
}); });
const staffs = await db.staff.count({
where: { deletedAt: null },
});
const baseSeting = appConfig.meta as BaseSetting; const baseSeting = appConfig.meta as BaseSetting;
await db.appConfig.update({ await db.appConfig.update({
where: { where: {
@ -38,7 +49,15 @@ export async function updateTotalCourseViewCount(type: VisitType) {
data: { data: {
meta: { meta: {
...baseSeting, ...baseSeting,
reads: totalViews, appConfig: {
...(baseSeting?.appConfig || {}),
statistics: {
reads: totalViews._sum.views || 0,
courses: courseIds?.length || 0,
staffs: staffs || 0,
lectures: lectures?.length || 0,
},
},
}, },
}, },
}); });

View File

@ -24,7 +24,8 @@ export default function BaseSettingPage() {
const [isFormChanged, setIsFormChanged] = useState(false); const [isFormChanged, setIsFormChanged] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { user, hasSomePermissions } = useAuth(); const { user, hasSomePermissions } = useAuth();
const { pageWidth } = useContext?.(MainLayoutContext); const context = useContext(MainLayoutContext);
const pageWidth = context?.pageWidth;
function handleFieldsChange() { function handleFieldsChange() {
setIsFormChanged(true); setIsFormChanged(true);
} }
@ -43,7 +44,6 @@ export default function BaseSettingPage() {
} }
async function onSubmit(values: BaseSetting) { async function onSubmit(values: BaseSetting) {
setLoading(true); setLoading(true);
try { try {
await update.mutateAsync({ await update.mutateAsync({
where: { where: {

View File

@ -84,9 +84,6 @@ const CategorySection = () => {
// description: term.description // description: term.description
// })) || []; // })) || [];
// },[data]) // },[data])
const handleMouseEnter = useCallback((index: number) => { const handleMouseEnter = useCallback((index: number) => {
setHoveredIndex(index); setHoveredIndex(index);
}, []); }, []);

View File

@ -1,6 +1,6 @@
import React, { useState, useMemo, useEffect } from 'react'; import React, { useState, useMemo, useEffect } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { Button, Card, Typography, Tag, Progress, Spin } from 'antd'; import { Button, Card, Typography, Tag, Progress, Spin, Empty } from 'antd';
import { import {
PlayCircleOutlined, PlayCircleOutlined,
UserOutlined, UserOutlined,
@ -17,7 +17,6 @@ interface GetTaxonomyProps {
categories: string[]; categories: string[];
isLoading: boolean; isLoading: boolean;
} }
function useGetTaxonomy({ type }): GetTaxonomyProps { function useGetTaxonomy({ type }): GetTaxonomyProps {
const { data, isLoading }: { data: TermDto[], isLoading: boolean } = api.term.findMany.useQuery({ const { data, isLoading }: { data: TermDto[], isLoading: boolean } = api.term.findMany.useQuery({
where: { where: {
@ -40,6 +39,7 @@ function useGetTaxonomy({ type }): GetTaxonomyProps {
}, [data]); }, [data]);
return { categories, isLoading } return { categories, isLoading }
} }
// 修改useGetName中的查询条件
function useFetchCoursesByCategory(category: string) { function useFetchCoursesByCategory(category: string) {
const isAll = category === '全部'; const isAll = category === '全部';
@ -60,6 +60,8 @@ function useFetchCoursesByCategory(category: string) {
return { data, isLoading}; return { data, isLoading};
} }
// 不同分类跳转
const { Title, Text } = Typography; const { Title, Text } = Typography;
interface Course { interface Course {
@ -82,6 +84,7 @@ interface CoursesSectionProps {
initialVisibleCoursesCount?: number; initialVisibleCoursesCount?: number;
} }
const CoursesSection: React.FC<CoursesSectionProps> = ({ const CoursesSection: React.FC<CoursesSectionProps> = ({
title, title,
description, description,
@ -126,6 +129,9 @@ const CoursesSection: React.FC<CoursesSectionProps> = ({
navigate(`/course?courseId=${course.id}/detail`); navigate(`/course?courseId=${course.id}/detail`);
} }
useEffect(()=>{
console.log('data:', data)
})
const filteredCourses = useMemo(() => { const filteredCourses = useMemo(() => {
return selectedCategory === '全部' return selectedCategory === '全部'
? data ? data
@ -162,7 +168,12 @@ const CoursesSection: React.FC<CoursesSectionProps> = ({
: 'bg-white text-gray-600 hover:bg-gray-100' : 'bg-white text-gray-600 hover:bg-gray-100'
}`} }`}
></Tag> ></Tag>
{ {gateGory.categories.length === 0 && (
<Empty
description="暂无课程分类"
image={Empty.PRESENTED_IMAGE_DEFAULT}
/>
)}:{
gateGory.categories.map((category) => ( gateGory.categories.map((category) => (
<Tag <Tag
key={category} key={category}
@ -181,6 +192,7 @@ const CoursesSection: React.FC<CoursesSectionProps> = ({
</Tag> </Tag>
)) ))
} }
</> </>
) )
} }

View File

@ -45,16 +45,16 @@ const carouselItems: CarouselItem[] = [
}, },
]; ];
const HeroSection = () => {
const carouselRef = useRef<CarouselRef>(null);
const { statistics, baseSetting } = useAppConfig();
const platformStats: PlatformStat[] = [ const platformStats: PlatformStat[] = [
{ icon: <TeamOutlined />, value: "50,000+", label: "注册学员" }, { icon: <TeamOutlined />, value: "50,000+", label: "注册学员" },
{ icon: <BookOutlined />, value: "1,000+", label: "精品课程" }, { icon: <BookOutlined />, value: "1,000+", label: "精品课程" },
// { icon: <StarOutlined />, value: '98%', label: '好评度' }, // { icon: <StarOutlined />, value: '98%', label: '好评度' },
{ icon: <EyeOutlined />, value: "100万+", label: "观看次数" }, { icon: <EyeOutlined />, value: "4552", label: "观看次数" },
]; ];
const HeroSection = () => {
const carouselRef = useRef<CarouselRef>(null);
const handlePrev = useCallback(() => { const handlePrev = useCallback(() => {
carouselRef.current?.prev(); carouselRef.current?.prev();
}, []); }, []);
@ -97,11 +97,10 @@ const HeroSection = () => {
{/* Content Container */} {/* Content Container */}
<div className="relative h-full max-w-7xl mx-auto px-6 lg:px-8"></div> <div className="relative h-full max-w-7xl mx-auto px-6 lg:px-8"></div>
</div> </div>
))) ))
:( ) : (
<div></div> <div></div>
) )}
}
</Carousel> </Carousel>
{/* Navigation Buttons */} {/* Navigation Buttons */}

View File

@ -127,12 +127,10 @@ const HomePage = () => {
}); });
useEffect(() => { useEffect(() => {
if (data) { if (data) {
console.log('mockCourses data:', data); console.log('Courses data:', data);
} }
}, [data]); }, [data]);
// 数据处理逻辑
// 修正依赖数组
return ( return (
<div className="min-h-screen"> <div className="min-h-screen">
<HeroSection /> <HeroSection />

View File

@ -8,7 +8,7 @@ export function MainFooter() {
{/* 开发组织信息 */} {/* 开发组织信息 */}
<div className="text-center md:text-left space-y-2"> <div className="text-center md:text-left space-y-2">
<h3 className="text-white font-semibold text-sm flex items-center justify-center md:justify-start"> <h3 className="text-white font-semibold text-sm flex items-center justify-center md:justify-start">
</h3> </h3>
<p className="text-gray-400 text-xs italic"> <p className="text-gray-400 text-xs italic">

View File

@ -84,7 +84,7 @@ export default function StaffForm() {
form.setFieldValue("officerId", data.officerId); form.setFieldValue("officerId", data.officerId);
form.setFieldValue("phoneNumber", data.phoneNumber); form.setFieldValue("phoneNumber", data.phoneNumber);
form.setFieldValue("enabled", data.enabled); form.setFieldValue("enabled", data.enabled);
form.setFieldValue("enabled", data.avatar); form.setFieldValue("avatar", data.avatar);
} }
}, [data]); }, [data]);
// useEffect(() => { // useEffect(() => {

View File

@ -1,5 +1,6 @@
import React, { ReactNode } from "react";
export interface MenuItemType { export interface MenuItemType {
icon: JSX.Element; icon: ReactNode;
label: string; label: string;
action: () => void; action: () => void;
} }

View File

@ -11,6 +11,7 @@ export interface TusUploaderProps {
value?: string[]; value?: string[];
onChange?: (value: string[]) => void; onChange?: (value: string[]) => void;
multiple?: boolean; multiple?: boolean;
allowTypes?: string[];
} }
interface UploadingFile { interface UploadingFile {
@ -25,8 +26,8 @@ export const TusUploader = ({
value = [], value = [],
onChange, onChange,
multiple = true, multiple = true,
allowTypes = undefined,
}: TusUploaderProps) => { }: TusUploaderProps) => {
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[]>(
@ -61,7 +62,10 @@ export const TusUploader = ({
const handleBeforeUpload = useCallback( const handleBeforeUpload = useCallback(
(file: File) => { (file: File) => {
if (allowTypes && !allowTypes.includes(file.type)) {
toast.error(`文件类型 ${file.type} 不在允许范围内`);
return Upload.LIST_IGNORE; // 使用 antd 的官方阻止方式
}
const fileKey = `${file.name}-${Date.now()}`; const fileKey = `${file.name}-${Date.now()}`;
setUploadingFiles((prev) => [ setUploadingFiles((prev) => [
@ -136,10 +140,10 @@ export const TusUploader = ({
return ( return (
<div className="space-y-1"> <div className="space-y-1">
<Upload.Dragger <Upload.Dragger
accept={allowTypes?.join(",")}
name="files" name="files"
multiple={multiple} multiple={multiple}
showUploadList={false} showUploadList={false}
beforeUpload={handleBeforeUpload}> beforeUpload={handleBeforeUpload}>
<p className="ant-upload-drag-icon"> <p className="ant-upload-drag-icon">
<UploadOutlined /> <UploadOutlined />
@ -149,6 +153,11 @@ export const TusUploader = ({
</p> </p>
<p className="ant-upload-hint"> <p className="ant-upload-hint">
{multiple ? "支持单个或批量上传文件" : "仅支持上传单个文件"} {multiple ? "支持单个或批量上传文件" : "仅支持上传单个文件"}
{allowTypes && (
<span className="block text-xs text-gray-500">
: {allowTypes.join(", ")}
</span>
)}
</p> </p>
<div className="px-2 py-0 rounded mt-1"> <div className="px-2 py-0 rounded mt-1">

View File

@ -47,7 +47,7 @@ export const CourseDetailDescription: React.FC = () => {
<div>{course?.subTitle}</div> <div>{course?.subTitle}</div>
<div className="flex gap-1"> <div className="flex gap-1">
<EyeOutlined></EyeOutlined> <EyeOutlined></EyeOutlined>
<div>{course?.meta?.views}</div> <div>{course?.meta?.views || 0}</div>
</div> </div>
<div className="flex gap-1"> <div className="flex gap-1">
<CalendarOutlined></CalendarOutlined> <CalendarOutlined></CalendarOutlined>

View File

@ -21,7 +21,7 @@ export function CourseDetailHeader() {
return ( return (
<Header className="select-none flex items-center justify-center bg-white shadow-md border-b border-gray-100 fixed w-full z-30"> <Header className="select-none flex items-center justify-center bg-white shadow-md border-b border-gray-100 fixed w-full z-30">
<div className="w-full max-w-screen-2xl px-4 md:px-6 mx-auto flex items-center justify-between h-full"> <div className="w-full flex items-center justify-between h-full">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<HomeOutlined <HomeOutlined
onClick={() => { onClick={() => {

View File

@ -2,6 +2,7 @@ import {
SkeletonItem, SkeletonItem,
SkeletonSection, SkeletonSection,
} from "@web/src/components/presentation/Skeleton"; } from "@web/src/components/presentation/Skeleton";
import { api } from "packages/client/dist";
export const CourseDetailSkeleton = () => { export const CourseDetailSkeleton = () => {
return ( return (

View File

@ -10,7 +10,13 @@ import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities"; import { CSS } from "@dnd-kit/utilities";
import QuillEditor from "@web/src/components/common/editor/quill/QuillEditor"; import QuillEditor from "@web/src/components/common/editor/quill/QuillEditor";
import { TusUploader } from "@web/src/components/common/uploader/TusUploader"; import { TusUploader } from "@web/src/components/common/uploader/TusUploader";
import { Lecture, LectureType, LessonTypeLabel, PostType } from "@nice/common"; import {
Lecture,
LectureType,
LessonTypeLabel,
PostType,
videoMimeTypes,
} from "@nice/common";
import { usePost } from "@nice/client"; import { usePost } from "@nice/client";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
@ -134,7 +140,9 @@ export const SortableLecture: React.FC<SortableLectureProps> = ({
name="title" name="title"
initialValue={field?.title} initialValue={field?.title}
className="mb-0 flex-1" className="mb-0 flex-1"
rules={[{ required: true }]}> rules={[
{ required: true, message: "请输入课时标题" },
]}>
<Input placeholder="课时标题" /> <Input placeholder="课时标题" />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@ -158,14 +166,24 @@ export const SortableLecture: React.FC<SortableLectureProps> = ({
<Form.Item <Form.Item
name={["meta", "videoIds"]} name={["meta", "videoIds"]}
className="mb-0 flex-1" className="mb-0 flex-1"
rules={[{ required: true }]}> rules={[
<TusUploader multiple={false} /> {
required: true,
message: "请传入视频",
},
]}>
<TusUploader
allowTypes={videoMimeTypes}
multiple={false}
/>
</Form.Item> </Form.Item>
) : ( ) : (
<Form.Item <Form.Item
name="content" name="content"
className="mb-0 flex-1" className="mb-0 flex-1"
rules={[{ required: true }]}> rules={[
{ required: true, message: "请输入内容" },
]}>
<QuillEditor /> <QuillEditor />
</Form.Item> </Form.Item>
)} )}

View File

@ -2,9 +2,10 @@ import { Button, Form, Input, Spin, Switch, message } from "antd";
import { useContext, useEffect } from "react"; import { useContext, useEffect } from "react";
import { useStaff } from "@nice/client"; import { useStaff } from "@nice/client";
import DepartmentSelect from "../department/department-select"; import DepartmentSelect from "../department/department-select";
import { api } from "@nice/client" import { api } from "@nice/client";
import { StaffEditorContext } from "./staff-editor"; import { StaffEditorContext } from "./staff-editor";
import { useAuth } from "@web/src/providers/auth-provider"; import { useAuth } from "@web/src/providers/auth-provider";
import AvatarUploader from "../../common/uploader/AvatarUploader";
export default function StaffForm() { export default function StaffForm() {
const { create, update } = useStaff(); // Ensure you have these methods in your hooks const { create, update } = useStaff(); // Ensure you have these methods in your hooks
const { const {
@ -21,6 +22,7 @@ export default function StaffForm() {
{ where: { id: editId } }, { where: { id: editId } },
{ enabled: !!editId } { enabled: !!editId }
); );
const { isRoot } = useAuth(); const { isRoot } = useAuth();
async function handleFinish(values: any) { async function handleFinish(values: any) {
const { const {
@ -31,8 +33,9 @@ export default function StaffForm() {
password, password,
phoneNumber, phoneNumber,
officerId, officerId,
enabled enabled,
} = values avatar,
} = values;
setFormLoading(true); setFormLoading(true);
try { try {
if (data && editId) { if (data && editId) {
@ -46,8 +49,9 @@ export default function StaffForm() {
password, password,
phoneNumber, phoneNumber,
officerId, officerId,
enabled enabled,
} avatar,
},
}); });
} else { } else {
await create.mutateAsync({ await create.mutateAsync({
@ -58,8 +62,9 @@ export default function StaffForm() {
domainId: fieldDomainId ? fieldDomainId : domainId, domainId: fieldDomainId ? fieldDomainId : domainId,
password, password,
officerId, officerId,
phoneNumber phoneNumber,
} avatar,
},
}); });
form.resetFields(); form.resetFields();
if (deptId) form.setFieldValue("deptId", deptId); if (deptId) form.setFieldValue("deptId", deptId);
@ -77,13 +82,14 @@ export default function StaffForm() {
useEffect(() => { useEffect(() => {
form.resetFields(); form.resetFields();
if (data && editId) { if (data && editId) {
form.setFieldValue("username", data.username); form.setFieldValue("username", data?.username);
form.setFieldValue("showname", data.showname); form.setFieldValue("showname", data?.showname);
form.setFieldValue("domainId", data.domainId); form.setFieldValue("domainId", data?.domainId);
form.setFieldValue("deptId", data.deptId); form.setFieldValue("deptId", data?.deptId);
form.setFieldValue("officerId", data.officerId); form.setFieldValue("officerId", data?.officerId);
form.setFieldValue("phoneNumber", data.phoneNumber); form.setFieldValue("phoneNumber", data?.phoneNumber);
form.setFieldValue("enabled", data.enabled) form.setFieldValue("enabled", data?.enabled);
form.setFieldValue("avatar", data?.avatar);
} }
}, [data]); }, [data]);
useEffect(() => { useEffect(() => {
@ -99,6 +105,7 @@ export default function StaffForm() {
<Spin /> <Spin />
</div> </div>
)} )}
<Form <Form
disabled={isLoading} disabled={isLoading}
form={form} form={form}
@ -106,6 +113,9 @@ export default function StaffForm() {
requiredMark="optional" requiredMark="optional"
autoComplete="off" autoComplete="off"
onFinish={handleFinish}> onFinish={handleFinish}>
<Form.Item name={"avatar"} label="头像">
<AvatarUploader></AvatarUploader>
</Form.Item>
{canManageAnyStaff && ( {canManageAnyStaff && (
<Form.Item <Form.Item
name={"domainId"} name={"domainId"}
@ -127,7 +137,8 @@ export default function StaffForm() {
rules={[{ required: true }]} rules={[{ required: true }]}
name={"username"} name={"username"}
label="帐号"> label="帐号">
<Input allowClear <Input
allowClear
autoComplete="new-username" // 使用非标准的自动完成值 autoComplete="new-username" // 使用非标准的自动完成值
spellCheck={false} spellCheck={false}
/> />
@ -136,7 +147,8 @@ export default function StaffForm() {
rules={[{ required: true }]} rules={[{ required: true }]}
name={"showname"} name={"showname"}
label="姓名"> label="姓名">
<Input allowClear <Input
allowClear
autoComplete="new-name" // 使用非标准的自动完成值 autoComplete="new-name" // 使用非标准的自动完成值
spellCheck={false} spellCheck={false}
/> />
@ -146,8 +158,8 @@ export default function StaffForm() {
{ {
required: false, required: false,
pattern: /^\d{5,18}$/, pattern: /^\d{5,18}$/,
message: "请输入正确的证件号(数字)" message: "请输入正确的证件号(数字)",
} },
]} ]}
name={"officerId"} name={"officerId"}
label="证件号"> label="证件号">
@ -158,20 +170,29 @@ export default function StaffForm() {
{ {
required: false, required: false,
pattern: /^\d{6,11}$/, pattern: /^\d{6,11}$/,
message: "请输入正确的手机号(数字)" message: "请输入正确的手机号(数字)",
} },
]} ]}
name={"phoneNumber"} name={"phoneNumber"}
label="手机号"> label="手机号">
<Input autoComplete="new-phone" // 使用非标准的自动完成值 <Input
spellCheck={false} allowClear /> autoComplete="new-phone" // 使用非标准的自动完成值
spellCheck={false}
allowClear
/>
</Form.Item> </Form.Item>
<Form.Item label="密码" name={"password"}> <Form.Item label="密码" name={"password"}>
<Input.Password spellCheck={false} visibilityToggle autoComplete="new-password" /> <Input.Password
spellCheck={false}
visibilityToggle
autoComplete="new-password"
/>
</Form.Item> </Form.Item>
{editId && <Form.Item label="是否启用" name={"enabled"}> {editId && (
<Form.Item label="是否启用" name={"enabled"}>
<Switch></Switch> <Switch></Switch>
</Form.Item>} </Form.Item>
)}
</Form> </Form>
</div> </div>
); );

View File

@ -64,7 +64,7 @@ services:
# extra_hosts: # extra_hosts:
# - "host.docker.internal:host-gateway" # - "host.docker.internal:host-gateway"
nginx: nginx:
image: nice-nginx:latest image: nice-nginx:2.0
ports: ports:
- "80:80" - "80:80"
volumes: volumes:
@ -79,6 +79,7 @@ services:
entrypoint: ["/docker-entrypoint.sh"] entrypoint: ["/docker-entrypoint.sh"]
extra_hosts: extra_hosts:
- "host.docker.internal:host-gateway" - "host.docker.internal:host-gateway"
redis: redis:
image: redis:latest image: redis:latest
ports: ports:

View File

@ -10,6 +10,7 @@ export function useAppConfig() {
api.app_config.findFirst.useQuery({ api.app_config.findFirst.useQuery({
where: { slug: AppConfigSlug.BASE_SETTING }, where: { slug: AppConfigSlug.BASE_SETTING },
}); });
const handleMutationSuccess = useCallback(() => { const handleMutationSuccess = useCallback(() => {
utils.app_config.invalidate(); utils.app_config.invalidate();
}, [utils]); }, [utils]);
@ -26,7 +27,8 @@ export function useAppConfig() {
}); });
useEffect(() => { useEffect(() => {
if (data?.meta) { if (data?.meta) {
setBaseSetting(JSON.parse(data?.meta)); // console.log(JSON.parse(data?.meta));
setBaseSetting(data?.meta);
} }
}, [data, isLoading]); }, [data, isLoading]);
const splashScreen = useMemo(() => { const splashScreen = useMemo(() => {
@ -38,6 +40,16 @@ export function useAppConfig() {
const slides = useMemo(() => { const slides = useMemo(() => {
return baseSetting?.appConfig?.slides || []; return baseSetting?.appConfig?.slides || [];
}, [baseSetting]); }, [baseSetting]);
const statistics = useMemo(() => {
return (
baseSetting?.appConfig?.statistics || {
reads: 0,
staffs: 0,
courses: 0,
lectures: 0,
}
);
}, [baseSetting]);
return { return {
create, create,
deleteMany, deleteMany,
@ -47,5 +59,6 @@ export function useAppConfig() {
devDept, devDept,
isLoading, isLoading,
slides, slides,
statistics,
}; };
} }

View File

@ -81,3 +81,17 @@ export const InitAppConfigs: Prisma.AppConfigCreateInput[] = [
description: "", description: "",
}, },
]; ];
export const videoMimeTypes = [
"video/*", // 通配符 (部分浏览器可能不支持)
"video/mp4", // .mp4
"video/quicktime", // .mov
"video/x-msvideo", // .avi
"video/x-matroska", // .mkv
"video/webm", // .webm
"video/ogg", // .ogv
"video/mpeg", // .mpeg
"video/3gpp", // .3gp
"video/3gpp2", // .3g2
"video/x-flv", // .flv
"video/x-ms-wmv", // .wmv
];

View File

@ -44,7 +44,12 @@ export interface BaseSetting {
splashScreen?: string; splashScreen?: string;
devDept?: string; devDept?: string;
slides?: []; slides?: [];
statistics?: {
reads?: number; reads?: number;
courses?: number;
lectures?: number;
staffs?: number;
};
}; };
} }
export type RowModelResult = { export type RowModelResult = {