feat(web): app support features

This commit is contained in:
zhaoying
2026-03-17 15:55:04 +08:00
parent 6b839bd5a8
commit 130490c022
26 changed files with 1106 additions and 1146 deletions

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-06 21:09:42
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-06 12:20:43
* @Last Modified time: 2026-03-17 14:42:31
*/
/**
* File Upload Component
@@ -20,7 +20,7 @@
*
* @component
*/
import { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
import { useState, useEffect, forwardRef, useImperativeHandle, useMemo } from 'react';
import { Upload, Progress, App } from 'antd';
import type { UploadProps, UploadFile } from 'antd';
import type { UploadProps as RcUploadProps } from 'antd/es/upload/interface';
@@ -28,6 +28,7 @@ import { useTranslation } from 'react-i18next';
import { request } from '@/utils/request'
import { fileUploadUrlWithoutApiPrefix } from '@/api/fileStorage'
import type { FeaturesConfigForm } from '@/views/ApplicationConfig/types';
interface UploadFilesProps extends Omit<UploadProps, 'onChange'> {
/** Upload API endpoint */
@@ -48,14 +49,14 @@ interface UploadFilesProps extends Omit<UploadProps, 'onChange'> {
disabled?: boolean;
/** File size limit in MB */
fileSize?: number;
/** Allowed file types ['doc', 'xls', 'ppt', 'pdf'] */
fileType?: string[];
/** Auto-upload on file selection, default is true */
isAutoUpload?: boolean;
/** Maximum number of files allowed */
maxCount?: number;
/** Custom file removal callback */
onRemove?: (file: UploadFile) => boolean | void | Promise<boolean | void>;
featureConfig: FeaturesConfigForm['file_upload']
}
const transform_file_type = {
@@ -130,11 +131,11 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
onChange,
disabled = false,
fileSize = 5,
fileType = Object.entries(ALL_FILE_TYPE).map(([key]) => key),
isAutoUpload = true,
maxCount = 1,
onRemove: customOnRemove,
requestConfig,
featureConfig,
...props
}, ref) => {
const { t } = useTranslation();
@@ -142,18 +143,37 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
const [fileList, setFileList] = useState<UploadFile[]>(propFileList);
const [accept, setAccept] = useState<string | undefined>();
const fileType = useMemo(() => {
let types: string[] = [];
['image', 'document', 'video', 'audio'].forEach(type => {
if (featureConfig[`${type}_enabled` as keyof FeaturesConfigForm['file_upload']]) {
types = types.concat(featureConfig[`${type}_allowed_extensions` as keyof FeaturesConfigForm['file_upload']] as string[])
}
})
return types
}, [featureConfig])
/**
* Validates file type and size before upload
* @returns Upload.LIST_IGNORE to prevent upload, or true to proceed
*/
const beforeUpload: RcUploadProps['beforeUpload'] = (file) => {
// Validate file size
if (fileSize) {
const isLtMaxSize = (file.size / 1024 / 1024) < fileSize;
if (!isLtMaxSize) {
message.error(t('common.fileSizeTip', { size: fileSize }));
return Upload.LIST_IGNORE;
}
// Determine file category and get max size from featureConfig
const mimePrefix = file.type?.split('/')[0]
const categoryMap: Record<string, keyof FeaturesConfigForm['file_upload']> = {
image: 'image_max_size_mb',
video: 'video_max_size_mb',
audio: 'audio_max_size_mb',
}
const maxSizeKey = categoryMap[mimePrefix] ?? 'document_max_size_mb'
const maxSize = (featureConfig[maxSizeKey] as number) ?? fileSize
const fileSizeMB = file.size / 1024 / 1024
const isLtMaxSize = fileSizeMB < maxSize;
if (!isLtMaxSize) {
message.error(t('common.fileSizeTip', { size: maxSize }));
return Upload.LIST_IGNORE;
}
// Validate file type
if (fileType && fileType.length > 0) {

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-06 21:09:47
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-04 17:47:09
* @Last Modified time: 2026-03-17 10:28:04
*/
/**
* Upload File List Modal Component
@@ -18,25 +18,28 @@
*
* @component
*/
import { forwardRef, useImperativeHandle, useState } from 'react';
import { forwardRef, useImperativeHandle, useState, useMemo } from 'react';
import { Form, Input, Select, Button, Flex } from 'antd';
import { useTranslation } from 'react-i18next';
import type { UploadFileListModalRef } from '../types'
import RbModal from '@/components/RbModal'
import type { FeaturesConfigForm } from '@/views/ApplicationConfig/types';
const FormItem = Form.Item;
interface UploadFileListModalProps {
/** Callback to refresh parent component with new file list */
refresh: (fileList?: any[]) => void;
featureConfig: FeaturesConfigForm['file_upload']
}
/**
* Modal for adding remote files via URL
*/
const UploadFileListModal = forwardRef<UploadFileListModalRef, UploadFileListModalProps>(({
refresh
refresh,
featureConfig
}, ref) => {
const { t } = useTranslation();
const [visible, setVisible] = useState(false);
@@ -79,6 +82,20 @@ const UploadFileListModal = forwardRef<UploadFileListModalRef, UploadFileListMod
handleOpen
}));
const fileTypeOptions = useMemo(() => {
const options = [];
if (featureConfig?.image_enabled) {
options.push({ label: t('memoryConversation.image'), value: 'image' });
}
if (featureConfig?.audio_enabled) {
options.push({ label: t('memoryConversation.audio'), value: 'audio' });
}
if (featureConfig?.video_enabled) {
options.push({ label: t('memoryConversation.video'), value: 'video' });
}
return options;
}, [featureConfig, t])
return (
<RbModal
title={t('memoryConversation.addRemoteFile')}
@@ -103,11 +120,7 @@ const UploadFileListModal = forwardRef<UploadFileListModalRef, UploadFileListMod
>
<Select
placeholder={t('memoryConversation.fileType')}
options={[
{ label: t('memoryConversation.image'), value: 'image' },
{ label: t('memoryConversation.audio'), value: 'audio' },
{ label: t('memoryConversation.video'), value: 'video' },
]}
options={fileTypeOptions}
className="rb:w-30"
/>
</FormItem>