diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index 1a53fe8b..6a44437f 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -38,6 +38,12 @@ export default [ key: 'workspace', component: './Workspace/index', }, + { + name: '消息中心', + path: 'message', + key: 'message', + component: './Message/index', + }, ], }, { @@ -582,6 +588,11 @@ export default [ }, ], }, + { + name: '审核', + path: 'approval', + component: './System/Approval', + }, ], }, { diff --git a/react-ui/src/assets/img/message/at-hover.png b/react-ui/src/assets/img/message/at-hover.png new file mode 100644 index 00000000..57aa81db Binary files /dev/null and b/react-ui/src/assets/img/message/at-hover.png differ diff --git a/react-ui/src/assets/img/message/at.png b/react-ui/src/assets/img/message/at.png new file mode 100644 index 00000000..e1cd2875 Binary files /dev/null and b/react-ui/src/assets/img/message/at.png differ diff --git a/react-ui/src/assets/img/message/content-bg.png b/react-ui/src/assets/img/message/content-bg.png new file mode 100644 index 00000000..dc31d537 Binary files /dev/null and b/react-ui/src/assets/img/message/content-bg.png differ diff --git a/react-ui/src/assets/img/message/menu-bg.png b/react-ui/src/assets/img/message/menu-bg.png new file mode 100644 index 00000000..c762354b Binary files /dev/null and b/react-ui/src/assets/img/message/menu-bg.png differ diff --git a/react-ui/src/assets/img/message/message-bg.png b/react-ui/src/assets/img/message/message-bg.png new file mode 100644 index 00000000..6a54cd26 Binary files /dev/null and b/react-ui/src/assets/img/message/message-bg.png differ diff --git a/react-ui/src/assets/img/message/red-point.png b/react-ui/src/assets/img/message/red-point.png new file mode 100644 index 00000000..bfd49b2e Binary files /dev/null and b/react-ui/src/assets/img/message/red-point.png differ diff --git a/react-ui/src/assets/img/message/system-hover.png b/react-ui/src/assets/img/message/system-hover.png new file mode 100644 index 00000000..31efbf60 Binary files /dev/null and b/react-ui/src/assets/img/message/system-hover.png differ diff --git a/react-ui/src/assets/img/message/system.png b/react-ui/src/assets/img/message/system.png new file mode 100644 index 00000000..4e23e3ec Binary files /dev/null and b/react-ui/src/assets/img/message/system.png differ diff --git a/react-ui/src/assets/img/message/trumpet-hover.png b/react-ui/src/assets/img/message/trumpet-hover.png new file mode 100644 index 00000000..82439bd6 Binary files /dev/null and b/react-ui/src/assets/img/message/trumpet-hover.png differ diff --git a/react-ui/src/assets/img/message/trumpet.png b/react-ui/src/assets/img/message/trumpet.png new file mode 100644 index 00000000..720aeb04 Binary files /dev/null and b/react-ui/src/assets/img/message/trumpet.png differ diff --git a/react-ui/src/components/KFButton/index.less b/react-ui/src/components/KFButton/index.less new file mode 100644 index 00000000..e69de29b diff --git a/react-ui/src/components/KFButton/index.tsx b/react-ui/src/components/KFButton/index.tsx new file mode 100644 index 00000000..e69de29b diff --git a/react-ui/src/components/MessageBroadcast/index.less b/react-ui/src/components/MessageBroadcast/index.less new file mode 100644 index 00000000..d3fda321 --- /dev/null +++ b/react-ui/src/components/MessageBroadcast/index.less @@ -0,0 +1,19 @@ +.message-broadcast { + position: relative; + width: 32px; + height: 32px; + margin-right: 8px; + cursor: pointer; + .backgroundFullImage(url(@/assets/img/message/trumpet.png)); + + &:hover { + background-image: url(@/assets/img/message/trumpet-hover.png); + } + + &__red-point { + position: absolute; + top: 8px; + left: 18px; + width: 6px; + } +} diff --git a/react-ui/src/components/MessageBroadcast/index.tsx b/react-ui/src/components/MessageBroadcast/index.tsx new file mode 100644 index 00000000..72b6b884 --- /dev/null +++ b/react-ui/src/components/MessageBroadcast/index.tsx @@ -0,0 +1,46 @@ +import RedPointImg from '@/assets/img/message/red-point.png'; +import { getMessageCountReq } from '@/services/message'; +import { to } from '@/utils/promise'; +import { useModel, useNavigate } from '@umijs/max'; +import { useCallback, useEffect, useState } from 'react'; +import styles from './index.less'; + +function MessageBroadcast() { + const { initialState } = useModel('@@initialState'); + const { currentUser } = initialState || {}; + const { userId } = currentUser || {}; + const [total, setTotal] = useState(0); + const navigate = useNavigate(); + + const getMessageCount = useCallback(async () => { + const params: Record = { + receiver: userId, + type: -1, + }; + const [res] = await to(getMessageCountReq(params)); + if (res && res.data) { + const { unread_total } = res.data; + setTotal(unread_total); + } + }, [userId]); + + useEffect(() => { + const interval = setInterval(() => { + getMessageCount(); + }, 60 * 1000); + getMessageCount(); + return () => { + clearInterval(interval); + }; + }, [getMessageCount]); + + return ( +
navigate('/workspace/message')}> + {total > 0 && ( + + )} +
+ ); +} + +export default MessageBroadcast; diff --git a/react-ui/src/components/RightContent/index.tsx b/react-ui/src/components/RightContent/index.tsx index 9b84950a..fcdf9f10 100644 --- a/react-ui/src/components/RightContent/index.tsx +++ b/react-ui/src/components/RightContent/index.tsx @@ -1,8 +1,9 @@ import KFIcon from '@/components/KFIcon'; import { ProBreadcrumb } from '@ant-design/pro-components'; import { useModel } from '@umijs/max'; -import { Button } from 'antd'; +import { Button, Flex } from 'antd'; import React from 'react'; +import MessageBroadcast from '../MessageBroadcast'; import Avatar from './AvatarDropdown'; import styles from './index.less'; // import { SelectLang } from '@umijs/max'; @@ -43,7 +44,15 @@ const GlobalHeaderRight: React.FC = () => { - + + + + + + {/* */} ); diff --git a/react-ui/src/enums/index.ts b/react-ui/src/enums/index.ts index 848016f0..5f522c50 100644 --- a/react-ui/src/enums/index.ts +++ b/react-ui/src/enums/index.ts @@ -171,3 +171,16 @@ export enum ComponentType { Map = 'map', Str = 'str', } + +// 消息类型 +export enum MessageType { + System = 1, + Mine = 2, +} + +// 消息状态 +export enum MessageStatus { + All = -1, + UnRead = 1, + Readed = 2, +} diff --git a/react-ui/src/hooks/useServerTime.ts b/react-ui/src/hooks/useServerTime.ts index a5c6f229..fcf8469d 100644 --- a/react-ui/src/hooks/useServerTime.ts +++ b/react-ui/src/hooks/useServerTime.ts @@ -10,6 +10,7 @@ import { useCallback, useEffect, useState } from 'react'; let globalTimeOffset: number | undefined = undefined; +// 获取服务器时间偏移 export const globalGetSeverTime = async () => { const requestStartTime = Date.now(); const [res] = await to(getSeverTimeReq()); @@ -23,7 +24,8 @@ export const globalGetSeverTime = async () => { } }; -export const now = () => { +// 服务器的当前时间 +export const serverNow = () => { return new Date(Date.now() + (globalTimeOffset ?? 0)); }; diff --git a/react-ui/src/pages/Home/components/Intro/index.tsx b/react-ui/src/pages/Home/components/Intro/index.tsx index 527aa2e8..7ea944c1 100644 --- a/react-ui/src/pages/Home/components/Intro/index.tsx +++ b/react-ui/src/pages/Home/components/Intro/index.tsx @@ -1,7 +1,7 @@ import miniHeaderImage from '@/assets/img/home/header-bg-mini.png'; import headerImage from '@/assets/img/home/header-bg.png'; import { convertRemToPx } from '@/utils'; -import { useNavigate } from '@umijs/max'; +import { gotoPageIfLogin } from '@/utils/ui'; import { motion, useMotionTemplate, @@ -18,7 +18,6 @@ import styles from './index.less'; function IntroBlock() { const [backgroundImage1, setBackgroundImage1] = useState(undefined); const [backgroundImage2, setBackgroundImage2] = useState(headerImage); - const navigate = useNavigate(); const { scrollY } = useScroll(); const springValue = useSpring(scrollY, { stiffness: 100, @@ -70,7 +69,7 @@ function IntroBlock() { 智能材料科研平台是用于材料研究和开发的技术平台,它旨在提供实验数据收集、分析和可视化等功能, 以支持材料工程师、科学家和研究人员在材料设计、性能评估和工艺优化方面的工作。 -
navigate('/workspace')}> +
gotoPageIfLogin('/workspace')}> 开始使用
diff --git a/react-ui/src/pages/Home/components/Model/index.tsx b/react-ui/src/pages/Home/components/Model/index.tsx index c3b0e621..abea0841 100644 --- a/react-ui/src/pages/Home/components/Model/index.tsx +++ b/react-ui/src/pages/Home/components/Model/index.tsx @@ -9,14 +9,14 @@ import BlockTitle from '../BlockTitle'; import styles from './index.less'; const modelVariants: Variants = { - offscreen: (index: number) => ({ + offscreen: { y: 0, opacity: 1, transition: { ease: 'linear', - duration: 0.1, + duration: 0.3, }, - }), + }, onscreen: { y: [0, 200, 0], opacity: [0, 0, 1], @@ -56,7 +56,7 @@ function ModelBlock() { return ( 陈志航 已加入项目 复杂智能软件系统研究/模型训练项目UI设计", +// "notification_url": "https://www.gitlink.org.cn/ci4s/UIdesign", +// "source": "ProjectMemberJoined", +// "created_at": "2025-08-06 08:54:54", +// "time_ago": "23天前", +// "type": "notification" +// } + +// receiver:消息接收者ID (必传) +// status:值未-1时,获取全部信息;值为1时,只获取未读消息;值为2时,获取已读消息 +// type:值为-1时,获取全部信息;值为1时,获取系统消息;值为2时,获取@我消息 +// sources:消息来源 +// page:页码:值为-1时(默认值),不开启分页; +// size:页大小页码,默认20 + +// 统计数量的接口 +// receiver:消息接收者ID (必传) +// type:值为-1时,获取全部信息;值为1时,获取系统消息;值为2时,获取@我消息 + +// 消息列表接口返回类型 +export interface MessageResponse { + receiver: number; + type: MessageType; + unread_total: number; + unread_notification: number; + unread_atme: number; + records: Message[]; + records_count: number; + page_num: number; + total_page_count: number; + page_size: number; +} + +// 消息数据 +export interface Message { + id: number; + sender: number; + receiver: number; + content: string; + status: MessageStatus; + type: MessageType; + source: string; + extra: string; + notification_url: string; + created_at: Date; +} + +export type MessageContentProps = { + messageType: MessageType; +}; + +function MessageContent({ messageType }: MessageContentProps) { + const { initialState } = useModel('@@initialState'); + const { currentUser } = initialState || {}; + const { userId } = currentUser || {}; + const [pagination, setPagination] = useState({ + current: 1, + pageSize: 20, + }); + const [messages, setMessages] = useState(undefined); + const [allTotal, setAllTotal] = useState(0); + const [unreadTotal, setUnreadTotal] = useState(undefined); + const [messageStatus, setMessageStatus] = useState(MessageStatus.UnRead); + const [isDelete, setIsDelete] = useState(false); + const messageIds = useMemo(() => messages?.map((v) => v.id), [messages]); + const [ + selectedMessages, + setSelectedMessages, + messagesAllChecked, + messagesIndeterminate, + checkAllMessages, + isSingleMessagesChecked, + checkSingleMessages, + ] = useCheck(messageIds ?? []); + + const tabs = useMemo( + () => [ + { + title: '未读', + status: MessageStatus.UnRead, + total: unreadTotal, + }, + { + title: '全部', + status: MessageStatus.All, + }, + ], + [unreadTotal], + ); + + const getMessages = useCallback(async () => { + const params: Record = { + receiver: userId, + status: messageStatus, + type: messageType, + page: pagination.current! - 1, + size: pagination.pageSize, + }; + const [res] = await to(getMessageListReq(params)); + if (res && res.data) { + const { records, records_count, unread_notification, unread_atme } = + res.data as MessageResponse; + setMessages(records); + setAllTotal(records_count); + setUnreadTotal(messageType === MessageType.System ? unread_notification : unread_atme); + } + }, [pagination, userId, messageStatus, messageType]); + + // 标记已读 + const readMessages = async (ids?: number[]) => { + const params: Record = { + ids: ids, + }; + const [res] = await to(readMessagesReq(params)); + if (res && res.data) { + getMessages(); + } + }; + + // 删除 + const deleteMessages = async (ids?: number[]) => { + const params: Record = { + ids: ids, + }; + const [res] = await to(deleteMessagesReq(params)); + if (res && res.data) { + getMessages(); + } + }; + + // 取消批量删除 + const cancelBatchDelete = useCallback(() => { + setIsDelete(false); + setSelectedMessages([]); + }, [setSelectedMessages]); + + useEffect(() => { + getMessages(); + }, [getMessages]); + + useEffect(() => { + cancelBatchDelete(); + }, [messageType, messageStatus, cancelBatchDelete]); + + // 批量删除 + const handleBatchDelete = () => { + modalConfirm({ + title: '删除后,消息不可恢复', + content: '是否确认删除?', + onOk: () => { + setIsDelete(false); + setSelectedMessages([]); + }, + }); + }; + + // 分页切换 + const handlePageChange: PaginationProps['onChange'] = (page, pageSize) => { + setPagination({ + current: page, + pageSize: pageSize, + }); + }; + + return ( +
+
+ {tabs.map((item) => ( +
{ + setMessageStatus(item.status); + }} + > + {item.title + (item.total ? `(${item.total})` : '')} +
+ ))} + + {isDelete ? ( + <> + + + + ) : ( + <> + {messageType === MessageType.Mine && allTotal > 0 && ( + + )} + + + )} +
+ + {isDelete && ( +
+ + 全选 + + + 已选 + + {selectedMessages.length} + + 项 + +
+ )} + + {messages && messages.length > 0 && ( + <> +
+ {messages.map((message) => ( +
+ {messageType === MessageType.Mine && isDelete && ( + { + e.stopPropagation(); + checkSingleMessages(message.id); + }} + > + )} + {messageStatus === MessageStatus.All && ( +
+ {message.status === MessageStatus.UnRead ? '未读' : '已读'} +
+ )} + + {message.content} + +
+ {ago(message.created_at)} +
+ {message.status === MessageStatus.UnRead && ( + + )} + {messageType === MessageType.Mine && ( + + )} +
+ ))} +
+ + + )} + {messages && messages.length === 0 && ( + + )} +
+ ); +} + +export default MessageContent; diff --git a/react-ui/src/pages/Message/components/Menu/index.less b/react-ui/src/pages/Message/components/Menu/index.less new file mode 100644 index 00000000..dbfba954 --- /dev/null +++ b/react-ui/src/pages/Message/components/Menu/index.less @@ -0,0 +1,69 @@ +.message-menu { + flex: none; + width: 196px; + height: 100%; + .backgroundFullImage(url(@/assets/img/message/menu-bg.png)); + + &__title { + position: relative; + margin-bottom: 25px; + padding: 20px 20px 10px; + color: @text-color; + font-size: @font-size; + + &::after { + position: absolute; + right: 20px; + bottom: 0; + left: 20px; + border-bottom: 1px dashed rgba(130, 132, 164, 0.18); + content: ''; + } + } + + &__item { + display: flex; + align-items: center; + margin-bottom: 4px; + padding: 10px 0 10px 18px; + color: @text-color-secondary; + font-size: @font-size; + border-left: 2px solid transparent; + + &--selected, + &:hover { + color: @primary-color; + background-image: linear-gradient( + 101.08deg, + rgba(81, 76, 249, 0.09) 0%, + rgba(255, 255, 255, 0) 100% + ); + border-left: 2px solid @primary-color; + } + + &__icon, + &__icon--hover { + width: 18px; + height: 18px; + margin-right: 10px; + } + + &__icon { + display: block; + } + + &__icon--hover { + display: none; + } + + &--selected &__icon, + &__item:hover &__icon { + display: none; + } + + &--selected &__icon--hover, + &__item:hover &__icon--hover { + display: block; + } + } +} diff --git a/react-ui/src/pages/Message/components/Menu/index.tsx b/react-ui/src/pages/Message/components/Menu/index.tsx new file mode 100644 index 00000000..351c1873 --- /dev/null +++ b/react-ui/src/pages/Message/components/Menu/index.tsx @@ -0,0 +1,50 @@ +import AtHoverIcon from '@/assets/img/message/at-hover.png'; +import AtIcon from '@/assets/img/message/at.png'; +import SystemHoverIcon from '@/assets/img/message/system-hover.png'; +import SystemIcon from '@/assets/img/message/system.png'; +import { MessageType } from '@/enums'; +import classNames from 'classnames'; +import styles from './index.less'; + +const menus = [ + { + title: '系统消息', + icon: SystemIcon, + hoverIcon: SystemHoverIcon, + type: MessageType.System, + }, + { + title: '@我的', + icon: AtIcon, + hoverIcon: AtHoverIcon, + type: MessageType.Mine, + }, +]; + +export type MessageMenuProps = { + messageType: MessageType; + onChange: (type: MessageType) => void; +}; + +function MessageMenu({ messageType: currentType, onChange }: MessageMenuProps) { + return ( +
+
消息列表
+ {menus.map((item) => ( +
onChange(item.type)} + > + + + {item.title} +
+ ))} +
+ ); +} + +export default MessageMenu; diff --git a/react-ui/src/pages/Message/index.less b/react-ui/src/pages/Message/index.less new file mode 100644 index 00000000..c8232486 --- /dev/null +++ b/react-ui/src/pages/Message/index.less @@ -0,0 +1,8 @@ +.message { + display: flex; + flex-direction: row; + gap: 0 20px; + height: 100%; + padding: 75px 260px 30px; + .backgroundFullImage(url(@/assets/img/message/message-bg.png)); +} diff --git a/react-ui/src/pages/Message/index.tsx b/react-ui/src/pages/Message/index.tsx new file mode 100644 index 00000000..10bcdcf3 --- /dev/null +++ b/react-ui/src/pages/Message/index.tsx @@ -0,0 +1,20 @@ +import { MessageType } from '@/enums'; +import { useState } from 'react'; +import MessageContent from './components/Content'; +import MessageMenu from './components/Menu'; +import styles from './index.less'; + +function MessagePage() { + const [messageType, setMessageType] = useState(MessageType.System); + return ( +
+ setMessageType(type)} + messageType={messageType} + > + +
+ ); +} + +export default MessagePage; diff --git a/react-ui/src/pages/System/Approval/index.less b/react-ui/src/pages/System/Approval/index.less new file mode 100644 index 00000000..c8834492 --- /dev/null +++ b/react-ui/src/pages/System/Approval/index.less @@ -0,0 +1,22 @@ +.approval-list { + height: 100%; + &__header { + display: flex; + align-items: center; + justify-content: space-between; + height: 50px; + margin-bottom: 10px; + padding: 0 30px; + background-image: url(@/assets/img/page-title-bg.png); + background-repeat: no-repeat; + background-position: top center; + background-size: 100% 100%; + } + + &__table { + height: calc(100% - 60px); + padding: 20px 30px 0; + background-color: white; + border-radius: 10px; + } +} diff --git a/react-ui/src/pages/System/Approval/index.tsx b/react-ui/src/pages/System/Approval/index.tsx new file mode 100644 index 00000000..20a874e4 --- /dev/null +++ b/react-ui/src/pages/System/Approval/index.tsx @@ -0,0 +1,407 @@ +/* + * @Author: 赵伟 + * @Date: 2024-04-16 13:58:08 + * @Description: 开发环境列表 + */ + +import { CodeConfigData } from '@/components/CodeSelectorModal'; +import KFIcon from '@/components/KFIcon'; +import { DevEditorStatus } from '@/enums'; +import { useCacheState } from '@/hooks/useCacheState'; +import { useSystemResource } from '@/hooks/useComputingResource'; +import { DatasetData, ModelData } from '@/pages/Dataset/config'; +import { + deleteEditorReq, + getEditorListReq, + startEditorReq, + stopEditorReq, +} from '@/services/developmentEnvironment'; +import themes from '@/styles/theme.less'; +import { parseJsonText } from '@/utils'; +import { formatCodeConfig, formatDataset, formatModel } from '@/utils/format'; +import { to } from '@/utils/promise'; +import SessionStorage from '@/utils/sessionStorage'; +import tableCellRender, { TableCellValueType } from '@/utils/table'; +import { modalConfirm } from '@/utils/ui'; +import { useNavigate } from '@umijs/max'; +import { + App, + Button, + ConfigProvider, + Table, + type TablePaginationConfig, + type TableProps, +} from 'antd'; +import classNames from 'classnames'; +import { useCallback, useState } from 'react'; +// import CreateMirrorModal from '../components/CreateMirrorModal'; +// import EditorStatusCell from '../components/EditorStatusCell'; +import styles from './index.less'; + +export type EditorData = { + id: number; + name: string; + status: string; + computing_resource: string; + update_by: string; + create_time: string; + url: string; + computing_resource_id: number; + dataset?: string | DatasetData; + model?: string | ModelData; + image?: string; + code_config?: string | CodeConfigData; +}; + +function ApprovalList() { + const navigate = useNavigate(); + const [cacheState, setCacheState] = useCacheState(); + const { message } = App.useApp(); + const [tableData, setTableData] = useState([]); + const [total, setTotal] = useState(0); + const [pagination, setPagination] = useState( + cacheState?.pagination ?? { + current: 1, + pageSize: 10, + }, + ); + const getResourceDescription = useSystemResource(); + + // 获取编辑器列表 + const getEditorList = useCallback(async () => { + const params: Record = { + page: pagination.current! - 1, + size: pagination.pageSize, + }; + const [res] = await to(getEditorListReq(params)); + if (res && res.data) { + const { content = [], totalElements = 0 } = res.data; + content.forEach((item: EditorData) => { + item.dataset = typeof item.dataset === 'string' ? parseJsonText(item.dataset) : null; + item.model = typeof item.model === 'string' ? parseJsonText(item.model) : null; + item.image = typeof item.image === 'string' ? parseJsonText(item.image) : null; + item.code_config = + typeof item.code_config === 'string' ? parseJsonText(item.code_config) : null; + }); + setTableData(content); + setTotal(totalElements); + } + }, [pagination]); + + // useEffect(() => { + // getEditorList(); + // }, [getEditorList]); + + // 删除编辑器 + const deleteEditor = async (id: number) => { + const [res] = await to(deleteEditorReq(id)); + if (res) { + message.success('删除成功'); + // 如果是一页的唯一数据,删除后,请求第一页的数据 + // 否则直接刷新这一页的数据 + setPagination((prev) => { + return { + ...prev, + current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, + }; + }); + } + }; + + // 启动编辑器 + const startEditor = async (id: number) => { + const [res] = await to(startEditorReq(id)); + if (res) { + message.success('操作成功'); + getEditorList(); + } + }; + + // 停止编辑器 + const stopEditor = async (id: number) => { + modalConfirm({ + title: '停止后,该编辑器将不可使用', + content: '是否确认停止?', + isDelete: false, + onOk: async () => { + const [res] = await to(stopEditorReq(id)); + if (res) { + message.success('操作成功'); + getEditorList(); + } + }, + }); + }; + + // 制作镜像 + // const createMirror = (id: number) => { + // const { close } = openAntdModal(CreateMirrorModal, { + // envId: id, + // onOk: () => { + // close(); + // }, + // }); + // }; + + // 处理删除 + const handleEditorDelete = (record: EditorData) => { + modalConfirm({ + title: '删除后,该编辑器将不可恢复', + content: '是否确认删除?', + onOk: () => { + deleteEditor(record.id); + }, + }); + }; + + // 创建编辑器 + const createEditor = () => { + navigate(`/developmentEnvironment/create`); + setCacheState({ + pagination, + }); + }; + + // 跳转编辑器页面 + const gotoEditorPage = (record: EditorData, e: React.MouseEvent) => { + e.stopPropagation(); + + setCacheState({ + pagination, + }); + + SessionStorage.setItem(SessionStorage.editorUrlKey, record.url); + navigate(`/developmentEnvironment/editor`); + }; + + // 去数据集 + const gotoDataset = (record: EditorData, e: React.MouseEvent) => { + e.stopPropagation(); + + const dataset = record.dataset as DatasetData; + const link = formatDataset(dataset)?.link; + if (link) { + setCacheState({ + pagination, + }); + navigate(link); + } + }; + + // 去模型 + const gotoModel = (record: EditorData, e: React.MouseEvent) => { + e.stopPropagation(); + + const model = record.model as ModelData; + const link = formatModel(model)?.link; + if (link) { + setCacheState({ + pagination, + }); + navigate(link); + } + }; + + // 打开代码配置仓库 + const gotoCodeConfig = (record: EditorData, e: React.MouseEvent) => { + e.stopPropagation(); + + const codeConfig = record.code_config as CodeConfigData; + const url = formatCodeConfig(codeConfig)?.url; + if (url) { + window.open(url, '_blank'); + } + }; + + // 分页切换 + const handleTableChange: TableProps['onChange'] = ( + pagination, + _filters, + _sorter, + { action }, + ) => { + if (action === 'paginate') { + setPagination(pagination); + } + }; + + const columns: TableProps['columns'] = [ + { + title: '编辑器名称', + dataIndex: 'name', + key: 'name', + width: '12%', + render: (text, record, index) => + record.url && record.status === DevEditorStatus.Running + ? tableCellRender(true, TableCellValueType.Link, { + onClick: gotoEditorPage, + })(text, record, index) + : tableCellRender(true, TableCellValueType.Text)(text, record, index), + }, + { + title: '计算资源', + dataIndex: 'computing_resource', + key: 'computing_resource', + width: '11%', + render: tableCellRender(), + }, + { + title: '资源规格', + dataIndex: 'computing_resource_id', + key: 'computing_resource_id', + width: '11%', + render: tableCellRender(true, TableCellValueType.Custom, { + format: getResourceDescription, + }), + }, + { + title: '数据集', + dataIndex: ['dataset', 'showValue'], + key: 'dataset', + width: '11%', + render: tableCellRender(true, TableCellValueType.Link, { + onClick: gotoDataset, + }), + }, + { + title: '模型', + dataIndex: ['model', 'showValue'], + key: 'model', + width: '11%', + render: tableCellRender(true, TableCellValueType.Link, { + onClick: gotoModel, + }), + }, + { + title: '代码配置', + dataIndex: ['code_config', 'showValue'], + key: 'code_config', + width: '11%', + render: tableCellRender(true, TableCellValueType.Link, { + onClick: gotoCodeConfig, + }), + }, + { + title: '镜像', + dataIndex: ['image', 'showValue'], + key: 'image', + width: '11%', + render: tableCellRender(true), + }, + { + title: '创建者', + dataIndex: 'update_by', + key: 'update_by', + width: '11%', + render: tableCellRender(true), + }, + { + title: '创建时间', + dataIndex: 'create_time', + key: 'create_time', + width: '11%', + render: tableCellRender(true, TableCellValueType.Date), + }, + // { + // title: '状态', + // dataIndex: 'status', + // key: 'status', + // width: 100, + // render: EditorStatusCell, + // }, + { + title: '操作', + dataIndex: 'operation', + width: 270, + key: 'operation', + render: (_: any, record: EditorData) => ( +
+ {record.status === DevEditorStatus.Pending || + record.status === DevEditorStatus.Running ? ( + + ) : ( + + )} + {/* {record.status !== DevEditorStatus.Running ? ( + + ) : null} */} + + + +
+ ), + }, + ]; + + return ( +
+
+
审核
+ +
+
+ `共${total}条`, + }} + onChange={handleTableChange} + rowKey="id" + /> + + + ); +} + +export default ApprovalList; diff --git a/react-ui/src/services/message/index.ts b/react-ui/src/services/message/index.ts new file mode 100644 index 00000000..612f035c --- /dev/null +++ b/react-ui/src/services/message/index.ts @@ -0,0 +1,40 @@ +/* + * @Author: 赵伟 + * @Date: 2025-08-28 10:18:27 + * @Description: 消息 + */ + +import { request } from '@umijs/max'; + +// 获取消息列表 +export function getMessageListReq(params: any) { + return request(`/api/reader/gns/notification/gitlink/list`, { + method: 'GET', + params, + }); +} + +// 获取消息数量 +export function getMessageCountReq(params: any) { + return request(`/api/reader/gns/notification/gitlink/count`, { + method: 'GET', + params, + skipLoading: true, + }); +} + +// 标记已读 +export function readMessagesReq(data: any) { + return request(`/api/reader/gns/notification/gitlink/count`, { + method: 'POST', + data, + }); +} + +// 删除消息 +export function deleteMessagesReq(data: any) { + return request(`/api/reader/gns/notification/gitlink/count`, { + method: 'POST', + data, + }); +} diff --git a/react-ui/src/utils/date.ts b/react-ui/src/utils/date.ts index 25abb51c..df7f59ae 100644 --- a/react-ui/src/utils/date.ts +++ b/react-ui/src/utils/date.ts @@ -1,3 +1,4 @@ +import { serverNow } from '@/hooks/useServerTime'; import dayjs from 'dayjs'; /** @@ -59,6 +60,60 @@ export const elapsedTime = (begin?: string | Date | null, end?: string | Date | return elspsedArray.slice(0, 2).join(''); }; +/** + * 计算相比现在的时间过去了多久 + * + * @param {string | null | undefined} date - The starting date. + * @return {string} The formatted elapsed time string. + */ +export const ago = (date?: string | Date | null): string => { + if (date === undefined || date === null) { + return '--'; + } + + const beginDate = dayjs(date); + if (!beginDate.isValid()) { + return '--'; + } + + const endDate = dayjs(serverNow()); + const timestamp = endDate.valueOf() - beginDate.valueOf(); + if (timestamp <= 0) { + return '刚刚'; + } + const duration = dayjs.duration(timestamp); + const years = duration.years(); + const months = duration.months(); + const days = duration.days(); + const hours = duration.hours(); + const minutes = duration.minutes(); + const seconds = duration.seconds(); + const elspsedArray = []; + if (years !== 0) { + elspsedArray.push(`${years}年`); + } + if (months !== 0) { + elspsedArray.push(`${months}个月`); + } + if (days !== 0) { + elspsedArray.push(`${days}天`); + } + if (hours !== 0) { + elspsedArray.push(`${hours}小时`); + } + if (minutes !== 0) { + elspsedArray.push(`${minutes}分`); + } + if (seconds !== 0) { + elspsedArray.push(`${seconds}秒`); + } + + if (elspsedArray.length === 0) { + return '刚刚'; + } + return elspsedArray[0] + '前'; +}; + /** * 是否是有效的日期 *