Browse Source

feat: 完成消息中心和审核管理

dev-zw-notification
zhaowei 6 months ago
parent
commit
48ba6dbb55
26 changed files with 669 additions and 503 deletions
  1. +1
    -1
      react-ui/config/routes.ts
  2. +12
    -9
      react-ui/src/app.tsx
  3. BIN
      react-ui/src/assets/img/home/model-item-bg-hover2.png
  4. +1
    -0
      react-ui/src/components/MessageBroadcast/index.tsx
  5. +7
    -0
      react-ui/src/enums/index.ts
  6. +20
    -1
      react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx
  7. +5
    -0
      react-ui/src/pages/Dataset/config.tsx
  8. +1
    -1
      react-ui/src/pages/Home/components/Model/index.less
  9. +21
    -13
      react-ui/src/pages/Home/components/Model/index.tsx
  10. +0
    -25
      react-ui/src/pages/Home/components/ScrollReveal/index.tsx
  11. +3
    -4
      react-ui/src/pages/Home/components/Service/index.tsx
  12. +1
    -0
      react-ui/src/pages/Home/components/Statistics/index.less
  13. +15
    -2
      react-ui/src/pages/Message/components/Content/index.less
  14. +140
    -114
      react-ui/src/pages/Message/components/Content/index.tsx
  15. +3
    -3
      react-ui/src/pages/Message/components/Menu/index.tsx
  16. +1
    -1
      react-ui/src/pages/Message/index.less
  17. +65
    -4
      react-ui/src/pages/Message/index.tsx
  18. +9
    -0
      react-ui/src/pages/System/Approval/components/ApprovalModal/index.less
  19. +166
    -0
      react-ui/src/pages/System/Approval/components/ApprovalModal/index.tsx
  20. +15
    -0
      react-ui/src/pages/System/Approval/components/StatusCell/index.less
  21. +36
    -0
      react-ui/src/pages/System/Approval/components/StatusCell/index.tsx
  22. +97
    -318
      react-ui/src/pages/System/Approval/index.tsx
  23. +1
    -1
      react-ui/src/pages/System/Role/index.tsx
  24. +1
    -1
      react-ui/src/pages/System/User/index.tsx
  25. +17
    -0
      react-ui/src/services/dataset/index.js
  26. +31
    -5
      react-ui/src/services/message/index.ts

+ 1
- 1
react-ui/config/routes.ts View File

@@ -589,7 +589,7 @@ export default [
], ],
}, },
{ {
name: '审核',
name: '审核管理',
path: 'approval', path: 'approval',
component: './System/Approval', component: './System/Approval',
}, },


+ 12
- 9
react-ui/src/app.tsx View File

@@ -192,6 +192,9 @@ export const antd: RuntimeAntdConfig = (memo) => {
colorPrimary: themes['primaryColor'], colorPrimary: themes['primaryColor'],
colorPrimaryHover: themes['primaryHoverColor'], colorPrimaryHover: themes['primaryHoverColor'],
colorPrimaryActive: themes['primaryActiveColor'], colorPrimaryActive: themes['primaryActiveColor'],
colorPrimaryText: themes['primaryColor'],
colorPrimaryTextHover: themes['primaryHoverColor'],
colorPrimaryTextActive: themes['primaryActiveColor'],
// colorPrimaryBg: 'rgba(81, 76, 249, 0.07)', // colorPrimaryBg: 'rgba(81, 76, 249, 0.07)',
colorSuccess: themes['successColor'], colorSuccess: themes['successColor'],
colorError: themes['errorColor'], colorError: themes['errorColor'],
@@ -204,15 +207,15 @@ export const antd: RuntimeAntdConfig = (memo) => {
memo.theme.components ??= {}; memo.theme.components ??= {};
memo.theme.components.Tabs = {}; memo.theme.components.Tabs = {};
memo.theme.components.Button = { memo.theme.components.Button = {
// defaultBg: 'rgba(22, 100, 255, 0.06)',
// defaultBorderColor: 'rgba(22, 100, 255, 0.11)',
// defaultColor: themes['textColor'],
// defaultHoverBg: 'rgba(22, 100, 255, 0.06)',
// defaultHoverBorderColor: 'rgba(22, 100, 255, 0.5)',
// defaultHoverColor: '#3F7FFF',
// defaultActiveBg: 'rgba(22, 100, 255, 0.12)',
// defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)',
// defaultActiveColor: themes['primaryColor'],
defaultBg: 'rgba(22, 100, 255, 0.06)',
defaultBorderColor: 'rgba(22, 100, 255, 0.11)',
defaultColor: themes['textColor'],
defaultHoverBg: 'rgba(22, 100, 255, 0.06)',
defaultHoverBorderColor: 'rgba(22, 100, 255, 0.5)',
defaultHoverColor: '#3F7FFF',
defaultActiveBg: 'rgba(22, 100, 255, 0.12)',
defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)',
defaultActiveColor: themes['primaryColor'],
contentFontSize: parseInt(themes['fontSize']), contentFontSize: parseInt(themes['fontSize']),
}; };
memo.theme.components.Input = { memo.theme.components.Input = {


BIN
react-ui/src/assets/img/home/model-item-bg-hover2.png View File

Before After
Width: 894  |  Height: 416  |  Size: 142 kB

+ 1
- 0
react-ui/src/components/MessageBroadcast/index.tsx View File

@@ -13,6 +13,7 @@ function MessageBroadcast() {
const navigate = useNavigate(); const navigate = useNavigate();


const getMessageCount = useCallback(async () => { const getMessageCount = useCallback(async () => {
if (!userId) return;
const params: Record<string, any> = { const params: Record<string, any> = {
receiver: userId, receiver: userId,
type: -1, type: -1,


+ 7
- 0
react-ui/src/enums/index.ts View File

@@ -184,3 +184,10 @@ export enum MessageStatus {
UnRead = 1, UnRead = 1,
Readed = 2, Readed = 2,
} }

// 审核状态
export enum ApprovalStatus {
Pending = 0,
Agree = 1,
Reject = 2,
}

+ 20
- 1
react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx View File

@@ -64,7 +64,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {


// 获取详情 // 获取详情
const getResourceDetail = useCallback( const getResourceDetail = useCallback(
async (version: string | undefined) => {
async (version?: string) => {
const params = { const params = {
id: resourceId, id: resourceId,
owner, owner,
@@ -223,6 +223,21 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
} }
}; };


// 处理发布
const handlePublish = async () => {
const request = config.publish;
const params = {
id: resourceId,
owner,
name,
identifier,
};
const [res] = await to(request(params));
if (res) {
message.success('操作成功');
}
};

const items = [ const items = [
{ {
key: ResourceInfoTabKeys.Introduction, key: ResourceInfoTabKeys.Introduction,
@@ -282,6 +297,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
{(info[tagPropertyName] as string) || '--'} {(info[tagPropertyName] as string) || '--'}
</div> </div>
)} )}

<div <div
className={classNames(styles['resource-info__top__praise'], { className={classNames(styles['resource-info__top__praise'], {
[styles['resource-info__top__praise--praised']]: info.praised, [styles['resource-info__top__praise--praised']]: info.praised,
@@ -295,6 +311,9 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
/> />
<span>{info.praises_count}</span> <span>{info.praises_count}</span>
</div> </div>
<Button type="default" onClick={handlePublish}>
发布
</Button>
</Flex> </Flex>
{version ? ( {version ? (
<Flex align="center"> <Flex align="center">


+ 5
- 0
react-ui/src/pages/Dataset/config.tsx View File

@@ -19,6 +19,8 @@ import {
getModelList, getModelList,
getModelNextVersionReq, getModelNextVersionReq,
getModelVersionList, getModelVersionList,
publishDatasetReq,
publishModelReq,
} from '@/services/dataset/index.js'; } from '@/services/dataset/index.js';
import { limitUploadFileType } from '@/utils/ui'; import { limitUploadFileType } from '@/utils/ui';
import type { TabsProps, UploadFile } from 'antd'; import type { TabsProps, UploadFile } from 'antd';
@@ -45,6 +47,7 @@ type ResourceTypeInfo = {
getInfo: (params: any) => Promise<any>; // 获取详情 getInfo: (params: any) => Promise<any>; // 获取详情
compareVersion: (params: any) => Promise<any>; // 版本对比 compareVersion: (params: any) => Promise<any>; // 版本对比
getNextVersion: (params: any) => Promise<any>; // 获取下一个版本 getNextVersion: (params: any) => Promise<any>; // 获取下一个版本
publish: (params: any) => Promise<any>; // 发布
name: string; // 名称 name: string; // 名称
typeParamKey: 'data_type' | 'model_type'; // 类型参数名称,获取资源列表接口使用 typeParamKey: 'data_type' | 'model_type'; // 类型参数名称,获取资源列表接口使用
tagParamKey: 'data_tag' | 'model_tag'; // 标签参数名称,获取资源列表接口使用 tagParamKey: 'data_tag' | 'model_tag'; // 标签参数名称,获取资源列表接口使用
@@ -76,6 +79,7 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
getInfo: getDatasetInfo, getInfo: getDatasetInfo,
compareVersion: compareDatasetVersion, compareVersion: compareDatasetVersion,
getNextVersion: getDatasetNextVersionReq, getNextVersion: getDatasetNextVersionReq,
publish: publishDatasetReq,
name: '数据集', name: '数据集',
typeParamKey: 'data_type', typeParamKey: 'data_type',
tagParamKey: 'data_tag', tagParamKey: 'data_tag',
@@ -116,6 +120,7 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
getInfo: getModelInfo, getInfo: getModelInfo,
compareVersion: compareModelVersion, compareVersion: compareModelVersion,
getNextVersion: getModelNextVersionReq, getNextVersion: getModelNextVersionReq,
publish: publishModelReq,
name: '模型', name: '模型',
typeParamKey: 'model_type', typeParamKey: 'model_type',
tagParamKey: 'model_tag', tagParamKey: 'model_tag',


+ 1
- 1
react-ui/src/pages/Home/components/Model/index.less View File

@@ -30,7 +30,7 @@


&:hover { &:hover {
color: white; color: white;
.backgroundFullImage(url(@/assets/img/home/model-item-bg-hover.png));
.backgroundFullImage(url(@/assets/img/home/model-item-bg-hover2.png));
} }


&__hot { &__hot {


+ 21
- 13
react-ui/src/pages/Home/components/Model/index.tsx View File

@@ -3,34 +3,42 @@ import { getPublicModelsReq } from '@/services/home';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { gotoPageIfLogin } from '@/utils/ui'; import { gotoPageIfLogin } from '@/utils/ui';
import { Divider, Flex } from 'antd'; import { Divider, Flex } from 'antd';
import { motion, type Variants } from 'motion/react';
import { motion, useMotionValueEvent, useScroll, type Variants } from 'motion/react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import BlockTitle from '../BlockTitle'; import BlockTitle from '../BlockTitle';
import styles from './index.less'; import styles from './index.less';


const modelVariants: Variants = { const modelVariants: Variants = {
offscreen: {
y: 0,
opacity: 1,
offscreen: (down: boolean) => ({
y: 100,
opacity: 0,
transition: { transition: {
ease: 'linear', ease: 'linear',
duration: 0.3,
duration: 0.5,
}, },
},
}),
onscreen: { onscreen: {
y: [0, 200, 0],
opacity: [0, 0, 1],
y: 0,
opacity: 1,
transition: { transition: {
ease: 'easeOut', ease: 'easeOut',
duration: 0.3,
times: [0, 0, 1],
delay: 0.5,
duration: 0.5,
// times: [0, 0, 1],
}, },
}, },
}; };


function ModelBlock() { function ModelBlock() {
const [modelData, setModelData] = useState<ModelData[]>([]); const [modelData, setModelData] = useState<ModelData[]>([]);
const [isDowning, setIsDowning] = useState(true);
const { scrollYProgress } = useScroll();
useMotionValueEvent(scrollYProgress, 'change', (value) => {
setIsDowning((scrollYProgress.getPrevious() ?? 0) - value < 0);
});

useEffect(() => {
console.log(isDowning);
}, [isDowning]);


useEffect(() => { useEffect(() => {
const getPublicModels = async () => { const getPublicModels = async () => {
@@ -56,11 +64,11 @@ function ModelBlock() {
return ( return (
<motion.div <motion.div
variants={modelVariants} variants={modelVariants}
initial={false}
initial={'offscreen'}
whileInView={'onscreen'} whileInView={'onscreen'}
custom={index}
className={styles['model__item']} className={styles['model__item']}
key={item.id} key={item.id}
custom={isDowning}
onClick={() => { onClick={() => {
gotoPageIfLogin( gotoPageIfLogin(
`/dataset/model/info/${item.id}?name=${item.name}&owner=${item.owner}&identifier=${item.identifier}&is_public=${item.is_public}`, `/dataset/model/info/${item.id}?name=${item.name}&owner=${item.owner}&identifier=${item.identifier}&is_public=${item.is_public}`,


+ 0
- 25
react-ui/src/pages/Home/components/ScrollReveal/index.tsx View File

@@ -1,25 +0,0 @@
import { motion, useInView, Variants } from 'motion/react';
import { ReactNode, useRef } from 'react';

type ScrollRevealProps = {
children: ReactNode;
variants: Variants;
};

function ScrollReveal({ children, variants }: ScrollRevealProps) {
const ref = useRef<HTMLDivElement>(null);
const isInView = useInView(ref, { amount: 'all' });

return (
<motion.div
variants={variants}
ref={ref}
initial="offscreen"
animate={isInView ? 'onscreen' : 'offscreen'}
>
{children}
</motion.div>
);
}

export default ScrollReveal;

+ 3
- 4
react-ui/src/pages/Home/components/Service/index.tsx View File

@@ -15,22 +15,21 @@ import styles from './index.less';


const serviceVariants: Variants = { const serviceVariants: Variants = {
offscreen: { offscreen: {
y: -100,
y: -200,
opacity: 0, opacity: 0,
transition: { transition: {
ease: 'linear', ease: 'linear',
duration: 0, duration: 0,
}, },
}, },
onscreen: (index: number) => ({
onscreen: {
y: 0, y: 0,
opacity: 1, opacity: 1,
transition: { transition: {
type: 'spring', type: 'spring',
duration: 1, duration: 1,
delay: index * 0.3,
}, },
}),
},
}; };


function ServiceBlock() { function ServiceBlock() {


+ 1
- 0
react-ui/src/pages/Home/components/Statistics/index.less View File

@@ -9,6 +9,7 @@
&__item { &__item {
display: flex; display: flex;
align-items: center; align-items: center;
width: 9rem;


&__icon { &__icon {
width: 3.75rem; width: 3.75rem;


+ 15
- 2
react-ui/src/pages/Message/components/Content/index.less View File

@@ -8,6 +8,7 @@


&__tabs { &__tabs {
display: flex; display: flex;
flex: none;
align-items: center; align-items: center;
height: 76px; height: 76px;
padding: 0 30px; padding: 0 30px;
@@ -21,6 +22,14 @@
&--selected, &--selected,
&:hover { &:hover {
color: @text-color; color: @text-color;
font-weight: 500;
}
}

:global {
.ant-btn:first-of-type {
margin-right: 10px;
margin-left: auto;
} }
} }
} }
@@ -40,9 +49,7 @@
} }


&__list { &__list {
display: flex;
flex: 1; flex: 1;
flex-direction: column;
width: 100%; width: 100%;
overflow-y: auto; overflow-y: auto;


@@ -73,8 +80,14 @@
} }
} }


&__content {
flex: 1;
margin-right: 10px;
}

&__time { &__time {
display: block; display: block;
flex: none;
margin-left: auto; margin-left: auto;
color: @text-color-secondary; color: @text-color-secondary;
} }


+ 140
- 114
react-ui/src/pages/Message/components/Content/index.tsx View File

@@ -1,89 +1,49 @@
import KFButton from '@/components/KFButton';
import KFEmpty, { EmptyType } from '@/components/KFEmpty'; import KFEmpty, { EmptyType } from '@/components/KFEmpty';
import { MessageStatus, MessageType } from '@/enums'; import { MessageStatus, MessageType } from '@/enums';
import { useCacheState } from '@/hooks/useCacheState';
import { useCheck } from '@/hooks/useCheck'; import { useCheck } from '@/hooks/useCheck';
import { Message, MessageResponse } from '@/pages/Message';
import { deleteMessagesReq, getMessageListReq, readMessagesReq } from '@/services/message'; import { deleteMessagesReq, getMessageListReq, readMessagesReq } from '@/services/message';
import { ago } from '@/utils/date'; import { ago } from '@/utils/date';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui'; import { modalConfirm } from '@/utils/ui';
import { useModel } from '@umijs/max';
import { useModel, useNavigate } from '@umijs/max';
import { import {
Button, Button,
Checkbox, Checkbox,
Pagination, Pagination,
PaginationProps, PaginationProps,
type TablePaginationConfig,
Typography, Typography,
message,
type TablePaginationConfig,
} from 'antd'; } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import styles from './index.less'; import styles from './index.less';


// {
// "id": 673396,
// "status": 1,
// "content": "<b>陈志航</b> 已加入项目 <b>复杂智能软件系统研究/模型训练项目UI设计</b>",
// "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 = { export type MessageContentProps = {
messageType: MessageType; messageType: MessageType;
messageStatus: MessageStatus;
pagination: TablePaginationConfig;
onStatusChange: (status: MessageStatus) => void;
onPaginationChange: (pagination: TablePaginationConfig) => void;
}; };


function MessageContent({ messageType }: MessageContentProps) {
function MessageContent({
messageType,
messageStatus,
pagination,
onStatusChange,
onPaginationChange,
}: MessageContentProps) {
const { initialState } = useModel('@@initialState'); const { initialState } = useModel('@@initialState');
const { currentUser } = initialState || {}; const { currentUser } = initialState || {};
const { userId } = currentUser || {}; const { userId } = currentUser || {};
const [pagination, setPagination] = useState<TablePaginationConfig>({
current: 1,
pageSize: 20,
});
const setCacheState = useCacheState()[1];
const [messages, setMessages] = useState<Message[] | undefined>(undefined); const [messages, setMessages] = useState<Message[] | undefined>(undefined);
const [allTotal, setAllTotal] = useState<number>(0); const [allTotal, setAllTotal] = useState<number>(0);
const [unreadTotal, setUnreadTotal] = useState<number | undefined>(undefined); const [unreadTotal, setUnreadTotal] = useState<number | undefined>(undefined);
const [messageStatus, setMessageStatus] = useState(MessageStatus.UnRead);
const [isDelete, setIsDelete] = useState(false); const [isDelete, setIsDelete] = useState(false);
const messageIds = useMemo(() => messages?.map((v) => v.id), [messages]); const messageIds = useMemo(() => messages?.map((v) => v.id), [messages]);
const [ const [
@@ -95,6 +55,7 @@ function MessageContent({ messageType }: MessageContentProps) {
isSingleMessagesChecked, isSingleMessagesChecked,
checkSingleMessages, checkSingleMessages,
] = useCheck(messageIds ?? []); ] = useCheck(messageIds ?? []);
const navigate = useNavigate();


const tabs = useMemo( const tabs = useMemo(
() => [ () => [
@@ -112,11 +73,13 @@ function MessageContent({ messageType }: MessageContentProps) {
); );


const getMessages = useCallback(async () => { const getMessages = useCallback(async () => {
if (!userId) return;

const params: Record<string, any> = { const params: Record<string, any> = {
receiver: userId, receiver: userId,
status: messageStatus, status: messageStatus,
type: messageType, type: messageType,
page: pagination.current! - 1,
page: pagination.current,
size: pagination.pageSize, size: pagination.pageSize,
}; };
const [res] = await to(getMessageListReq(params)); const [res] = await to(getMessageListReq(params));
@@ -129,25 +92,71 @@ function MessageContent({ messageType }: MessageContentProps) {
} }
}, [pagination, userId, messageStatus, messageType]); }, [pagination, userId, messageStatus, messageType]);


// 标记已读
const readMessages = async (ids?: number[]) => {
const params: Record<string, any> = {
ids: ids,
};
const [res] = await to(readMessagesReq(params));
if (res && res.data) {
// 标记为已读
const readMessages = async (
message?: Message,
skipLoading: boolean = false,
skipResult: boolean = false,
) => {
const params: Record<string, any> = message
? {
notificationIds: message.id,
status: MessageStatus.Readed,
receiver: message.receiver,
type: message.type,
}
: {
notificationIds: -1,
status: MessageStatus.Readed,
receiver: userId,
type: messageType,
};
const [res] = await to(readMessagesReq(params, skipLoading));

// 点击消息置为已读时,不需要修改数据
if (!skipResult && res) {
// 如果当前是【未读】状态
// 【一键已读】后,设置分页为第一页
// 如果是一页的唯一数据,设置为前一页
if (messageStatus === MessageStatus.UnRead) {
onPaginationChange({
...pagination,
current: message
? messages?.length === 1
? Math.max(1, pagination.current! - 1)
: pagination.current
: 1,
});
}
} else {
getMessages(); getMessages();
} }
}; };


// 删除 // 删除
const deleteMessages = async (ids?: number[]) => {
const deleteMessages = async (ids: number[]) => {
if (ids.length <= 0) {
message.error('请选择要删除的消息');
return;
}

const params: Record<string, any> = { const params: Record<string, any> = {
ids: ids,
notificationIds: ids.join(','),
receiver: userId,
type: messageType,
}; };
const [res] = await to(deleteMessagesReq(params)); const [res] = await to(deleteMessagesReq(params));
if (res && res.data) {
getMessages();
if (res) {
cancelBatchDelete();
// 如果是一页的唯一数据,删除后,请求前一页的数据
// 否则直接刷新这一页的数据
onPaginationChange({
...pagination,
current:
ids.length === messages?.length
? Math.max(1, pagination.current! - 1)
: pagination.current,
});
} }
}; };


@@ -161,25 +170,46 @@ function MessageContent({ messageType }: MessageContentProps) {
getMessages(); getMessages();
}, [getMessages]); }, [getMessages]);


// 重置批量删除状态、分页
useEffect(() => { useEffect(() => {
cancelBatchDelete(); cancelBatchDelete();
}, [messageType, messageStatus, cancelBatchDelete]); }, [messageType, messageStatus, cancelBatchDelete]);


// 批量删除 // 批量删除
const handleBatchDelete = () => { const handleBatchDelete = () => {
if (selectedMessages.length <= 0) {
message.error('请选择要删除的消息');
return;
}

modalConfirm({ modalConfirm({
title: '删除后,消息不可恢复', title: '删除后,消息不可恢复',
content: '是否确认删除?', content: '是否确认删除?',
onOk: () => { onOk: () => {
setIsDelete(false);
setSelectedMessages([]);
deleteMessages(selectedMessages);
}, },
}); });
}; };


// 点击消息
const hanldeMessageClick = (message: Message) => {
if (message.status === MessageStatus.UnRead) {
readMessages(message, true, true);
}

if (message.notification_url) {
navigate(message.notification_url);
setCacheState({
messageType,
pagination,
messageStatus,
});
}
};

// 分页切换 // 分页切换
const handlePageChange: PaginationProps['onChange'] = (page, pageSize) => { const handlePageChange: PaginationProps['onChange'] = (page, pageSize) => {
setPagination({
onPaginationChange({
current: page, current: page,
pageSize: pageSize, pageSize: pageSize,
}); });
@@ -195,52 +225,34 @@ function MessageContent({ messageType }: MessageContentProps) {
[styles['message-content__tabs__item--selected']]: item.status === messageStatus, [styles['message-content__tabs__item--selected']]: item.status === messageStatus,
})} })}
onClick={() => { onClick={() => {
setMessageStatus(item.status);
onStatusChange(item.status);
}} }}
> >
<span>{item.title + (item.total ? `(${item.total})` : '')}</span>
<span>{item.title + (item.total !== undefined ? `(${item.total})` : '')}</span>
</div> </div>
))} ))}


{isDelete ? ( {isDelete ? (
<> <>
<Button
color="default"
variant="link"
style={{ marginLeft: 'auto', marginRight: 10 }}
onClick={cancelBatchDelete}
>
<KFButton kfColor="default" variant="link" onClick={cancelBatchDelete}>
取消 取消
</Button>
<Button
color="danger"
variant="link"
style={{ marginLeft: messageType === MessageType.Mine ? 0 : 'auto', marginRight: 0 }}
onClick={handleBatchDelete}
>
</KFButton>
<KFButton kfColor="danger" variant="link" onClick={handleBatchDelete}>
删除 删除
</Button>
</KFButton>
</> </>
) : ( ) : (
<> <>
{messageType === MessageType.Mine && allTotal > 0 && ( {messageType === MessageType.Mine && allTotal > 0 && (
<Button
color="primary"
variant="link"
style={{ marginLeft: 'auto', marginRight: 10 }}
onClick={() => setIsDelete(true)}
>
<KFButton kfColor="primary" variant="link" onClick={() => setIsDelete(true)}>
批量删除 批量删除
</Button>
</KFButton>
)}
{allTotal > 0 && (
<KFButton kfColor="primary" variant="link" onClick={() => readMessages()}>
一键已读
</KFButton>
)} )}
<Button
color="primary"
variant="link"
style={{ marginLeft: messageType === MessageType.Mine ? 0 : 'auto', marginRight: 0 }}
onClick={() => readMessages()}
>
一键已读
</Button>
</> </>
)} )}
</div> </div>
@@ -269,15 +281,19 @@ function MessageContent({ messageType }: MessageContentProps) {
<> <>
<div className={styles['message-content__list']}> <div className={styles['message-content__list']}>
{messages.map((message) => ( {messages.map((message) => (
<div className={styles['message-content__list__item']} key={message.id}>
<div
className={styles['message-content__list__item']}
key={message.id}
onClick={() => hanldeMessageClick(message)}
>
{messageType === MessageType.Mine && isDelete && ( {messageType === MessageType.Mine && isDelete && (
<Checkbox <Checkbox
style={{ marginRight: 10 }} style={{ marginRight: 10 }}
checked={isSingleMessagesChecked(message.id)} checked={isSingleMessagesChecked(message.id)}
onChange={(e) => {
e.stopPropagation();
onChange={() => {
checkSingleMessages(message.id); checkSingleMessages(message.id);
}} }}
onClick={(e) => e.stopPropagation()}
></Checkbox> ></Checkbox>
)} )}
{messageStatus === MessageStatus.All && ( {messageStatus === MessageStatus.All && (
@@ -292,9 +308,13 @@ function MessageContent({ messageType }: MessageContentProps) {
{message.status === MessageStatus.UnRead ? '未读' : '已读'} {message.status === MessageStatus.UnRead ? '未读' : '已读'}
</div> </div>
)} )}
<Typography.Text ellipsis={{ tooltip: message.content }}>
{message.content}
<Typography.Text
className={styles['message-content__list__item__content']}
ellipsis={{ tooltip: message.content.replace(/<\/?b>/g, '') }}
>
<span dangerouslySetInnerHTML={{ __html: message.content }}></span>
</Typography.Text> </Typography.Text>

<div className={styles['message-content__list__item__time']}> <div className={styles['message-content__list__item__time']}>
{ago(message.created_at)} {ago(message.created_at)}
</div> </div>
@@ -302,16 +322,22 @@ function MessageContent({ messageType }: MessageContentProps) {
<Button <Button
className={styles['message-content__list__item__button']} className={styles['message-content__list__item__button']}
type="link" type="link"
onClick={() => readMessages([message.id])}
onClick={(e) => {
e.stopPropagation();
readMessages(message);
}}
> >
标记已读
标记已读
</Button> </Button>
)} )}
{messageType === MessageType.Mine && ( {messageType === MessageType.Mine && (
<Button <Button
type="link" type="link"
className={styles['message-content__list__item__button']} className={styles['message-content__list__item__button']}
onClick={() => deleteMessages([message.id])}
onClick={(e) => {
e.stopPropagation();
deleteMessages([message.id]);
}}
> >
删除 删除
</Button> </Button>
@@ -336,8 +362,8 @@ function MessageContent({ messageType }: MessageContentProps) {
className={styles['message-content__empty']} className={styles['message-content__empty']}
type={EmptyType.NoData} type={EmptyType.NoData}
title="暂无数据" title="暂无数据"
content={'很抱歉,没有搜索到您想要的内容\n建议刷新试试'}
hasFooter={true}
content={`没有${messageStatus === MessageStatus.UnRead ? '未读' : ''}消息`}
hasFooter
onButtonClick={getMessages} onButtonClick={getMessages}
/> />
)} )}


+ 3
- 3
react-ui/src/pages/Message/components/Menu/index.tsx View File

@@ -23,10 +23,10 @@ const menus = [


export type MessageMenuProps = { export type MessageMenuProps = {
messageType: MessageType; messageType: MessageType;
onChange: (type: MessageType) => void;
onTypeChange: (type: MessageType) => void;
}; };


function MessageMenu({ messageType: currentType, onChange }: MessageMenuProps) {
function MessageMenu({ messageType: currentType, onTypeChange }: MessageMenuProps) {
return ( return (
<div className={styles['message-menu']}> <div className={styles['message-menu']}>
<div className={styles['message-menu__title']}>消息列表</div> <div className={styles['message-menu__title']}>消息列表</div>
@@ -36,7 +36,7 @@ function MessageMenu({ messageType: currentType, onChange }: MessageMenuProps) {
className={classNames(styles['message-menu__item'], { className={classNames(styles['message-menu__item'], {
[styles['message-menu__item--selected']]: item.type === currentType, [styles['message-menu__item--selected']]: item.type === currentType,
})} })}
onClick={() => onChange(item.type)}
onClick={() => onTypeChange(item.type)}
> >
<img className={styles['message-menu__item__icon']} src={item.icon} /> <img className={styles['message-menu__item__icon']} src={item.icon} />
<img className={styles['message-menu__item__icon--hover']} src={item.hoverIcon} /> <img className={styles['message-menu__item__icon--hover']} src={item.hoverIcon} />


+ 1
- 1
react-ui/src/pages/Message/index.less View File

@@ -3,6 +3,6 @@
flex-direction: row; flex-direction: row;
gap: 0 20px; gap: 0 20px;
height: 100%; height: 100%;
padding: 75px 260px 30px;
padding: 30px 60px 30px;
.backgroundFullImage(url(@/assets/img/message/message-bg.png)); .backgroundFullImage(url(@/assets/img/message/message-bg.png));
} }

+ 65
- 4
react-ui/src/pages/Message/index.tsx View File

@@ -1,18 +1,79 @@
import { MessageType } from '@/enums';
import { MessageStatus, MessageType } from '@/enums';
import { useCacheState } from '@/hooks/useCacheState';
import { type TablePaginationConfig } from 'antd';
import { useState } from 'react'; import { useState } from 'react';
import MessageContent from './components/Content'; import MessageContent from './components/Content';
import MessageMenu from './components/Menu'; import MessageMenu from './components/Menu';
import styles from './index.less'; import styles from './index.less';


// 消息列表接口返回类型
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;
}

function MessagePage() { function MessagePage() {
const [messageType, setMessageType] = useState(MessageType.System);
const [cacheState] = useCacheState();
const [messageType, setMessageType] = useState(cacheState?.messageType ?? MessageType.System);
const [pagination, setPagination] = useState<TablePaginationConfig>(
cacheState?.pagination ?? {
current: 1,
pageSize: 20,
},
);
const [messageStatus, setMessageStatus] = useState(
cacheState?.messageStatus ?? MessageStatus.UnRead,
);

// 重置页面为第一页
const resetToFirstPage = () => {
setPagination((prev) => ({
...prev,
current: 1,
}));
};

return ( return (
<div className={styles['message']}> <div className={styles['message']}>
<MessageMenu <MessageMenu
onChange={(type) => setMessageType(type)}
onTypeChange={(type) => {
setMessageType(type);
resetToFirstPage();
}}
messageType={messageType} messageType={messageType}
></MessageMenu> ></MessageMenu>
<MessageContent messageType={messageType}></MessageContent>
<MessageContent
messageType={messageType}
messageStatus={messageStatus}
onStatusChange={(status) => {
setMessageStatus(status);
resetToFirstPage();
}}
pagination={pagination}
onPaginationChange={setPagination}
></MessageContent>
</div> </div>
); );
} }


+ 9
- 0
react-ui/src/pages/System/Approval/components/ApprovalModal/index.less View File

@@ -0,0 +1,9 @@
.approval-info {
margin-bottom: 20px;

&__item {
display: flex;
align-items: center;
margin-bottom: 10px;
}
}

+ 166
- 0
react-ui/src/pages/System/Approval/components/ApprovalModal/index.tsx View File

@@ -0,0 +1,166 @@
import BasicInfo from '@/components/BasicInfo';
import KFModal from '@/components/KFModal';
import SubAreaTitle from '@/components/SubAreaTitle';
import { ApprovalData, ApprovalType } from '@/pages/System/Approval';
import { agreeApprovalReq, rejectApprovalReq } from '@/services/message';
import { parseJsonText } from '@/utils';
import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui';
import { Button, Form, Input, message, type ModalProps } from 'antd';

interface ApprovalModalProps extends Omit<ModalProps, 'onOk'> {
record: ApprovalData;
onOk: () => void;
}

function ApprovalModal({ record, onOk, ...rest }: ApprovalModalProps) {
const [form] = Form.useForm();
const content = parseJsonText(record.content);
const recordTypeName = record.type === ApprovalType.Dataset ? '数据集' : '模型';

const items =
record.type === ApprovalType.Dataset
? [
{
label: '数据集名称',
value: content.name,
},
{
label: '数据集分类',
value: content.dataType,
},
{
label: '研究方向',
value: content.dataTag,
},
{
label: '数据集描述',
value: content.description,
},
]
: [
{
label: '模型名称',
value: content.name,
},
{
label: '模型框架',
value: content.model_type,
},
{
label: '模型能力',
value: content.model_tag,
},
{
label: '模型描述',
value: content.description,
},
];

// 审批通过
const agreeApproval = async (remark?: string) => {
const [res] = await to(
agreeApprovalReq({
id: record.id,
result: remark,
}),
);
if (res) {
onOk?.();
}
};

// 审批拒绝
const rejectApproval = async (remark: string) => {
const [res] = await to(
rejectApprovalReq({
id: record.id,
result: remark,
}),
);
if (res) {
onOk?.();
}
};

const handleAgree = () => {
const remark = form.getFieldValue('remark') as string | undefined;
const remarkTrim = remark?.trim();
modalConfirm({
isDelete: false,
title: `审批通过后,将发布该${recordTypeName}`,
content: '是否确认通过?',
onOk: () => {
agreeApproval(remarkTrim);
},
});
};

const handleReject = () => {
const remark = form.getFieldValue('remark') as string | undefined;
const remarkTrim = remark?.trim();
if (!remarkTrim) {
message.error('请输入审批意见');
return;
}

modalConfirm({
isDelete: false,
title: `审批拒绝后,将不发布该${recordTypeName}`,
content: '是否确认拒绝?',
onOk: () => {
rejectApproval(remarkTrim);
},
});
};

return (
<KFModal
{...rest}
title="审核"
width={825}
footer={[
<Button key="agree" type="primary" onClick={handleAgree}>
审批通过
</Button>,
<Button key="reject" type="primary" danger onClick={handleReject}>
审批拒绝
</Button>,
]}
>
<SubAreaTitle
title="基本信息"
image={require('@/assets/img/mirror-basic.png')}
style={{ marginBottom: '15px' }}
></SubAreaTitle>
<BasicInfo
datas={items}
labelWidth={80}
labelAlign="justify"
style={{ marginBottom: 20, width: '100%' }}
></BasicInfo>
<Form name="form" layout="vertical" form={form} autoComplete="off">
<Form.Item
label="审核意见"
name="remark"
rules={[
{
required: false,
message: '请输入审核意见',
},
]}
>
<Input.TextArea
placeholder="请输入审核意见"
autoSize={{ minRows: 3, maxRows: 6 }}
maxLength={128}
showCount
allowClear
/>
</Form.Item>
</Form>
</KFModal>
);
}

export default ApprovalModal;

+ 15
- 0
react-ui/src/pages/System/Approval/components/StatusCell/index.less View File

@@ -0,0 +1,15 @@
.status-cell {
color: @text-color;

&--agree {
color: @success-color;
}

&--reject {
color: @error-color;
}

&--pending {
color: @text-color;
}
}

+ 36
- 0
react-ui/src/pages/System/Approval/components/StatusCell/index.tsx View File

@@ -0,0 +1,36 @@
/*
* @Author: 赵伟
* @Date: 2024-04-18 18:35:41
* @Description: 编辑器状态组件
*/
import { ApprovalStatus } from '@/enums';
import styles from './index.less';

export type DevEditorStatusInfo = {
text: string;
classname: string;
};

export const statusInfo: Record<ApprovalStatus, DevEditorStatusInfo> = {
[ApprovalStatus.Pending]: {
classname: styles['status-cell--pending'],
text: '待审核',
},
[ApprovalStatus.Agree]: {
classname: styles['status-cell--agree'],
text: '通过',
},
[ApprovalStatus.Reject]: {
classname: styles['status-cell--reject'],
text: '已拒绝',
},
};

function StatusCell(status?: ApprovalStatus | null) {
if (status === null || status === undefined || !statusInfo[status]) {
return <span>--</span>;
}
return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>;
}

export default StatusCell;

+ 97
- 318
react-ui/src/pages/System/Approval/index.tsx View File

@@ -4,217 +4,82 @@
* @Description: 开发环境列表 * @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 { ApprovalStatus } from '@/enums';
import { getApprovalListReq } from '@/services/message';
import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import tableCellRender, { TableCellValueType } from '@/utils/table'; 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 { Button, Table, Typography, type TablePaginationConfig, type TableProps } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { useCallback, useState } from 'react';
// import CreateMirrorModal from '../components/CreateMirrorModal';
// import EditorStatusCell from '../components/EditorStatusCell';
import { useCallback, useEffect, useState } from 'react';
import ApprovalModal from './components/ApprovalModal';
import StatusCell from './components/StatusCell';
import styles from './index.less'; import styles from './index.less';


export type EditorData = {
export interface ApprovalData {
id: number; id: number;
name: string;
status: string;
computing_resource: string;
update_by: string;
create_time: string;
status: number;
result: null;
content: string;
applicant_id: number;
applicant_name: null;
applicant_time: Date;
approver_id: number;
approver_time: Date;
title: string;
type: ApprovalType;
url: string; url: string;
computing_resource_id: number;
dataset?: string | DatasetData;
model?: string | ModelData;
image?: string;
code_config?: string | CodeConfigData;
};
}

export enum ApprovalType {
Dataset = 'DATASET',
Model = 'MODEL',
}

const approvalTypeOptions = [
{ label: '数据集', value: ApprovalType.Dataset },
{ label: '模型', value: ApprovalType.Model },
];


function ApprovalList() { function ApprovalList() {
const navigate = useNavigate();
const [cacheState, setCacheState] = useCacheState();
const { message } = App.useApp();
const [tableData, setTableData] = useState<EditorData[]>([]);
const [tableData, setTableData] = useState<ApprovalData[]>([]);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [pagination, setPagination] = useState<TablePaginationConfig>(
cacheState?.pagination ?? {
current: 1,
pageSize: 10,
},
);
const getResourceDescription = useSystemResource();
const [pagination, setPagination] = useState<TablePaginationConfig>({
current: 1,
pageSize: 10,
});


// 获取编辑器列表
const getEditorList = useCallback(async () => {
// 获取审核列表
const getApprovalList = useCallback(async () => {
const params: Record<string, any> = { const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
current: pagination.current,
pageSize: 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));
const [res] = await to(getApprovalListReq(params));
if (res) { if (res) {
message.success('删除成功');
// 如果是一页的唯一数据,删除后,请求第一页的数据
// 否则直接刷新这一页的数据
setPagination((prev) => {
return {
...prev,
current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current,
};
});
const { rows = [], total = 0 } = res;
setTableData(rows);
setTotal(total);
} }
};

// 启动编辑器
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();
}
},
});
};
}, [pagination]);


// 制作镜像
// const createMirror = (id: number) => {
// const { close } = openAntdModal(CreateMirrorModal, {
// envId: id,
// onOk: () => {
// close();
// },
// });
// };
useEffect(() => {
getApprovalList();
}, [getApprovalList]);


// 处理删除
const handleEditorDelete = (record: EditorData) => {
modalConfirm({
title: '删除后,该编辑器将不可恢复',
content: '是否确认删除?',
// 审核
const approval = (record: ApprovalData) => {
const { close } = openAntdModal(ApprovalModal, {
record: record,
onOk: () => { onOk: () => {
deleteEditor(record.id);
close();
getApprovalList();
}, },
}); });
}; };


// 创建编辑器
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<EditorData>['onChange'] = (
const handleTableChange: TableProps<ApprovalData>['onChange'] = (
pagination, pagination,
_filters, _filters,
_sorter, _sorter,
@@ -225,147 +90,69 @@ function ApprovalList() {
} }
}; };


const columns: TableProps<EditorData>['columns'] = [
{
title: '编辑器名称',
dataIndex: 'name',
key: 'name',
width: '12%',
render: (text, record, index) =>
record.url && record.status === DevEditorStatus.Running
? tableCellRender<EditorData>(true, TableCellValueType.Link, {
onClick: gotoEditorPage,
})(text, record, index)
: tableCellRender<EditorData>(true, TableCellValueType.Text)(text, record, index),
},
const columns: TableProps<ApprovalData>['columns'] = [
{ {
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: 'title',
key: 'title',
render: (title) => (
<Typography.Text
style={{ width: '100%' }}
ellipsis={{ tooltip: title.replace(/<\/?b>/g, '') }}
>
<span dangerouslySetInnerHTML={{ __html: title }}></span>
</Typography.Text>
),
}, },
{ {
title: '型',
dataIndex: ['model', 'showValue'],
key: 'model',
width: '11%',
render: tableCellRender(true, TableCellValueType.Link, {
onClick: gotoModel,
title: '类型',
dataIndex: 'type',
key: 'type',
width: 100,
render: tableCellRender(true, TableCellValueType.Enum, {
options: approvalTypeOptions,
}), }),
}, },
{ {
title: '代码配置',
dataIndex: ['code_config', 'showValue'],
key: 'code_config',
width: '11%',
render: tableCellRender(true, TableCellValueType.Link, {
onClick: gotoCodeConfig,
}),
title: '申请者',
dataIndex: 'applicant_name',
key: 'applicant_name',
width: 180,
render: tableCellRender(true),
}, },
{ {
title: '镜像',
dataIndex: ['image', 'showValue'],
key: 'image',
width: '11%',
render: tableCellRender(true),
title: '申请时间',
dataIndex: 'applicant_time',
key: 'applicant_time',
width: 180,
render: tableCellRender(true, TableCellValueType.Date),
}, },
{ {
title: '创建者',
dataIndex: 'update_by',
key: 'update_by',
width: '11%',
title: '审核意见',
dataIndex: 'result',
key: 'result',
width: 200,
render: tableCellRender(true), 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: StatusCell,
}, },
// {
// title: '状态',
// dataIndex: 'status',
// key: 'status',
// width: 100,
// render: EditorStatusCell,
// },
{ {
title: '操作', title: '操作',
dataIndex: 'operation', dataIndex: 'operation',
width: 270,
width: 150,
key: 'operation', key: 'operation',
render: (_: any, record: EditorData) => (
render: (_: any, record: ApprovalData) => (
<div> <div>
{record.status === DevEditorStatus.Pending ||
record.status === DevEditorStatus.Running ? (
<Button
type="link"
size="small"
key="stop"
icon={<KFIcon type="icon-tingzhi" />}
onClick={() => stopEditor(record.id)}
>
停止
</Button>
) : (
<Button
type="link"
size="small"
key="debug"
icon={<KFIcon type="icon-tiaoshi" />}
onClick={() => startEditor(record.id)}
>
启动
{record.status === ApprovalStatus.Pending ? (
<Button type="link" size="small" key="stop" onClick={() => approval(record)}>
审核
</Button> </Button>
)}
{/* {record.status !== DevEditorStatus.Running ? (
<Button
type="link"
size="small"
key="jingxiang"
icon={<KFIcon type="icon-jingxiang" />}
onClick={() => createMirror(record.id)}
>
制作镜像
</Button>
) : null} */}
<ConfigProvider
theme={{
token: {
colorLink: themes['warningColor'],
},
}}
>
<Button
type="link"
size="small"
key="remove"
icon={<KFIcon type="icon-shanchu" />}
onClick={() => handleEditorDelete(record)}
>
删除
</Button>
</ConfigProvider>
) : null}
</div> </div>
), ),
}, },
@@ -374,15 +161,7 @@ function ApprovalList() {
return ( return (
<div className={styles['approval-list']}> <div className={styles['approval-list']}>
<div className={styles['approval-list__header']}> <div className={styles['approval-list__header']}>
<div>审核</div>
<Button
style={{ marginLeft: '20px' }}
type="default"
onClick={getEditorList}
icon={<KFIcon type="icon-shuaxin" />}
>
刷新
</Button>
<div>审核管理</div>
</div> </div>
<div className={classNames('vertical-scroll-table', styles['approval-list__table'])}> <div className={classNames('vertical-scroll-table', styles['approval-list__table'])}>
<Table <Table


+ 1
- 1
react-ui/src/pages/System/Role/index.tsx View File

@@ -254,7 +254,7 @@ const RoleTableList: React.FC = () => {
{ {
title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />, title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
dataIndex: 'option', dataIndex: 'option',
width: '220px',
width: '240px',
valueType: 'option', valueType: 'option',
render: (_, record) => [ render: (_, record) => [
<Button <Button


+ 1
- 1
react-ui/src/pages/System/User/index.tsx View File

@@ -281,7 +281,7 @@ const UserTableList: React.FC = () => {
{ {
title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />, title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
dataIndex: 'option', dataIndex: 'option',
width: '220px',
width: '240px',
valueType: 'option', valueType: 'option',
render: (_, record) => [ render: (_, record) => [
<Button <Button


+ 17
- 0
react-ui/src/services/dataset/index.js View File

@@ -98,6 +98,14 @@ export function getDatasetNextVersionReq(data) {
}); });
} }


// 发布数据集
export function publishDatasetReq(data) {
return request(`/api/mmp/newdataset/publish`, {
method: 'POST',
data,
});
}



// ----------------------------模型--------------------------------- // ----------------------------模型---------------------------------


@@ -237,3 +245,12 @@ export function unpraiseResourceReq(id) {
}); });
} }


// 发布模型
export function publishModelReq(data) {
return request(`/api/mmp/newmodel/publish`, {
method: 'POST',
data,
});
}



+ 31
- 5
react-ui/src/services/message/index.ts View File

@@ -20,20 +20,46 @@ export function getMessageCountReq(params: any) {
method: 'GET', method: 'GET',
params, params,
skipLoading: true, skipLoading: true,
skipErrorHandler: true,
}); });
} }


// 标记已读
export function readMessagesReq(data: any) {
return request(`/api/reader/gns/notification/gitlink/count`, {
method: 'POST',
// 单条消息标记已读
export function readMessagesReq(data: any, skipLoading: boolean) {
return request(`/api/writer/gns/notification/gitlink`, {
method: 'PUT',
data, data,
skipLoading,
}); });
} }


// 删除消息 // 删除消息
export function deleteMessagesReq(data: any) { export function deleteMessagesReq(data: any) {
return request(`/api/reader/gns/notification/gitlink/count`, {
return request(`/api/writer/gns/notification/gitlink`, {
method: 'DELETE',
data,
});
}

// 获取审核列表
export function getApprovalListReq(params: any) {
return request(`/api/mmp/sysApproval/my-approve`, {
method: 'GET',
params,
});
}

// 审核通过
export function agreeApprovalReq(data: any) {
return request(`/api/mmp/sysApproval/approve`, {
method: 'POST',
data,
});
}

// 审核拒绝
export function rejectApprovalReq(data: any) {
return request(`/api/mmp/sysApproval/reject`, {
method: 'POST', method: 'POST',
data, data,
}); });


Loading…
Cancel
Save