feat: ️ 新增图片上传类型校验及错误提示

This commit is contained in:
LittleBoy 2025-04-16 18:32:03 +08:00
parent 116c171249
commit a2b5df22f8
7 changed files with 52 additions and 20 deletions

12
.prettierignore Normal file
View File

@ -0,0 +1,12 @@
/node_modules
package*.json
.gitignore
*.local
*_local
__test__
.ide
.vscode
.idea
test
dist
public

7
.prettierrc Normal file
View File

@ -0,0 +1,7 @@
{
"useTabs": true,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100
}

View File

@ -2,12 +2,14 @@ import React, {useState} from "react";
import {Input, Popconfirm, Spin, Upload, UploadProps} from "antd";
import {CloseOutlined} from "@ant-design/icons";
import {clsx} from "clsx";
import {useTranslation} from "react-i18next";
import styles from './article.module.scss'
import {getOssPolicy} from "@/service/api/common.ts";
import {showToast} from "@/components/message.ts";
import {IconAddImage, IconWarningCircle} from "@/components/icons";
import {useTranslation} from "react-i18next";
import {IconAddImage} from "@/components/icons";
import {ModalWarningIcon, ModalWarningTitle} from "@/components/icons/ModalWarning.tsx";
import { BizError } from '@/service/types.ts';
type Props = {
children?: React.ReactNode;
@ -37,6 +39,10 @@ export function BlockImage({data, editable, onChange, onlyUpload, onRemove}: Ima
});
const beforeUpload = async (file: any) => {
try {
// 判断文件类型
if (!MimeTypes.includes(file.type)) {
throw new Error('upload_file_type_error')
}
// 因为有超时问题,所以每次上传都重新获取参数
Data.uploadConfig = await getOssPolicy();
const suffix = file.name.slice(file.name.lastIndexOf('.'));
@ -52,17 +58,22 @@ export function BlockImage({data, editable, onChange, onlyUpload, onRemove}: Ima
const onUploadChange = async (info) => {
if (info.fileList.length == 0) return;
const file = info.fileList[0];
console.log('onChange', file);
console.log('onUploadChange', file);
if (file.status == 'done') {
setLoading(-1)
onChange?.({type: 'image', content: Data.uploadConfig?.host + '/' + file.url})
setLoading(-1);
onChange?.({ type: 'image', content: Data.uploadConfig?.host + '/' + file.url });
} else if (file.status == 'error') {
setLoading(-1)
showToast(t('upload.upload_failed'), 'warning')
if (!MimeTypes.includes(file.type)) {
showToast(t('upload.upload_file_type_error'), 'warning');
return;
}
setLoading(-1);
showToast(t('upload.upload_failed'), 'warning');
} else if (file.status == 'uploading') {
setLoading(file.percent)
setLoading(file.percent);
}
}
};
//
return <div className={styles.image}>
{editable && onlyUpload ? <div className={'relative'}>
@ -104,8 +115,9 @@ export function BlockImage({data, editable, onChange, onlyUpload, onRemove}: Ima
rootClassName={'popconfirm-main'}
placement={'right'}
arrow={false}
icon={<IconWarningCircle/>}
title={<div style={{minWidth: 150}}><span>{t('upload.delete_confirm')}</span></div>}
icon={<ModalWarningIcon/>}
title={<ModalWarningTitle/>}
description={<div style={{minWidth: 150}}><span>{t('upload.delete_confirm')}</span></div>}
onConfirm={onRemove}
okText={t('delete')}
cancelText={t('cancel')}

View File

@ -185,6 +185,7 @@
"upload": {
"delete_confirm": "Are you sure delete the picture?",
"upload_failed": "Upload failed",
"upload_file_type_error": "Only support upload image",
"upload_image": "Upload Image"
},
"user": {

View File

@ -185,6 +185,7 @@
"upload": {
"delete_confirm": "请确认删除此图片?",
"upload_failed": "上传图片失败,请重试",
"upload_file_type_error": "仅支持上传图片",
"upload_image": "上传图片"
},
"user": {

View File

@ -1,4 +1,4 @@
import {Checkbox, Popconfirm, Space} from "antd";
import {Checkbox, Space} from "antd";
import React, {useRef, useState} from "react";
import {useRequest} from "ahooks";
@ -10,13 +10,12 @@ import ButtonPush2Video, {ProcessResult} from "@/pages/news/components/button-pu
import styles from './components/style.module.scss'
import InfiniteScroller, {InfiniteScrollerRef} from "@/components/scoller/infinite-scroller.tsx";
import {IconDelete, IconEdit, IconWarningCircle} from "@/components/icons";
import {IconDelete, IconEdit} from "@/components/icons";
import {clsx} from "clsx";
import ButtonToTop from "@/components/scoller/button-to-top.tsx";
import ButtonDeleteBatch from "@/pages/news/components/button-delete-batch.tsx";
import {showErrorToast, showToast} from "@/components/message.ts";
import {useTranslation} from "react-i18next";
import {ModalWarningTitle,ModalWarningIcon} from "@/components/icons/ModalWarning.tsx";
import {DeleteItemPopoverConfirm} from "@/components/message/confirm.tsx";
const FilterCache: Partial<ApiArticleSearchParams> = {
@ -125,12 +124,12 @@ export default function NewEdit() {
...prev,
pagination: {page, limit: 10}
}))
}} onScroll={(top) => setState({showToTop: top > 30})} loading={loading}
}} onScroll={(top) => setState(s=>({...s,showToTop: top > 30}))} loading={loading}
pagination={data?.pagination}>
<div className="body">
{data?.list?.map((item, i) => {
const checked = selectedRowKeys.includes(item.id)
return <div key={i} className={clsx("row flex", {checked})}>
return <div key={item.id} className={clsx("row flex", {checked})}>
<div className="col title cursor-pointer" onClick={() => setEditId(item.id)}>
<div className="flex-1">
<div className="text-base line-clamp-1">{item.title}</div>

View File

@ -1,6 +1,6 @@
import React, {useMemo, useRef, useState} from "react";
import {Checkbox, Divider, Empty, Modal, Space} from "antd";
import {useRequest} from "ahooks";
import { useRequest, useSetState } from 'ahooks';
import {CloseOutlined} from "@ant-design/icons"
import {clsx} from "clsx";
@ -30,7 +30,7 @@ export default function NewsIndex() {
const [activeNews, setActiveNews] = useState<NewsInfo>()
const [state, setState] = useState<{
const [state, setState] = useSetState<{
checkAll?: boolean;
showToTop?: boolean;
}>({})
@ -43,10 +43,10 @@ export default function NewsIndex() {
FilterCache.tag_level_2_id = params.tag_level_2_id;
FilterCache.title = params.title;
FilterCache.time_flag = params.time_flag;
setCheckedId([])
if (params.pagination.page === 1) {
setCheckedId([])
setData(_data)
setState({checkAll: checkedId && _data.list && checkedId.length === _data.list.length})
setState({checkAll: false,showToTop: false})
} else {
setData({
pagination: _data.pagination,