| @@ -143,6 +143,11 @@ export default [ | |||
| path: 'compare', | |||
| component: './Experiment/Comparison/index', | |||
| }, | |||
| { | |||
| name: '实验可视化对比', | |||
| path: 'compare-visual', | |||
| component: './Experiment/Aim/index', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| @@ -16,7 +16,7 @@ | |||
| "docker:dev": "docker-compose -f ./docker/docker-compose.dev.yml up", | |||
| "docker:push": "npm run docker-hub:build && npm run docker:tag && docker push antdesign/ant-design-pro", | |||
| "docker:tag": "docker tag ant-design-pro antdesign/ant-design-pro", | |||
| "docs": "typedoc --entryPointStrategy expand --entryPoints 'src/utils' --skipErrorChecking --out docs", | |||
| "docs": "typedoc", | |||
| "gh-pages": "gh-pages -d dist", | |||
| "i18n-remove": "pro i18n-remove --locale=zh-CN --write", | |||
| "postinstall": "max setup", | |||
| @@ -3,6 +3,7 @@ import KFSpin from '@/components/KFSpin'; | |||
| import { getLabelStudioUrl } from '@/services/developmentEnvironment'; | |||
| import { to } from '@/utils/promise'; | |||
| import SessionStorage from '@/utils/sessionStorage'; | |||
| import { FloatButton } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useEffect, useState } from 'react'; | |||
| import { createPortal } from 'react-dom'; | |||
| @@ -13,6 +14,7 @@ export enum IframePageType { | |||
| AppDevelopment = 'AppDevelopment', // 应用开发 | |||
| DevEnv = 'DevEnv', // 开发环境 | |||
| GitLink = 'GitLink', // git link | |||
| Aim = 'Aim', // 实验对比 | |||
| } | |||
| const getRequestAPI = (type: IframePageType): (() => Promise<any>) => { | |||
| @@ -29,12 +31,20 @@ const getRequestAPI = (type: IframePageType): (() => Promise<any>) => { | |||
| }); | |||
| case IframePageType.GitLink: // git link | |||
| return () => Promise.resolve({ code: 200, data: 'http://172.20.32.201:4000' }); | |||
| case IframePageType.Aim: // Aim | |||
| return () => | |||
| Promise.resolve({ | |||
| code: 200, | |||
| data: SessionStorage.getItem(SessionStorage.aimUrlKey) || '', | |||
| }); | |||
| } | |||
| }; | |||
| type IframePageProps = { | |||
| /** 子系统 */ | |||
| type: IframePageType; | |||
| /** 是否可以在页签上打开 */ | |||
| openInTab: boolean; | |||
| /** 自定义样式类名 */ | |||
| className?: string; | |||
| /** 自定义样式 */ | |||
| @@ -42,7 +52,7 @@ type IframePageProps = { | |||
| }; | |||
| /** 系统内嵌 iframe,目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */ | |||
| function IframePage({ type, className, style }: IframePageProps) { | |||
| function IframePage({ type, openInTab = false, className, style }: IframePageProps) { | |||
| const [iframeUrl, setIframeUrl] = useState(''); | |||
| const [loading, setLoading] = useState(false); | |||
| @@ -65,6 +75,7 @@ function IframePage({ type, className, style }: IframePageProps) { | |||
| <div className={classNames('kf-iframe-page', className)} style={style}> | |||
| {loading && createPortal(<KFSpin size="large" />, document.body)} | |||
| <FullScreenFrame url={iframeUrl} onLoad={hideLoading} onError={hideLoading} /> | |||
| {openInTab && <FloatButton onClick={() => window.open(iframeUrl, '_blank')} />} | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -22,7 +22,7 @@ | |||
| border-radius: 4px; | |||
| &__value { | |||
| .singleLine(); | |||
| //.singleLine(); | |||
| margin-right: 8px; | |||
| font-size: @font-size-input; | |||
| line-height: 1.5714285714285714; | |||
| @@ -6,7 +6,7 @@ | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { CloseOutlined } from '@ant-design/icons'; | |||
| import { ConfigProvider, Form, Input } from 'antd'; | |||
| import { ConfigProvider, Form, Input, Typography } from 'antd'; | |||
| import { RuleObject } from 'antd/es/form'; | |||
| import classNames from 'classnames'; | |||
| import './index.less'; | |||
| @@ -120,7 +120,12 @@ function ParameterInput({ | |||
| > | |||
| {valueObj?.showValue ? ( | |||
| <div className="parameter-input__content"> | |||
| <span className="parameter-input__content__value">{valueObj?.showValue}</span> | |||
| <Typography.Text | |||
| className="parameter-input__content__value" | |||
| ellipsis={{ tooltip: valueObj.showValue }} | |||
| > | |||
| {valueObj.showValue} | |||
| </Typography.Text> | |||
| <CloseOutlined | |||
| className="parameter-input__content__close-icon" | |||
| onClick={handleRemove} | |||
| @@ -224,6 +224,8 @@ export class MirrorSelector implements SelectorTypeInfo { | |||
| image_id: parentKey, | |||
| page: 0, | |||
| size: 2000, | |||
| status: 'available', | |||
| state: 1, | |||
| }); | |||
| if (res && res.data) { | |||
| const list = res.data.content || []; | |||
| @@ -29,13 +29,18 @@ const removeCacheState = (key: string) => { | |||
| } | |||
| }; | |||
| // 移除所有页面 state 缓存 | |||
| /** | |||
| * 移除所有页面 state 缓存 | |||
| */ | |||
| export const removeAllPageCacheState = () => { | |||
| pageKeys.forEach((key) => { | |||
| sessionStorage.removeItem(key); | |||
| }); | |||
| }; | |||
| /** | |||
| * 缓存页面数据 | |||
| */ | |||
| export const useCacheState = () => { | |||
| const { pathname } = window.location; | |||
| const key = 'pagecache:' + pathname; | |||
| @@ -1,15 +1,9 @@ | |||
| import { useCallback, useMemo, useState } from 'react'; | |||
| /** | |||
| * @description 选择、全选操作 | |||
| * @param list 需要进行选择的列表 | |||
| * @returns selected 选中的项 | |||
| * setSelected 设置 selected 的方法 | |||
| * checked 是否全选 | |||
| * indeterminate 是否部分选中 | |||
| * checkAll 全选 | |||
| * isSingleChecked 是否单个选中 | |||
| * checkSingle 单个选中 | |||
| * 选择、全选操作 | |||
| * @param list - 需要进行选择的列表 | |||
| * @return [选中的项, 设置选中的方法, 是否全选, 是否部分选中, 全选方法,是否单个选中,选中单个方法] | |||
| */ | |||
| export const useCheck = <T>(list: T[]) => { | |||
| const [selected, setSelected] = useState<T[]>([]); | |||
| @@ -12,7 +12,7 @@ import { useCallback, useEffect, useState } from 'react'; | |||
| const computingResource: ComputingResource[] = []; | |||
| // 过滤资源规格 | |||
| /** 过滤资源规格 */ | |||
| export const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] = ( | |||
| input: string, | |||
| option?: ComputingResource, | |||
| @@ -22,13 +22,13 @@ export const filterResourceStandard: SelectProps<string, ComputingResource>['fil | |||
| ); | |||
| }; | |||
| // 资源规格字段 | |||
| /** 资源规格字段 */ | |||
| export const resourceFieldNames = { | |||
| label: 'description', | |||
| value: 'id', | |||
| }; | |||
| // 获取资源规格 | |||
| /** 获取资源规格 */ | |||
| export function useComputingResource() { | |||
| const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]); | |||
| @@ -1,6 +1,8 @@ | |||
| // 处理 react-draggable 组件拖动结束时,响应了点击事件的 | |||
| import { useState } from 'react'; | |||
| /** | |||
| * 处理 react-draggable 组件拖动结束时,响应了点击事件的 | |||
| */ | |||
| export const useDraggable = (onClick: () => void) => { | |||
| const [isDragging, setIsDragging] = useState(false); | |||
| @@ -58,7 +58,8 @@ function ExperimentInstanceComponent({ | |||
| // 删除实验实例确认 | |||
| const handleRemove = (instance: ExperimentInstance) => { | |||
| modalConfirm({ | |||
| title: '确定删除该条实例吗?', | |||
| title: '删除后,该实验实例将不可恢复', | |||
| content: '是否确认删除?', | |||
| onOk: () => { | |||
| deleteExperimentInstance(instance.id); | |||
| }, | |||
| @@ -96,6 +97,18 @@ function ExperimentInstanceComponent({ | |||
| } | |||
| }; | |||
| // 终止实验实例 | |||
| const handleTerminate = (instance: ExperimentInstance) => { | |||
| modalConfirm({ | |||
| title: '终止后,该次实验运行将不可恢复', | |||
| content: '是否确认终止?', | |||
| isDelete: false, | |||
| onOk: () => { | |||
| terminateExperimentInstance(instance); | |||
| }, | |||
| }); | |||
| }; | |||
| // 终止实验实例 | |||
| const terminateExperimentInstance = async (instance: ExperimentInstance) => { | |||
| const request = config.stopInsReq; | |||
| @@ -188,7 +201,7 @@ function ExperimentInstanceComponent({ | |||
| item.status === ExperimentStatus.Terminated | |||
| } | |||
| icon={<KFIcon type="icon-zhongzhi" />} | |||
| onClick={() => terminateExperimentInstance(item)} | |||
| onClick={() => handleTerminate(item)} | |||
| > | |||
| 终止 | |||
| </Button> | |||
| @@ -4,7 +4,12 @@ import KFModal from '@/components/KFModal'; | |||
| import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | |||
| import { addDataset } from '@/services/dataset/index.js'; | |||
| import { to } from '@/utils/promise'; | |||
| import { getFileListFromEvent, limitUploadFileType, validateUploadFiles } from '@/utils/ui'; | |||
| import { | |||
| getFileListFromEvent, | |||
| limitUploadFileType, | |||
| removeUploadedFile, | |||
| validateUploadFiles, | |||
| } from '@/utils/ui'; | |||
| import { | |||
| Button, | |||
| Form, | |||
| @@ -29,11 +34,6 @@ interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | |||
| function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { | |||
| const [uuid] = useState(Date.now()); | |||
| // const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]); | |||
| // useEffect(() => { | |||
| // getClusterOptions(); | |||
| // }, []); | |||
| // 上传组件参数 | |||
| const uploadProps: UploadProps = { | |||
| @@ -44,16 +44,9 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||
| defaultFileList: [], | |||
| accept: '.zip,.tgz', | |||
| beforeUpload: limitUploadFileType('zip,tgz'), | |||
| onRemove: removeUploadedFile, | |||
| }; | |||
| // 获取集群版本数据 | |||
| // const getClusterOptions = async () => { | |||
| // const [res] = await to(getDictSelectOption('available_cluster')); | |||
| // if (res) { | |||
| // setClusterOptions(res); | |||
| // } | |||
| // }; | |||
| // 上传请求 | |||
| const createDataset = async (params: any) => { | |||
| const [res] = await to(addDataset(params)); | |||
| @@ -113,7 +106,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入数据名称" showCount allowClear maxLength={50} /> | |||
| <Input placeholder="请输入数据名称" showCount allowClear maxLength={40} /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="数据集版本" | |||
| @@ -4,7 +4,7 @@ import KFModal from '@/components/KFModal'; | |||
| import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | |||
| import { addModel } from '@/services/dataset/index.js'; | |||
| import { to } from '@/utils/promise'; | |||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | |||
| import { getFileListFromEvent, removeUploadedFile, validateUploadFiles } from '@/utils/ui'; | |||
| import { | |||
| Button, | |||
| Form, | |||
| @@ -37,6 +37,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||
| Authorization: getAccessToken() || '', | |||
| }, | |||
| defaultFileList: [], | |||
| onRemove: removeUploadedFile, | |||
| }; | |||
| // 上传请求 | |||
| @@ -96,7 +97,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入模型名称" showCount allowClear maxLength={50} /> | |||
| <Input placeholder="请输入模型名称" showCount allowClear maxLength={40} /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="模型版本" | |||
| @@ -3,7 +3,7 @@ import KFIcon from '@/components/KFIcon'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | |||
| import { to } from '@/utils/promise'; | |||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | |||
| import { getFileListFromEvent, removeUploadedFile, validateUploadFiles } from '@/utils/ui'; | |||
| import { | |||
| Button, | |||
| Form, | |||
| @@ -50,6 +50,7 @@ function AddVersionModal({ | |||
| defaultFileList: [], | |||
| beforeUpload: config.beforeUpload, | |||
| accept: config.uploadAccept, | |||
| onRemove: removeUploadedFile, | |||
| }; | |||
| // 上传请求 | |||
| @@ -0,0 +1,12 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2025-03-31 16:38:59 | |||
| * @Description: 实验对比 Aim | |||
| */ | |||
| import IframePage, { IframePageType } from '@/components/IFramePage'; | |||
| function AimPage() { | |||
| return <IframePage type={IframePageType.Aim}></IframePage>; | |||
| } | |||
| export default AimPage; | |||
| @@ -12,8 +12,9 @@ import { | |||
| } from '@/services/experiment'; | |||
| import { tableSorter } from '@/utils'; | |||
| import { to } from '@/utils/promise'; | |||
| import SessionStorage from '@/utils/sessionStorage'; | |||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||
| import { useSearchParams } from '@umijs/max'; | |||
| import { useNavigate, useSearchParams } from '@umijs/max'; | |||
| import { App, Button, Table, TablePaginationConfig, TableProps } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useEffect, useMemo, useState } from 'react'; | |||
| @@ -46,6 +47,7 @@ function ExperimentComparison() { | |||
| }); | |||
| const { message } = App.useApp(); | |||
| const navigate = useNavigate(); | |||
| const config = comparisonConfig[comparisonType]; | |||
| useEffect(() => { | |||
| @@ -73,7 +75,9 @@ function ExperimentComparison() { | |||
| const [res] = await to(getExpMetricsReq(selectedRowKeys)); | |||
| if (res && res.data) { | |||
| const url = res.data; | |||
| window.open(url, '_blank'); | |||
| // window.open(url, '_blank'); | |||
| SessionStorage.setItem(SessionStorage.aimUrlKey, url); | |||
| navigate('../compare-visual'); | |||
| } | |||
| }; | |||
| @@ -62,7 +62,8 @@ function ExperimentInstanceComponent({ | |||
| // 删除实验实例确认 | |||
| const handleRemove = (instance: ExperimentInstance) => { | |||
| modalConfirm({ | |||
| title: '确定删除该条实例吗?', | |||
| title: '删除后,该实验实例将不可恢复', | |||
| content: '是否确认删除?', | |||
| onOk: () => { | |||
| deleteExperimentInstance(instance.id); | |||
| }, | |||
| @@ -101,7 +102,8 @@ function ExperimentInstanceComponent({ | |||
| // 终止实验实例 | |||
| const handleTerminate = (instance: ExperimentInstance) => { | |||
| modalConfirm({ | |||
| title: '确定要终止此次实验运行吗?', | |||
| title: '终止后,该次实验运行将不可恢复', | |||
| content: '是否确认终止?', | |||
| isDelete: false, | |||
| onOk: () => { | |||
| terminateExperimentInstance(instance); | |||
| @@ -286,8 +286,6 @@ function Experiment() { | |||
| message.success('运行成功'); | |||
| refreshExperimentList(); | |||
| refreshExperimentIns(id); | |||
| } else { | |||
| message.error('运行失败'); | |||
| } | |||
| }; | |||
| @@ -25,19 +25,19 @@ enum TaskType { | |||
| const taskTypeOptions = [ | |||
| { | |||
| value: 'dev_environment', | |||
| value: TaskType.DevEnvironment, | |||
| label: '开发环境', | |||
| }, | |||
| { | |||
| value: 'workflow', | |||
| value: TaskType.Workflow, | |||
| label: '实验', | |||
| }, | |||
| { | |||
| value: 'ray', | |||
| value: TaskType.Ray, | |||
| label: '超参数自动寻优', | |||
| }, | |||
| { | |||
| value: 'service', | |||
| value: TaskType.Service, | |||
| label: '服务', | |||
| }, | |||
| ]; | |||
| @@ -17,6 +17,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => { | |||
| const [form] = Form.useForm(); | |||
| const loginPassword = Form.useWatch('password', form); | |||
| const userId = props.values.userId; | |||
| const originPassword = props.values.originPassword; | |||
| const intl = useIntl(); | |||
| const handleOk = () => { | |||
| @@ -26,7 +27,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => { | |||
| props.onCancel(); | |||
| }; | |||
| const handleFinish = async (values: Record<string, any>) => { | |||
| props.onSubmit({ ...values, userId } as FormValueType); | |||
| props.onSubmit({ password: values.password, userId, originPassword } as FormValueType); | |||
| }; | |||
| const checkPassword = (rule: any, value: string) => { | |||
| @@ -63,8 +63,8 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||
| loginIp: props.values.loginIp, | |||
| loginDate: props.values.loginDate, | |||
| remark: props.values.remark, | |||
| gitLinkUsername: props.values.gitLinkUsername, | |||
| gitLinkPassword: props.values.gitLinkPassword, | |||
| // gitLinkUsername: props.values.gitLinkUsername, | |||
| // gitLinkPassword: props.values.gitLinkPassword, | |||
| credit: props.values.credit, | |||
| }); | |||
| }, [form, props, statusOptions]); | |||
| @@ -80,6 +80,7 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||
| const params = { | |||
| ...values, | |||
| userId: props.values.userId, | |||
| originPassword: props.values.originPassword, | |||
| }; | |||
| props.onSubmit(params as UserFormData); | |||
| }; | |||
| @@ -150,7 +151,7 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||
| colProps={{ md: 12, xl: 12 }} | |||
| rules={[ | |||
| { | |||
| required: false, | |||
| required: true, | |||
| message: <FormattedMessage id="请输入手机号码!" defaultMessage="请输入手机号码!" />, | |||
| }, | |||
| { | |||
| @@ -174,7 +175,7 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||
| colProps={{ md: 12, xl: 12 }} | |||
| rules={[ | |||
| { | |||
| required: false, | |||
| required: true, | |||
| message: <FormattedMessage id="请输入用户邮箱!" defaultMessage="请输入用户邮箱!" />, | |||
| }, | |||
| { | |||
| @@ -194,7 +195,7 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||
| id: 'system.user.user_name', | |||
| defaultMessage: '用户账号', | |||
| })} | |||
| hidden={userId} | |||
| disabled={!!props.values.userId} | |||
| placeholder="请输入用户账号" | |||
| colProps={{ md: 12, xl: 12 }} | |||
| rules={[ | |||
| @@ -202,9 +203,9 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||
| required: true, | |||
| }, | |||
| { | |||
| pattern: /^[a-zA-Z0-9](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/, | |||
| pattern: /^[a-zA-Z](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/, | |||
| message: | |||
| '只能包含数字,字母,下划线(_),中横线(-),英文句号(.),且必须以数字或字母开头与结尾', | |||
| '只能包含数字,字母,下划线(_),中横线(-),英文句号(.),且必须以字母开头,数字或字母结尾', | |||
| }, | |||
| ]} | |||
| /> | |||
| @@ -214,14 +215,23 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||
| id: 'system.user.password', | |||
| defaultMessage: '密码', | |||
| })} | |||
| hidden={userId} | |||
| placeholder="请输入密码" | |||
| colProps={{ md: 12, xl: 12 }} | |||
| fieldProps={{ | |||
| autoComplete: 'new-password', | |||
| }} | |||
| allowClear | |||
| rules={props.values.userId ? [] : [{ required: true, message: '请输入密码!' }]} | |||
| rules={ | |||
| props.values.userId | |||
| ? [] | |||
| : [ | |||
| { required: true, message: '请输入密码!' }, | |||
| { | |||
| pattern: /^[A-Za-z0-9!"#$%&'()*+,-./:;<=>?@[\\\]^_`{|}~]{8,16}$/, | |||
| message: '密码长度为8 ~ 16位,只支持字母数字和符号', | |||
| }, | |||
| ] | |||
| } | |||
| /> | |||
| <ProFormSelect | |||
| valueEnum={sexOptions} | |||
| @@ -279,7 +289,7 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||
| colProps={{ md: 12, xl: 12 }} | |||
| rules={[{ required: true, message: '请选择角色!' }]} | |||
| /> | |||
| <ProFormText | |||
| {/* <ProFormText | |||
| name="gitLinkUsername" | |||
| label="Git 用户名" | |||
| placeholder="请输入 Git 用户名" | |||
| @@ -300,7 +310,7 @@ const UserForm: React.FC<UserFormProps> = (props) => { | |||
| autoComplete: 'new-password', | |||
| }} | |||
| rules={props.values.userId ? [] : [{ required: true, message: '请输入 Git 密码!' }]} | |||
| /> | |||
| /> */} | |||
| <ProFormDigit | |||
| name="credit" | |||
| label="算力积分" | |||
| @@ -4,7 +4,6 @@ import { getRoleList } from '@/services/system/role'; | |||
| import { | |||
| addUser, | |||
| changeUserStatus, | |||
| exportUser, | |||
| getDeptTree, | |||
| getUser, | |||
| getUserList, | |||
| @@ -13,6 +12,7 @@ import { | |||
| updateAuthRole, | |||
| updateUser, | |||
| } from '@/services/system/user'; | |||
| import { downloadXlsx } from '@/utils/downloadfile'; | |||
| import { | |||
| DeleteOutlined, | |||
| DownOutlined, | |||
| @@ -132,15 +132,12 @@ const handleRemoveOne = async (selectedRow: API.System.User) => { | |||
| /** | |||
| * 导出数据 | |||
| * | |||
| * | |||
| */ | |||
| const handleExport = async () => { | |||
| const handleExport = async (deptId: string) => { | |||
| const hide = message.loading('正在导出'); | |||
| try { | |||
| await exportUser(); | |||
| await downloadXlsx('/api/system/user/export', 'POST', { data: { deptId: deptId } }); | |||
| hide(); | |||
| message.success('导出成功'); | |||
| return true; | |||
| } catch (error) { | |||
| hide(); | |||
| @@ -470,7 +467,7 @@ const UserTableList: React.FC = () => { | |||
| key="export" | |||
| hidden={!access.hasPerms('system:user:export')} | |||
| onClick={async () => { | |||
| handleExport(); | |||
| handleExport(selectDept.id); | |||
| }} | |||
| > | |||
| <PlusOutlined />{' '} | |||
| @@ -563,7 +560,7 @@ const UserTableList: React.FC = () => { | |||
| /> | |||
| <ResetPwd | |||
| onSubmit={async (values: any) => { | |||
| const success = await resetUserPwd(values.userId, values.password); | |||
| const success = await resetUserPwd(values); | |||
| if (success) { | |||
| setResetPwdModalVisible(false); | |||
| setSelectedRows([]); | |||
| @@ -581,7 +578,7 @@ const UserTableList: React.FC = () => { | |||
| /> | |||
| <AuthRoleForm | |||
| onSubmit={async (values: any) => { | |||
| const success = await updateAuthRole(values); | |||
| const success = await updateAuthRole(currentRow!.userId, values.roleIds); | |||
| if (success) { | |||
| setAuthRoleModalVisible(false); | |||
| setSelectedRows([]); | |||
| @@ -181,4 +181,13 @@ export function compareModelVersion(data) { | |||
| method: 'POST', | |||
| data, | |||
| }); | |||
| } | |||
| } | |||
| // 删除上传的文件 | |||
| export function deleteUploadFileReq(params) { | |||
| return request(`/api/mmp/newdataset/deleteFile`, { | |||
| method: 'DELETE', | |||
| params, | |||
| }); | |||
| } | |||
| @@ -60,8 +60,9 @@ export async function removeUser(ids: string, options?: { [key: string]: any }) | |||
| // 导出用户信息 | |||
| export function exportUser(params?: API.System.UserListParams, options?: { [key: string]: any }) { | |||
| return request<API.Result>(`/api/system/user/export`, { | |||
| method: 'GET', | |||
| params, | |||
| method: 'POST', | |||
| data: params, | |||
| skipValidating: true, | |||
| ...(options || {}), | |||
| }); | |||
| } | |||
| @@ -93,11 +94,7 @@ export function updateUserProfile(data: API.CurrentUser) { | |||
| } | |||
| // 用户密码重置 | |||
| export function resetUserPwd(userId: number, password: string) { | |||
| const data = { | |||
| userId, | |||
| password, | |||
| }; | |||
| export function resetUserPwd(data: any) { | |||
| return request<API.Result>('/api/system/user/resetPwd', { | |||
| method: 'put', | |||
| data: data, | |||
| @@ -126,16 +123,19 @@ export function uploadAvatar(data: any) { | |||
| // 查询授权角色 | |||
| export function getAuthRole(userId: number) { | |||
| return request('/system/user/authRole/' + userId, { | |||
| return request('/api/system/user/authRole/' + userId, { | |||
| method: 'get', | |||
| }); | |||
| } | |||
| // 保存授权角色 | |||
| export function updateAuthRole(data: Record<string, any>) { | |||
| return request('/system/user/authRole', { | |||
| method: 'put', | |||
| params: data, | |||
| export function updateAuthRole(userId: number, data: Record<string, any>) { | |||
| return request(`/api/system/user/authRole/${userId}`, { | |||
| method: 'PUT', | |||
| headers: { | |||
| 'Content-Type': 'application/json;charset=UTF-8', | |||
| }, | |||
| data: data, | |||
| }); | |||
| } | |||
| @@ -76,6 +76,45 @@ export const Select: Story = { | |||
| }, | |||
| }; | |||
| export const Ellipse: Story = { | |||
| args: { | |||
| placeholder: '请输入工作目录', | |||
| style: { width: 300 }, | |||
| canInput: true, | |||
| size: 'large', | |||
| }, | |||
| render: function Render(args) { | |||
| const [value, setValue] = useState<ParameterInputValue | undefined>(''); | |||
| const onClick = () => { | |||
| const value = { | |||
| value: 'storybook', | |||
| showValue: | |||
| 'storybookstorybookstorybookstorybookstorybookstorybookstorybookstorybookstorybookstorybook', | |||
| fromSelect: true, | |||
| otherValue: 'others', | |||
| }; | |||
| setValue(value); | |||
| action('onChange')(value); | |||
| }; | |||
| return ( | |||
| <> | |||
| <ParameterInput | |||
| {...args} | |||
| value={value} | |||
| onChange={(value) => { | |||
| setValue(value); | |||
| action('onChange')(value); | |||
| }} | |||
| ></ParameterInput> | |||
| <Button type="primary" style={{ display: 'block', marginTop: 10 }} onClick={onClick}> | |||
| 模拟从全局参数选择 | |||
| </Button> | |||
| </> | |||
| ); | |||
| }, | |||
| }; | |||
| export const Disabled: Story = { | |||
| args: { | |||
| placeholder: '请输入工作目录', | |||
| @@ -136,3 +136,17 @@ export type NodeStatus = { | |||
| startedAt: string; | |||
| finishedAt: string; | |||
| }; | |||
| // 应用响应 | |||
| export type AppResponse<T> = { | |||
| code: number; | |||
| msg: string; | |||
| data: T; | |||
| }; | |||
| // 上传文件的响应 | |||
| export type UploadFileRes = { | |||
| fileName: string; | |||
| fileSize: number; | |||
| url: string; | |||
| }; | |||
| @@ -22,6 +22,7 @@ declare namespace API.System { | |||
| gitLinkUsername?: string; | |||
| gitLinkPassword?: string; | |||
| credit?: number; | |||
| originPassword?: string; | |||
| } | |||
| export interface UserListParams { | |||
| @@ -1,3 +1,13 @@ | |||
| export const xlCols = { span: 12 }; | |||
| export const xllCols = { span: 10 }; | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2025-02-21 09:52:50 | |||
| * @Description: 通用表单项输入控件宽度 | |||
| */ | |||
| const xlCols = { span: 12 }; | |||
| const xllCols = { span: 10 }; | |||
| /** | |||
| * 输入控件宽度,xl: 12, xll: 10 | |||
| */ | |||
| export const formCols = { xl: xlCols, xxl: xllCols }; | |||
| @@ -1,7 +1,7 @@ | |||
| import dayjs from 'dayjs'; | |||
| /** | |||
| * Calculates the elapsed time between two dates and returns a formatted string representing the duration. | |||
| * 计算两个日期之间经过的时间,如 "3分12秒" | |||
| * | |||
| * @param {string | null | undefined} begin - The starting date. | |||
| * @param {string | null | undefined} end - The ending date. | |||
| @@ -1,19 +1,21 @@ | |||
| import { request } from '@umijs/max'; | |||
| const mimeMap = { | |||
| /** MimeType */ | |||
| export const mimeMap = { | |||
| xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', | |||
| zip: 'application/zip', | |||
| }; | |||
| /** | |||
| * 解析blob响应内容并下载 | |||
| * @param {*} res blob响应内容 | |||
| * @param {String} mimeType MIME类型 | |||
| * @param res - blob响应内容 | |||
| * @param mimeType - MIME类型 | |||
| */ | |||
| export function resolveBlob(res: any, mimeType: string) { | |||
| const aLink = document.createElement('a'); | |||
| const blob = new Blob([res.data], { type: mimeType }); | |||
| // //从response的headers中获取filename, 后端response.setHeader("Content-disposition", "attachment; filename=xxxx.docx") 设置的文件名; | |||
| // 从response的headers中获取filename, | |||
| // 后端response.setHeader("Content-disposition", "attachment; filename=xxxx.docx") 设置的文件名; | |||
| const patt = new RegExp('filename=([^;]+\\.[^\\.;]+);*'); | |||
| // console.log(res); | |||
| const contentDisposition = decodeURI(res.headers['content-disposition']); | |||
| @@ -29,6 +31,11 @@ export function resolveBlob(res: any, mimeType: string) { | |||
| document.body.removeChild(aLink); | |||
| } | |||
| /** | |||
| * 下载 Zip 文件 | |||
| * @param url - url 地址 | |||
| * @param options - 请求参数 | |||
| */ | |||
| export function downLoadZip(url: string, params?: any) { | |||
| request(url, { | |||
| method: 'GET', | |||
| @@ -40,24 +47,30 @@ export function downLoadZip(url: string, params?: any) { | |||
| }); | |||
| } | |||
| export async function downLoadXlsx(url: string, params: any, fileName: string) { | |||
| /** | |||
| * 下载 XLSX 文件 | |||
| * @param url - url 地址 | |||
| * @param method - 请求方式 | |||
| * @param options - 请求选项 | |||
| */ | |||
| export async function downloadXlsx( | |||
| url: string, | |||
| method: string = 'GET', | |||
| options?: Record<string, any>, | |||
| ) { | |||
| return request(url, { | |||
| ...params, | |||
| method: 'POST', | |||
| method: method, | |||
| ...options, | |||
| responseType: 'blob', | |||
| }).then((data) => { | |||
| const aLink = document.createElement('a'); | |||
| const blob = data as any; // new Blob([data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); | |||
| aLink.style.display = 'none'; | |||
| aLink.href = URL.createObjectURL(blob); | |||
| aLink.setAttribute('download', fileName); // 设置下载文件名称 | |||
| document.body.appendChild(aLink); | |||
| aLink.click(); | |||
| URL.revokeObjectURL(aLink.href); // 清除引用 | |||
| document.body.removeChild(aLink); | |||
| getResponse: true, | |||
| }).then((res) => { | |||
| resolveBlob(res, mimeMap.xlsx); | |||
| }); | |||
| } | |||
| /** | |||
| * @deprecated 无效 | |||
| */ | |||
| export function download(fileName: string) { | |||
| window.location.href = `/api/common/download?fileName=${encodeURI(fileName)}&delete=${true}`; | |||
| } | |||
| @@ -1,3 +1,4 @@ | |||
| import { BasicInfoLink } from '@/components/BasicInfo/types'; | |||
| import { ResourceSelectorResponse } from '@/components/ResourceSelectorModal'; | |||
| import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo'; | |||
| import { | |||
| @@ -18,8 +19,13 @@ type SelectedCodeConfig = { | |||
| show_value?: string; // 后端使用的 | |||
| }; | |||
| // 格式化数据集数组 | |||
| export const formatDatasets = (datasets?: DatasetData[]) => { | |||
| /** | |||
| * 格式化数据集数组 | |||
| * | |||
| * @param datasets - 数据集数组 | |||
| * @return 基本信息链接对象数组 | |||
| */ | |||
| export const formatDatasets = (datasets?: DatasetData[]): BasicInfoLink[] | undefined => { | |||
| if (!datasets || datasets.length === 0) { | |||
| return undefined; | |||
| } | |||
| @@ -29,8 +35,13 @@ export const formatDatasets = (datasets?: DatasetData[]) => { | |||
| })); | |||
| }; | |||
| // 格式化数据集 | |||
| export const formatDataset = (dataset?: DatasetData) => { | |||
| /** | |||
| * 格式化数据集 | |||
| * | |||
| * @param dataset - 数据集 | |||
| * @return 基本信息链接对象 | |||
| */ | |||
| export const formatDataset = (dataset?: DatasetData): BasicInfoLink | undefined => { | |||
| if (!dataset) { | |||
| return undefined; | |||
| } | |||
| @@ -40,8 +51,13 @@ export const formatDataset = (dataset?: DatasetData) => { | |||
| }; | |||
| }; | |||
| // 格式化模型 | |||
| export const formatModel = (model: ModelData) => { | |||
| /** | |||
| * 格式化模型 | |||
| * | |||
| * @param model - 模型 | |||
| * @return 基本信息链接对象 | |||
| */ | |||
| export const formatModel = (model: ModelData): BasicInfoLink | undefined => { | |||
| if (!model) { | |||
| return undefined; | |||
| } | |||
| @@ -51,16 +67,28 @@ export const formatModel = (model: ModelData) => { | |||
| }; | |||
| }; | |||
| // 格式化镜像 | |||
| export const formatMirror = (mirror: ResourceSelectorResponse) => { | |||
| /** | |||
| * 格式化镜像 | |||
| * | |||
| * @param mirror - 选择的镜像 | |||
| * @return 镜像地址 | |||
| */ | |||
| export const formatMirror = (mirror: ResourceSelectorResponse): string | undefined => { | |||
| if (!mirror) { | |||
| return undefined; | |||
| } | |||
| return mirror.path; | |||
| }; | |||
| // 格式化代码配置 | |||
| export const formatCodeConfig = (project?: ProjectDependency | SelectedCodeConfig) => { | |||
| /** | |||
| * 格式化代码配置 | |||
| * | |||
| * @param project - 代码配置或者选择的代码配置 | |||
| * @return 基本信息链接对象 | |||
| */ | |||
| export const formatCodeConfig = ( | |||
| project?: ProjectDependency | SelectedCodeConfig, | |||
| ): BasicInfoLink | undefined => { | |||
| if (!project) { | |||
| return undefined; | |||
| } | |||
| @@ -81,7 +109,12 @@ export const formatCodeConfig = (project?: ProjectDependency | SelectedCodeConfi | |||
| } | |||
| }; | |||
| // 格式化训练任务(实验实例) | |||
| /** | |||
| * 格式化训练任务(实验实例) | |||
| * | |||
| * @param task - 训练任务 | |||
| * @return 基本信息链接对象 | |||
| */ | |||
| export const formatTrainTask = (task?: TrainTask) => { | |||
| if (!task) { | |||
| return undefined; | |||
| @@ -92,8 +125,13 @@ export const formatTrainTask = (task?: TrainTask) => { | |||
| }; | |||
| }; | |||
| // 格式化数据来源 | |||
| export const formatSource = (source?: string) => { | |||
| /** | |||
| * 格式化数据来源 | |||
| * | |||
| * @param source - 数据来源枚举值 | |||
| * @return 数据来源中文名称 | |||
| */ | |||
| export const formatSource = (source?: string): string | undefined => { | |||
| if (source === DataSource.Create) { | |||
| return '用户上传'; | |||
| } else if (source === DataSource.HandExport) { | |||
| @@ -106,7 +144,12 @@ export const formatSource = (source?: string) => { | |||
| return source; | |||
| }; | |||
| // 格式化字符串数组,以逗号分隔 | |||
| /** | |||
| * 格式化字符串数组,以逗号分隔 | |||
| * | |||
| * @param value - 字符串数组 | |||
| * @return 字符串,以逗号分隔 | |||
| */ | |||
| export const formatList = (value: string[] | null | undefined): string => { | |||
| if ( | |||
| value === undefined || | |||
| @@ -119,14 +162,24 @@ export const formatList = (value: string[] | null | undefined): string => { | |||
| return value.join(','); | |||
| }; | |||
| // 格式化布尔值 | |||
| /** | |||
| * 格式化布尔值 | |||
| * | |||
| * @param value - 布尔值 | |||
| * @return true 为 "是",false 为 "否" | |||
| */ | |||
| export const formatBoolean = (value: boolean): string => { | |||
| return value ? '是' : '否'; | |||
| }; | |||
| type FormatEnumFunc = (value: string | number) => React.ReactNode; | |||
| // 格式化枚举 | |||
| /** | |||
| * 格式化枚举 | |||
| * | |||
| * @param options - 枚举选项数组 | |||
| * @return 一个函数,参数是枚举值,从选项数组中找到对应的项,然后返回该项的 label | |||
| */ | |||
| export const formatEnum = ( | |||
| options: { value?: string | number | null; label?: React.ReactNode }[], | |||
| ): FormatEnumFunc => { | |||
| @@ -6,6 +6,11 @@ export default class LocalStorage { | |||
| // 记住密码 | |||
| static readonly rememberPasswordKey = 'login-remember-password'; | |||
| /** | |||
| * 获取 LocalStorage 值 | |||
| * @param key - LocalStorage key | |||
| * @param isObject - 是不是对象 | |||
| */ | |||
| static getItem(key: string, isObject: boolean = false) { | |||
| const jsonStr = localStorage.getItem(key); | |||
| if (!isObject) { | |||
| @@ -17,12 +22,22 @@ export default class LocalStorage { | |||
| return null; | |||
| } | |||
| /** | |||
| * 设置 LocalStorage 值 | |||
| * @param key - LocalStorage key | |||
| * @param state - SessionStorage state | |||
| * @param isObject - 是不是对象 | |||
| */ | |||
| static setItem(key: string, state?: any, isObject: boolean = false) { | |||
| if (state) { | |||
| localStorage.setItem(key, isObject ? JSON.stringify(state) : state); | |||
| } | |||
| } | |||
| /** | |||
| * 移除 LocalStorage 值 | |||
| * @param key - LocalStorage key | |||
| */ | |||
| static removeItem(key: string) { | |||
| localStorage.removeItem(key); | |||
| } | |||
| @@ -1,6 +1,7 @@ | |||
| /** | |||
| * @param { Promise } promise | |||
| * @return { Promise } | |||
| * 封装 Promise,不会抛异常,resolve 的时候返回 [data, null], reject 的时候返回 [null, error] | |||
| * @param promise | |||
| * @return resolve 的时候返回 [data, null], reject 的时候返回 [null, error] | |||
| */ | |||
| export async function to<T, U = any>(promise: Promise<T>): Promise<[T, null] | [null, U]> { | |||
| try { | |||
| @@ -1,15 +1,22 @@ | |||
| import { parseJsonText } from './index'; | |||
| export default class SessionStorage { | |||
| // 用于新建镜像 | |||
| /** 用于新建镜像 */ | |||
| static readonly mirrorNameKey = 'mirror-name'; | |||
| // 模型部署服务版本 | |||
| /** 模型部署服务版本 */ | |||
| static readonly serviceVersionInfoKey = 'service-version-info'; | |||
| // 编辑器 url | |||
| /** 编辑器 url */ | |||
| static readonly editorUrlKey = 'editor-url'; | |||
| // 客户端信息 | |||
| /** 客户端信息 */ | |||
| static readonly clientInfoKey = 'client-info'; | |||
| /** aim url */ | |||
| static readonly aimUrlKey = 'aim-url'; | |||
| /** | |||
| * 获取 SessionStorage 值 | |||
| * @param key - SessionStorage key | |||
| * @param isObject - 是不是对象 | |||
| */ | |||
| static getItem(key: string, isObject: boolean = false) { | |||
| const jsonStr = sessionStorage.getItem(key); | |||
| if (!isObject) { | |||
| @@ -21,12 +28,22 @@ export default class SessionStorage { | |||
| return null; | |||
| } | |||
| /** | |||
| * 设置 SessionStorage 值 | |||
| * @param key - SessionStorage key | |||
| * @param state - SessionStorage state | |||
| * @param isObject - 是不是对象 | |||
| */ | |||
| static setItem(key: string, state?: any, isObject: boolean = false) { | |||
| if (state) { | |||
| sessionStorage.setItem(key, isObject ? JSON.stringify(state) : state); | |||
| } | |||
| } | |||
| /** | |||
| * 移除 SessionStorage 值 | |||
| * @param key - SessionStorage key | |||
| */ | |||
| static removeItem(key: string) { | |||
| sessionStorage.removeItem(key); | |||
| } | |||
| @@ -10,6 +10,10 @@ export type StatusInfo = { | |||
| color?: string; | |||
| }; | |||
| /** | |||
| * 通用的 Table 状态单元格 | |||
| * @param infos - 选项数组 | |||
| */ | |||
| function statusTableCell(infos: StatusInfo[]) { | |||
| return function (status?: string | number | null) { | |||
| const info = infos.find((item) => item.value === status); | |||
| @@ -10,27 +10,44 @@ import { Tooltip, TooltipProps, Typography } from 'antd'; | |||
| import dayjs from 'dayjs'; | |||
| export enum TableCellValueType { | |||
| /** 序号 */ | |||
| Index = 'Index', | |||
| /** 文本 */ | |||
| Text = 'Text', | |||
| /** 日期 */ | |||
| Date = 'Date', | |||
| /** 数组 */ | |||
| Array = 'Array', | |||
| /** 链接 */ | |||
| Link = 'Link', | |||
| /** 自定义 */ | |||
| Custom = 'Custom', | |||
| } | |||
| export type TableCellValueOptions<T> = { | |||
| page?: number; // 类型为 Index 时有效 | |||
| pageSize?: number; // 类型为 Index 时有效 | |||
| property?: string; // 类型为 Array 时有效 | |||
| dateFormat?: string; // 类型为 Date 时有效 | |||
| onClick?: (record: T, e: React.MouseEvent) => void; // 类型为 Link 时有效 | |||
| format?: (value: any | undefined | null, record: T, index: number) => string | undefined | null; // 类型为 Custom 时有效 | |||
| copyable?: boolean; // 省略时是否可以复制 | |||
| /** 页数,类型为 Index 时有效 */ | |||
| page?: number; | |||
| /** 分页大小,类型为 Index 时有效 */ | |||
| pageSize?: number; | |||
| /** 取数组对象的哪个属性值,类型为 Array 时有效 */ | |||
| property?: string; | |||
| /** 日期格式,类型为 Date 时有效*/ | |||
| dateFormat?: string; | |||
| /** 链接点击回调,类型为 Link 时有效 */ | |||
| onClick?: (record: T, e: React.MouseEvent) => void; | |||
| /** 自定义函数,类型为 Custom 时有效*/ | |||
| format?: (value: any | undefined | null, record: T, index: number) => string | undefined | null; | |||
| /** 省略时是否可以复制 */ | |||
| copyable?: boolean; | |||
| }; | |||
| type TableCellFormatter = (value: any | undefined | null) => string | undefined | null; | |||
| // 日期转换函数 | |||
| /** | |||
| * 日期转换函数 | |||
| * @param {string | undefined} dateFormat - 日期格式 | |||
| * @returns {TableCellFormatter} Table cell 渲染函数 | |||
| */ | |||
| function formatDateText(dateFormat?: string): TableCellFormatter { | |||
| return (value: any | undefined | null): ReturnType<TableCellFormatter> => { | |||
| if (value === undefined || value === null || value === '') { | |||
| @@ -45,7 +62,7 @@ function formatDateText(dateFormat?: string): TableCellFormatter { | |||
| /** | |||
| * 数组转换函数,将数组元素转换为字符串,用逗号分隔 | |||
| * @param {string} property 如果数组元素是对象,那么取数组元素的某个属性 | |||
| * @param {string} property - 如果数组元素是对象,那么取数组元素的某个属性 | |||
| * @returns {TableCellFormatter} Table cell 渲染函数 | |||
| */ | |||
| function formatArray(property?: string): TableCellFormatter { | |||
| @@ -65,6 +82,13 @@ function formatArray(property?: string): TableCellFormatter { | |||
| }; | |||
| } | |||
| /** | |||
| * Table cell render 函数 | |||
| * @param ellipsis - 是否省略 | |||
| * @param type - 类型 | |||
| * @param options - 选项 | |||
| * @returns React 节点 | |||
| */ | |||
| function tableCellRender<T>( | |||
| ellipsis: boolean | TooltipProps | 'auto' = false, | |||
| type: TableCellValueType = TableCellValueType.Text, | |||
| @@ -5,8 +5,10 @@ | |||
| */ | |||
| import { PageEnum } from '@/enums/pagesEnums'; | |||
| import { removeAllPageCacheState } from '@/hooks/useCacheState'; | |||
| import { deleteUploadFileReq } from '@/services/dataset/index.js'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { type ClientInfo } from '@/types'; | |||
| import { type AppResponse, type ClientInfo, type UploadFileRes } from '@/types'; | |||
| import { to } from '@/utils/promise'; | |||
| import { history } from '@umijs/max'; | |||
| import { | |||
| Modal, | |||
| @@ -25,7 +27,9 @@ type ModalConfirmProps = ModalFuncProps & { | |||
| isDelete?: boolean; | |||
| }; | |||
| // 自定义删除 Confirm 弹框 | |||
| /** | |||
| * 自定义 Confirm 弹框 | |||
| */ | |||
| export function modalConfirm({ | |||
| title, | |||
| content, | |||
| @@ -45,7 +49,7 @@ export function modalConfirm({ | |||
| src={ | |||
| isDelete | |||
| ? require('@/assets/img/delete-icon.png') | |||
| : require('@/assets/img/comfirm-icon.png') | |||
| : require('@/assets/img/confirm-icon.png') | |||
| } | |||
| style={{ width: '120px', marginBottom: '24px' }} | |||
| draggable={false} | |||
| @@ -63,7 +67,7 @@ export function modalConfirm({ | |||
| /** | |||
| * 跳转到登录页 | |||
| * @param toHome 是否跳转到首页 | |||
| * @param toHome - 是否跳转到首页 | |||
| */ | |||
| export const gotoLoginPage = (toHome: boolean = true) => { | |||
| const { pathname, search } = location; | |||
| @@ -80,6 +84,9 @@ export const gotoLoginPage = (toHome: boolean = true) => { | |||
| } | |||
| }; | |||
| /** | |||
| * 跳转到 OAuth2 登录页 | |||
| */ | |||
| export const gotoOAuth2 = () => { | |||
| const clientInfo = SessionStorage.getItem(SessionStorage.clientInfoKey, true) as ClientInfo; | |||
| if (clientInfo) { | |||
| @@ -89,7 +96,10 @@ export const gotoOAuth2 = () => { | |||
| } | |||
| }; | |||
| // 从事件中获取上传文件列表,用于 Upload + Form 中 | |||
| /** | |||
| * 从事件中获取上传文件列表,用于 Upload + Form 中 | |||
| * @param e - 事件,包含文件列表 fileList | |||
| */ | |||
| export const getFileListFromEvent = (e: any) => { | |||
| const fileList: UploadFile[] = (Array.isArray(e) ? e : e?.fileList) || []; | |||
| return fileList.map((item) => { | |||
| @@ -137,7 +147,10 @@ export const validateUploadFiles = (files: UploadFile[], required: boolean = tru | |||
| return !hasError; | |||
| }; | |||
| // 限制上传文件类型 | |||
| /** | |||
| * 限制上传文件类型 | |||
| * @param type - 只允许上次的的文件类型 | |||
| */ | |||
| export const limitUploadFileType = (type: string) => { | |||
| return (file: UploadFile): boolean | string => { | |||
| const acceptTypes = type.split(',').map((item) => item.trim()); | |||
| @@ -151,14 +164,40 @@ export const limitUploadFileType = (type: string) => { | |||
| }; | |||
| }; | |||
| /** | |||
| * 删除已上传的文件 | |||
| * @param file - 已上传的文件 | |||
| */ | |||
| export const removeUploadedFile = async (file: UploadFile<AppResponse<UploadFileRes[]>>) => { | |||
| const { status, response } = file; | |||
| const { code, data } = response ?? {}; | |||
| if (status === 'done' && code === 200 && Array.isArray(data) && data.length > 0) { | |||
| const uploadRes = data[0]; | |||
| const { fileName, url } = uploadRes; | |||
| const [res] = await to( | |||
| deleteUploadFileReq({ | |||
| fileName, | |||
| url, | |||
| }), | |||
| ); | |||
| if (res) { | |||
| return true; | |||
| } else { | |||
| return false; | |||
| } | |||
| } | |||
| return true; | |||
| }; | |||
| /** | |||
| * 删除 FormList 表单项,如果表单项没有值,则直接删除,否则弹出确认框 | |||
| * @param form From实例 | |||
| * @param listName FormList 的 name | |||
| * @param name FormList 的其中一项 | |||
| * @param remove FormList 的删除方法 | |||
| * @param fieldNames FormList 的子项名称数组 | |||
| * @param confirmTitle 弹出确认框的标题 | |||
| * @param form - From实例 | |||
| * @param listName - FormList 的 name | |||
| * @param name - FormList 的其中一项 | |||
| * @param remove - FormList 的删除方法 | |||
| * @param fieldNames - FormList 的子项名称数组 | |||
| * @param confirmTitle - 弹出确认框的标题 | |||
| */ | |||
| export const removeFormListItem = ( | |||
| form: FormInstance, | |||
| @@ -1,5 +1,5 @@ | |||
| { | |||
| "entryPoints": ["./src/utils"], | |||
| "entryPoints": ["./src/utils", "./src/hooks"], | |||
| "entryPointStrategy": "expand", | |||
| "out": "docs", | |||
| "excludePrivate": true, | |||
| @@ -7,5 +7,14 @@ | |||
| "excludeExternals": true, | |||
| "includeVersion": true, | |||
| "categorizeByGroup": true, | |||
| "skipErrorChecking": true, | |||
| "exclude": [ | |||
| "src/utils/formRules.ts", | |||
| "src/utils/loading.tsx", | |||
| "src/utils/menuRender.tsx", | |||
| "src/utils/IconUtil.ts", | |||
| "src/utils/permission.ts", | |||
| "src/utils/tree.ts" | |||
| ], | |||
| "name": "工具类文档" | |||
| } | |||