From 73db059e7dcfb728f5ec1bbb3d21266e9c98e0e9 Mon Sep 17 00:00:00 2001 From: linxd <544554903@qq.com> Date: Wed, 18 Jun 2025 18:46:32 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=8B=E6=83=85=E9=93=BE=E6=8E=A5=E7=AE=A1?= =?UTF-8?q?=E7=90=86;=E5=B0=81=E8=A3=85=E4=B8=8A=E4=BC=A0=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config.UAT.ts | 45 ---- config/config.dev.ts | 40 +--- config/config.prod.ts | 38 +-- config/config.sim.ts | 43 ---- config/config.ts | 2 - config/proxy.ts | 88 +------ src/components/FileUpload/FileUpload.less | 51 ++++ src/components/FileUpload/FileUpload.tsx | 226 ++++++++++++++++++ src/components/FileUpload/README.md | 127 ++++++++++ src/components/FileUpload/index.ts | 4 + src/dicts/friendLinkDict.ts | 8 +- src/locales/en-US/common.ts | 9 + src/locales/en-US/friendLink.ts | 1 + src/locales/en-US/helpManage.ts | 1 + src/locales/en-US/policyManage.ts | 3 +- src/locales/en-US/userQuestion.ts | 4 +- src/locales/zh-CN/common.ts | 9 + src/locales/zh-CN/friendLink.ts | 1 + .../friendLinkManage/friendLinkManage.tsx | 202 +++++++--------- src/servers/api/friendLink.ts | 7 + src/servers/api/typings.d.ts | 12 + src/typings.d.ts | 23 +- src/utils/request.ts | 2 +- src/utils/utils.ts | 1 - 24 files changed, 562 insertions(+), 385 deletions(-) delete mode 100644 config/config.UAT.ts delete mode 100644 config/config.sim.ts create mode 100644 src/components/FileUpload/FileUpload.less create mode 100644 src/components/FileUpload/FileUpload.tsx create mode 100644 src/components/FileUpload/README.md create mode 100644 src/components/FileUpload/index.ts diff --git a/config/config.UAT.ts b/config/config.UAT.ts deleted file mode 100644 index a022290..0000000 --- a/config/config.UAT.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { defineConfig } from 'umi'; -import proxy from './proxy'; -export default defineConfig({ - proxy: proxy['UAT'], - define:{ - //商城2.0跳转地址 - REACT_APP_MALL_V2_URL:'http://111.198.162.67/zglt/index_hzf.jsp?id=dev', - - //各系统跳转参数 - REACT_APP_CLIENT_KEY : 'KgPEkttG', - // REACT_APP_CLIENT_SECRET :'ae5bdb183c502355d2055b3de73300aa73cbfdf3', - - //密码加密参数 - REACT_APP_PASSWORD_CIPHERMODE:'1', - REACT_APP_PASSWORD_PUBLICKEY:'0428D625CEEB71CE823BD7D78DFEE7B122F2DA5C4D21E32253AD684D0FE21810394A799639C0CDFBFEB535A1DFD6A366A637E582CE0B1466A5FE7858841135DE6B', - - //当前环境 - START_ENV:'UAT', - - //询价查看报价跳转地址 - // REACT_APP_XUNJIA_REDIRECT: 'http://10.0.204.215:8280/provider_uat', - //询价-查看报价详情-虚拟用户uid - REACT_APP_XUNJIA_UID: 'admin_entrance', - - // //智慧客服ws地址 - // REACT_APP_CUSTOMERSERVICE_WS_REDIRECT: 'ws://10.242.31.158:18022/api/api/biz-customer-service', - - //智慧客服用户中心地址 - REACT_APP_CUSTOMERSERVICE_USERCENTER: 'http://10.242.31.158:18022/auth/oauth/authorize?response_type=code', - - //智慧客服client_id - REACT_APP_CUSTOMERSERVICE_CLIENT_ID: 'COsHJydx', - - //智慧客服地址 - REACT_APP_CUSTOMERSERVICE_REDIRECT: 'http://10.242.31.158:8632', - - // //智慧客服文档中心查看图片地址 - // REACT_APP_CUSTOMERSERVICE_DOC_REDIRECT: 'http://cos.gz-tst.cos.tg.unicom.local/349553515466:mall/', - - // //智慧客服加密公钥私钥ciphercode - // REACT_APP_CUSTOMERSERVICE_PUBLICKEY : '0428D625CEEB71CE823BD7D78DFEE7B122F2DA5C4D21E32253AD684D0FE21810394A799639C0CDFBFEB535A1DFD6A366A637E582CE0B1466A5FE7858841135DE6B', - // REACT_APP_CUSTOMERSERVICE_PRIVATEKEY : '4F7144028D4DCF88FA50F0E2B3FFDDCF63BBE17D1700537DCE037687D3AA3DA7', - // REACT_APP_CUSTOMERSERVICE_CIPHERCODE : 1, - }, -}); diff --git a/config/config.dev.ts b/config/config.dev.ts index d7f0893..93fd3d8 100644 --- a/config/config.dev.ts +++ b/config/config.dev.ts @@ -3,43 +3,7 @@ import proxy from './proxy'; export default defineConfig({ proxy: proxy['dev'], define: { - //商城2.0跳转地址 - REACT_APP_MALL_V2_URL: 'http://111.198.162.67/zglt/index_hzf.jsp?id=dev', - - //各系统跳转参数 - REACT_APP_CLIENT_KEY: 'KgPEkttG', - // REACT_APP_CLIENT_SECRET: 'ae5bdb183c502355d2055b3de73300aa73cbfdf3', - - //密码加密参数 - REACT_APP_PASSWORD_CIPHERMODE: '1', - REACT_APP_PASSWORD_PUBLICKEY: '0428D625CEEB71CE823BD7D78DFEE7B122F2DA5C4D21E32253AD684D0FE21810394A799639C0CDFBFEB535A1DFD6A366A637E582CE0B1466A5FE7858841135DE6B', - - //当前环境 - START_ENV: 'DEV', - - //询价查看报价跳转地址 - // REACT_APP_XUNJIA_REDIRECT: 'http://10.0.204.215:8080/provider_dev', - //询价-查看报价详情-虚拟用户uid - REACT_APP_XUNJIA_UID: 'admin_entrance', - - //智慧客服用户中心地址 - REACT_APP_CUSTOMERSERVICE_USERCENTER: 'http://10.242.31.158:8100/auth/oauth/authorize?response_type=code', - - //智慧客服client_id - REACT_APP_CUSTOMERSERVICE_CLIENT_ID: 'COsHJydx', - - //智慧客服地址 - REACT_APP_CUSTOMERSERVICE_REDIRECT: 'http://10.242.31.158:8632', - - // //智慧客服ws地址 - // REACT_APP_CUSTOMERSERVICE_WS_REDIRECT: 'ws://10.242.37.148:18022/api/api/biz-customer-service', - - // //智慧客服文档中心查看图片地址 - // REACT_APP_CUSTOMERSERVICE_DOC_REDIRECT: 'http://cos.gz-tst.cos.tg.unicom.local/349553515466:mall/', - - // //智慧客服加密公钥私钥ciphercode - // REACT_APP_CUSTOMERSERVICE_PUBLICKEY : '0428D625CEEB71CE823BD7D78DFEE7B122F2DA5C4D21E32253AD684D0FE21810394A799639C0CDFBFEB535A1DFD6A366A637E582CE0B1466A5FE7858841135DE6B', - // REACT_APP_CUSTOMERSERVICE_PRIVATEKEY : '4F7144028D4DCF88FA50F0E2B3FFDDCF63BBE17D1700537DCE037687D3AA3DA7', - // REACT_APP_CUSTOMERSERVICE_CIPHERCODE : 1, + UPLOAD_URL: '/upload', + REQUEST_BASE: '/api', }, }); diff --git a/config/config.prod.ts b/config/config.prod.ts index 61ff507..2b907e5 100644 --- a/config/config.prod.ts +++ b/config/config.prod.ts @@ -3,41 +3,7 @@ import proxy from './proxy'; export default defineConfig({ proxy: proxy['prod'], define:{ - //商城2.0跳转地址 - REACT_APP_MALL_V2_URL:'http://111.198.162.67/zglt/index_hzf.jsp?id=dev', - - //各系统跳转参数 - REACT_APP_CLIENT_KEY : 'KgPEkttG', - // REACT_APP_CLIENT_SECRET :'ae5bdb183c502355d2055b3de73300aa73cbfdf3', - - //密码加密参数 - REACT_APP_PASSWORD_CIPHERMODE:'1', - REACT_APP_PASSWORD_PUBLICKEY:'04819CF427F9150FEEBD91E8D2346F203FC47312D212022A967D8372EA30B9581CCEEFCE2670BDDAF2E8DA1620EA73948126078ED9FF9773AA3A94EE6C80035A18', - - //当前环境 - START_ENV:'PROD', - - //询价查看报价跳转地址 - // REACT_APP_XUNJIA_REDIRECT: 'https://60.10.26.178/provider', - //询价-查看报价详情-虚拟用户uid - REACT_APP_XUNJIA_UID: 'admin_entrance', - //智慧客服用户中心地址 - REACT_APP_CUSTOMERSERVICE_USERCENTER: 'https://uscm.chinaunicom.cn:18023/auth/oauth/authorize?response_type=code', - - //智慧客服client_id - REACT_APP_CUSTOMERSERVICE_CLIENT_ID: 'COsHJydx', - - //智慧客服地址 - REACT_APP_CUSTOMERSERVICE_REDIRECT: 'https://uscm.chinaunicom.cn:18011', - // //智慧客服ws地址 - // REACT_APP_CUSTOMERSERVICE_WS_REDIRECT: 'ws://uscm.unicom.local:18022/api/api/biz-customer-service', - - // //智慧客服文档中心查看图片地址 - // REACT_APP_CUSTOMERSERVICE_DOC_REDIRECT: 'http://cos.xx-pbc.cos.tg.unicom.local/349553515466:mall/', - - // //智慧客服加密公钥私钥ciphercode - // REACT_APP_CUSTOMERSERVICE_PUBLICKEY : '0428D625CEEB71CE823BD7D78DFEE7B122F2DA5C4D21E32253AD684D0FE21810394A799639C0CDFBFEB535A1DFD6A366A637E582CE0B1466A5FE7858841135DE6B', - // REACT_APP_CUSTOMERSERVICE_PRIVATEKEY : '4F7144028D4DCF88FA50F0E2B3FFDDCF63BBE17D1700537DCE037687D3AA3DA7', - // REACT_APP_CUSTOMERSERVICE_CIPHERCODE : 1, + UPLOAD_URL: '/upload', + REQUEST_BASE: '/api', }, }); diff --git a/config/config.sim.ts b/config/config.sim.ts deleted file mode 100644 index 43156ec..0000000 --- a/config/config.sim.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { defineConfig } from 'umi'; -import proxy from './proxy'; -export default defineConfig({ - proxy: proxy['sim'], - define: { - //商城2.0跳转地址 - REACT_APP_MALL_V2_URL: 'http://111.198.162.67/zglt/index_hzf.jsp?id=dev', - - //各系统跳转参数 - REACT_APP_CLIENT_KEY: 'KgPEkttG', - // REACT_APP_CLIENT_SECRET: 'ae5bdb183c502355d2055b3de73300aa73cbfdf3', - - //密码加密参数 - REACT_APP_PASSWORD_CIPHERMODE: '1', - REACT_APP_PASSWORD_PUBLICKEY: '0428D625CEEB71CE823BD7D78DFEE7B122F2DA5C4D21E32253AD684D0FE21810394A799639C0CDFBFEB535A1DFD6A366A637E582CE0B1466A5FE7858841135DE6B', - - //当前环境 - START_ENV: 'sim', - - //询价查看报价跳转地址 - // REACT_APP_XUNJIA_REDIRECT: 'http://10.0.204.215:8080/provider', - //询价-查看报价详情-虚拟用户uid - REACT_APP_XUNJIA_UID: 'admin_entrance', - //智慧客服用户中心地址 - REACT_APP_CUSTOMERSERVICE_USERCENTER: 'http://10.242.31.158:8100/auth/oauth/authorize?response_type=code', - - //智慧客服client_id - REACT_APP_CUSTOMERSERVICE_CLIENT_ID: 'COsHJydx', - - //智慧客服地址 - REACT_APP_CUSTOMERSERVICE_REDIRECT: 'http://10.242.31.158:8632', - // //智慧客服ws地址 - // REACT_APP_CUSTOMERSERVICE_WS_REDIRECT: 'ws://10.242.31.54:18022/api/api/biz-customer-service', - - // //智慧客服文档中心查看图片地址 - // REACT_APP_CUSTOMERSERVICE_DOC_REDIRECT: 'http://cos.xx-pbc.cos.tg.unicom.local/349553515466:mall/', - - // //智慧客服加密公钥私钥ciphercode - // REACT_APP_CUSTOMERSERVICE_PUBLICKEY : '0428D625CEEB71CE823BD7D78DFEE7B122F2DA5C4D21E32253AD684D0FE21810394A799639C0CDFBFEB535A1DFD6A366A637E582CE0B1466A5FE7858841135DE6B', - // REACT_APP_CUSTOMERSERVICE_PRIVATEKEY : '4F7144028D4DCF88FA50F0E2B3FFDDCF63BBE17D1700537DCE037687D3AA3DA7', - // REACT_APP_CUSTOMERSERVICE_CIPHERCODE : 1, - }, -}); diff --git a/config/config.ts b/config/config.ts index 22ab690..746ade6 100644 --- a/config/config.ts +++ b/config/config.ts @@ -1,8 +1,6 @@ import { defineConfig } from 'umi'; import defaultSettings,{antdTheme} from './defaultSettings'; -// import proxy from './proxy'; import PageRoutes from './router.config' -const { REACT_APP_ENV } = process.env; export default defineConfig({ hash: true, antd: { diff --git a/config/proxy.ts b/config/proxy.ts index dea0872..e6188de 100644 --- a/config/proxy.ts +++ b/config/proxy.ts @@ -1,96 +1,20 @@ export default { dev: { - // '/api/wfap/v1/audit/bill/find/by/procid': { - // target: 'http://10.242.37.148:8891/',//审批单 dev环境自动审批,暂时用不到 - // changeOrigin: true, - // pathRewrite: { '^': '' }, - // }, '/api': { - // target: 'http://10.242.37.148:18022',//连接天宫的ng - target: 'http://10.0.0.10:18013',//连接天宫的ng + // target: 'http://10.242.37.148:18022',// + target: 'http://10.0.0.10:18013',// changeOrigin: true, pathRewrite: { '^/api': '' }, }, - }, - UAT: { - '/api/core-service-ebtp-userinfo': { - target: 'http://localhost:18023', + '/upload': { + target: 'http://10.0.0.10:18013',// changeOrigin: true, - pathRewrite: { '/api/core-service-ebtp-userinfo': '' }, - }, - '/api/biz-service-ebtp-bid': { - target: 'http://localhost:18003', - changeOrigin: true, - pathRewrite: { 'biz-service-ebtp-bid': '' }, - }, - '/api/biz-service-ebtp-extend': { - target: 'http://localhost:18018', - changeOrigin: true, - pathRewrite: { '/api/biz-service-ebtp-extend': '' }, - }, - '/api/biz-service-ebtp-project': { - target: 'http://localhost:18012', - changeOrigin: true, - pathRewrite: { '/api/biz-service-ebtp-project': '' }, - }, - '/api/sys-manager-ebtp-project': { - target: 'http://localhost:18030', - changeOrigin: true, - pathRewrite: { '/api/sys-manager-ebtp-project': '' }, - }, - '/api/biz-service-ebtp-rsms': { - target: 'http://localhost:18014', - changeOrigin: true, - pathRewrite: { '/api/biz-service-ebtp-rsms': '' }, - }, - '/api/biz-service-ebtp-agency': { - target: 'http://localhost:18099', - changeOrigin: true, - pathRewrite: { '/api/biz-service-ebtp-agency': '' }, - }, - '/api/biz-supplier-manage': { - target: 'http://localhost:18096', - changeOrigin: true, - pathRewrite: { '/api/biz-supplier-manage': '' }, - } - - // '/api/wfap/v1/audit/bill/find/by/procid': { - // target: 'http://10.242.31.158:8891/',//审批单 uat环境自动审批,暂时用不到 - // changeOrigin: true, - // pathRewrite: { '^': '' }, - // }, - // '/living/api/*': { - // // target: 'https://ai.cubigdata.cn:5001',//连接天宫的ng - // target: 'http://localhost:3000',//连接天宫的ng - // changeOrigin: true, - // pathRewrite: { '/living/api': '' }, - // }, - // '/api/*': { - // target: 'http://10.242.31.158:18022',//连接天宫的ng - // changeOrigin: true, - // pathRewrite: { '^': '' }, - // }, - // '/doc/v1.0/*': { - // target: 'http://10.242.31.158:8806',//连接天宫的ng - // changeOrigin: true, - // pathRewrite: { '^': '' }, - // }, - }, - sim: { - // '/api/wfap/v1/audit/bill/find/by/procid': { - // target: 'http://10.242.31.158:8891/',//审批单 uat环境自动审批,暂时用不到 - // changeOrigin: true, - // pathRewrite: { '^': '' }, - // }, - '/api/*': { - target: 'http://10.242.31.54:18022',//连接天宫的ng - changeOrigin: true, - pathRewrite: { '^': '' }, + pathRewrite: { '^/upload': '' }, }, }, prod: { '/api/*': { - target: 'http://uscm.unicom.local:18022',//连接天宫的ng + target: 'http://uscm.unicom.local:18022',// changeOrigin: true, pathRewrite: { '^': '' }, }, diff --git a/src/components/FileUpload/FileUpload.less b/src/components/FileUpload/FileUpload.less new file mode 100644 index 0000000..413983a --- /dev/null +++ b/src/components/FileUpload/FileUpload.less @@ -0,0 +1,51 @@ +.file-upload-wrapper { + .ant-upload-list { + margin-top: 8px; + } + + .ant-upload-list-item-name { + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + } + + .ant-upload-select { + margin-bottom: 8px; + } + + .file-upload-tip { + margin-top: 8px; + color: rgba(0, 0, 0, 0.45); + font-size: 12px; + line-height: 1.5; + + span { + cursor: help; + text-decoration: underline dotted; + } + } + + .ant-upload-drag { + padding: 16px; + + .ant-upload-drag-icon { + margin-bottom: 8px; + + .anticon { + color: #40a9ff; + font-size: 48px; + } + } + + .ant-upload-text { + margin: 0 0 4px; + color: rgba(0, 0, 0, 0.85); + font-size: 16px; + } + + .ant-upload-hint { + color: rgba(0, 0, 0, 0.45); + font-size: 14px; + } + } +} diff --git a/src/components/FileUpload/FileUpload.tsx b/src/components/FileUpload/FileUpload.tsx new file mode 100644 index 0000000..92b1a13 --- /dev/null +++ b/src/components/FileUpload/FileUpload.tsx @@ -0,0 +1,226 @@ +import React, { useState, useEffect } from 'react'; +import { Upload, Button, message, Tooltip } from 'antd'; +import { UploadOutlined, InboxOutlined } from '@ant-design/icons'; +import type { UploadFile, UploadProps } from 'antd/es/upload/interface'; +import { validateFileSize } from '@/utils/utils'; +import { useIntl } from 'umi'; +import './FileUpload.less'; + +export interface FileUploadProps { + value?: UploadFile[]; + onChange?: (fileList: UploadFile[]) => void; + maxCount?: number; + maxSize?: number; + allowedTypes?: string[]; + listType?: 'text' | 'picture' | 'picture-card'; + buttonText?: string; + disabled?: boolean; + accept?: string; + showUploadList?: + | boolean + | { showPreviewIcon?: boolean; showRemoveIcon?: boolean; showDownloadIcon?: boolean }; + isDragger?: boolean; + tip?: string; + action?: string; +} + +// 上传接口返回数据格式 +interface UploadResponseData { + fileName: string; + fileSize: string; + filePath: string; + fileType: string; + url: string; +} + +// API响应格式 +interface ApiResponse { + code: number; + success: boolean; + message: string; + data: T; +} + +const FileUpload: React.FC = ({ + value, + onChange, + maxCount = 1, + maxSize = 5, + allowedTypes = ['*'], + listType = 'text', + buttonText, + disabled = false, + accept, + showUploadList = true, + isDragger = false, + tip, + action, +}) => { + const actionUrl = action ? `${UPLOAD_URL}${action}` : `${UPLOAD_URL}/api/files/upload`; + const intl = useIntl(); + const [fileList, setFileList] = useState([]); + + // 监听value变化 + useEffect(() => { + + // 处理字符串URL值(这是关键修复) + if (typeof value === 'string' && value) { + const file: Partial = { + uid: '-1', + name: 'image.jpg', + status: 'done', + url: value, + size: 0, + type: 'image/jpeg', + }; + setFileList([file as UploadFile]); + return; + } + + // 处理空值 + if (!value || (Array.isArray(value) && value.length === 0)) { + setFileList([]); + return; + } + + // 处理正常的文件数组 + if (Array.isArray(value)) { + setFileList(value); + return; + } + + // 处理对象格式 + if (typeof value === 'object') { + // 处理fileList属性 + if ('fileList' in value && Array.isArray((value as any).fileList)) { + const files = (value as { fileList: UploadFile[] }).fileList; + setFileList(files); + return; + } + + // 尝试作为单个文件对象处理 + if ('uid' in value && 'status' in value) { + setFileList([value as unknown as UploadFile]); + return; + } + + console.warn('FileUpload: Unrecognized object format', value); + setFileList([]); + return; + } + + // 无法识别的格式 + console.warn('FileUpload component: unrecognized value format:', value); + setFileList([]); + }, [value]); + + const defaultButtonText = intl.formatMessage({ + id: 'component.fileUpload.buttonText', + defaultMessage: '上传文件', + }); + + // 处理上传接口返回的数据格式 + const getFileUrl = (file: UploadFile) => { + // 如果response是API的标准响应格式 + if (file.response && typeof file.response === 'object') { + // 首先检查是否是标准API响应格式 + if ('success' in file.response && 'data' in file.response) { + const response = file.response as ApiResponse; + if (response.success && response.data) { + return response.data.url; + } + } + // 如果response直接包含url属性 + else if ('url' in file.response) { + return (file.response as UploadResponseData).url; + } + } + + // 回退到文件的url或thumbUrl + return file.url || file.thumbUrl; + }; + + const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => { + const filteredList = newFileList.filter((file) => file.status !== 'error'); + + // 为每个文件添加正确的url + const processedList = filteredList.map(file => { + if (file.status === 'done' && file.response && !file.url) { + return { + ...file, + url: getFileUrl(file) + }; + } + return file; + }); + + setFileList(processedList); + + if (onChange) { + onChange(processedList); + } + }; + + const beforeUpload = (file: File) => { + return validateFileSize(file, maxSize, allowedTypes); + }; + + const UploadComponent = isDragger ? Upload.Dragger : Upload; + + const uploadProps = { + fileList, + onChange: handleChange, + beforeUpload, + listType, + maxCount, + disabled, + accept, + showUploadList, + action: actionUrl, + }; + + const renderUploadButton = () => { + if (fileList.length >= maxCount) { + return null; + } + + if (isDragger) { + return ( + <> +

+ +

+

+ {buttonText || + intl.formatMessage({ + id: 'component.fileUpload.dragText', + defaultMessage: '点击或拖拽文件到此区域上传', + })} +

+ {tip &&

{tip}

} + + ); + } + + return ( + + ); + }; + + return ( +
+ {renderUploadButton()} + {!isDragger && tip && ( +
+ + {tip} + +
+ )} +
+ ); +}; + +export default FileUpload; diff --git a/src/components/FileUpload/README.md b/src/components/FileUpload/README.md new file mode 100644 index 0000000..6b44677 --- /dev/null +++ b/src/components/FileUpload/README.md @@ -0,0 +1,127 @@ +# FileUpload 文件上传组件 + +基于Ant Design Upload组件封装的文件上传组件,支持文件类型和大小校验。 + +## 组件特性 + +- 支持文件类型校验 +- 支持文件大小限制 +- 支持拖拽上传 +- 支持上传多个文件 +- 支持国际化 +- 支持自定义提示信息 +- 支持表单受控模式 + +## 使用示例 + +### 基本使用 + +```tsx +import React from 'react'; +import { Form } from 'antd'; +import FileUpload from '@/components/FileUpload'; + +const App: React.FC = () => { + const [form] = Form.useForm(); + + return ( +
+ + + +
+ ); +}; + +export default App; +``` + +### 拖拽上传 + +```tsx +import React from 'react'; +import { Form } from 'antd'; +import FileUpload from '@/components/FileUpload'; +import { useIntl } from 'umi'; + +const App: React.FC = () => { + const [form] = Form.useForm(); + const intl = useIntl(); + + const fileTip = intl.formatMessage( + { id: 'component.fileUpload.fileTypeTip' }, + { types: 'PDF, Word' } + ); + + return ( +
+ + + +
+ ); +}; + +export default App; +``` + +### 图片上传 + +```tsx +import React from 'react'; +import { Form } from 'antd'; +import FileUpload from '@/components/FileUpload'; + +const App: React.FC = () => { + const [form] = Form.useForm(); + + return ( +
+ + + +
+ ); +}; + +export default App; +``` + +## API + +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| value | 文件列表 | UploadFile[] | [] | +| onChange | 文件列表改变时的回调 | (fileList: UploadFile[]) => void | - | +| maxCount | 最大上传数量 | number | 1 | +| maxSize | 文件大小限制(MB) | number | 5 | +| allowedTypes | 允许的文件类型 | string[] | ['*'] | +| listType | 上传列表的样式 | 'text' \| 'picture' \| 'picture-card' | 'text' | +| buttonText | 上传按钮文字 | string | '上传文件' | +| disabled | 是否禁用 | boolean | false | +| accept | 接受的文件类型 | string | - | +| showUploadList | 是否展示文件列表 | boolean \| { showPreviewIcon?: boolean; showRemoveIcon?: boolean; showDownloadIcon?: boolean } | true | +| isDragger | 是否启用拖拽上传 | boolean | false | +| tip | 提示文字 | string | - | diff --git a/src/components/FileUpload/index.ts b/src/components/FileUpload/index.ts new file mode 100644 index 0000000..9292803 --- /dev/null +++ b/src/components/FileUpload/index.ts @@ -0,0 +1,4 @@ +import FileUpload from './FileUpload'; + +export default FileUpload; +export type { FileUploadProps } from './FileUpload'; diff --git a/src/dicts/friendLinkDict.ts b/src/dicts/friendLinkDict.ts index 25a3838..cdf6121 100644 --- a/src/dicts/friendLinkDict.ts +++ b/src/dicts/friendLinkDict.ts @@ -1,5 +1,9 @@ import { useIntl } from 'umi'; +export enum LinkStatusMap { + ENABLED = '0', + DISABLED = '1', +} export const useFriendLinkDict = () => { const intl = useIntl(); @@ -7,11 +11,11 @@ export const useFriendLinkDict = () => { const linkStatusOptions = [ { label: intl.formatMessage({ id: 'friendLink.enabled' }), - value: '1', + value: LinkStatusMap.ENABLED, }, { label: intl.formatMessage({ id: 'friendLink.disabled' }), - value: '0', + value: LinkStatusMap.DISABLED, }, ]; diff --git a/src/locales/en-US/common.ts b/src/locales/en-US/common.ts index 24b7896..d879327 100644 --- a/src/locales/en-US/common.ts +++ b/src/locales/en-US/common.ts @@ -31,4 +31,13 @@ export default { // Buttons "common.confirm": "Confirm", "common.cancel": "Cancel", + + // FileUpload component + 'component.fileUpload.buttonText': 'Upload File', + 'component.fileUpload.dragText': 'Click or drag file to this area to upload', + 'component.fileUpload.fileTypeTip': 'Supported file types: {types}', + 'component.fileUpload.fileSizeTip': 'File size should not exceed {size}MB', + 'component.fileUpload.uploadFailed': 'Upload failed', + 'component.fileUpload.parseError': 'Failed to parse response', + 'component.fileUpload.networkError': 'Network error', }; diff --git a/src/locales/en-US/friendLink.ts b/src/locales/en-US/friendLink.ts index 93adb01..2a0cc4f 100644 --- a/src/locales/en-US/friendLink.ts +++ b/src/locales/en-US/friendLink.ts @@ -51,6 +51,7 @@ export default { 'friendLink.enable.confirm.content': 'Are you sure you want to enable this link?', 'friendLink.disable.confirm.title': 'Disable Confirmation', 'friendLink.disable.confirm.content': 'Are you sure you want to disable this link?', + 'friendLink.detail.failed': 'Failed to get link details', // Friendly Link Category Management 'friendLink.category.title': 'Friendly Link Category Management', diff --git a/src/locales/en-US/helpManage.ts b/src/locales/en-US/helpManage.ts index 904e6b4..77d5079 100644 --- a/src/locales/en-US/helpManage.ts +++ b/src/locales/en-US/helpManage.ts @@ -22,6 +22,7 @@ export default { 'helpManage.cancel': 'Cancel', 'helpManage.confirmDelete': 'Are you sure you want to delete this help item?', 'helpManage.batchDelete': 'Are you sure you want to delete the selected help items?', + 'helpManage.batchDeleteButton': 'Batch Delete', 'helpManage.confirmPublish': 'Are you sure you want to publish this help item?', 'helpManage.confirmUnpublish': 'Are you sure you want to unpublish this help item?', 'helpManage.confirmTop': 'Are you sure you want to top this help item?', diff --git a/src/locales/en-US/policyManage.ts b/src/locales/en-US/policyManage.ts index 203ddfc..108f399 100644 --- a/src/locales/en-US/policyManage.ts +++ b/src/locales/en-US/policyManage.ts @@ -23,9 +23,10 @@ export default { 'policyManage.confirmDeleteBatch': 'Are you sure you want to delete the selected policies?', 'policyManage.confirmPublish': 'Are you sure you want to publish this policy?', 'policyManage.confirmUnpublish': 'Are you sure you want to unpublish this policy?', + 'policyManage.batchDelete': 'Batch Delete', 'policyManage.deleteSuccess': 'Delete successful', 'policyManage.deleteFailed': 'Delete failed', - 'policyManage.batchDeleteSuccess': 'Delete successful', + 'policyManage.batchDeleteSuccess': 'Batch delete successful', 'policyManage.batchDeletePartialFailed': 'Some deletions failed, please refresh and try again', 'policyManage.batchDeleteFailed': 'Batch delete failed', 'policyManage.publishSuccess': 'Publish successful', diff --git a/src/locales/en-US/userQuestion.ts b/src/locales/en-US/userQuestion.ts index 71bed36..6e8be36 100644 --- a/src/locales/en-US/userQuestion.ts +++ b/src/locales/en-US/userQuestion.ts @@ -62,7 +62,7 @@ export default { 'readQuestion.search': 'Search', 'readQuestion.reset': 'Reset', 'readQuestion.confirmDelete': 'Are you sure you want to delete this question?', - 'readQuestion.batchDelete': 'Delete Selected', + 'readQuestion.batchDelete': 'Batch Delete', 'readQuestion.viewDetails': 'View Question Details', 'readQuestion.questionDetails': 'Question Details', 'readQuestion.answerDetails': 'Reply Details', @@ -103,7 +103,7 @@ export default { 'unreadQuestion.search': 'Search', 'unreadQuestion.reset': 'Reset', 'unreadQuestion.confirmDelete': 'Are you sure you want to delete this question?', - 'unreadQuestion.batchDelete': 'Delete Selected', + 'unreadQuestion.batchDelete': 'Batch Delete', 'unreadQuestion.replyModal': 'Reply to Question', 'unreadQuestion.questionContent': 'Question Content', 'unreadQuestion.answerContent': 'Reply Content', diff --git a/src/locales/zh-CN/common.ts b/src/locales/zh-CN/common.ts index b6b6afc..1df02a3 100644 --- a/src/locales/zh-CN/common.ts +++ b/src/locales/zh-CN/common.ts @@ -31,4 +31,13 @@ export default { // 按钮 "common.confirm": "确定", "common.cancel": "取消", + + // FileUpload组件 + 'component.fileUpload.buttonText': '上传文件', + 'component.fileUpload.dragText': '点击或拖拽文件到此区域上传', + 'component.fileUpload.fileTypeTip': '支持的文件类型:{types}', + 'component.fileUpload.fileSizeTip': '文件大小不超过{size}MB', + 'component.fileUpload.uploadFailed': '上传失败', + 'component.fileUpload.parseError': '解析响应失败', + 'component.fileUpload.networkError': '网络错误', }; diff --git a/src/locales/zh-CN/friendLink.ts b/src/locales/zh-CN/friendLink.ts index 97b09b6..ff4de1b 100644 --- a/src/locales/zh-CN/friendLink.ts +++ b/src/locales/zh-CN/friendLink.ts @@ -51,6 +51,7 @@ export default { 'friendLink.enable.confirm.content': '确定要启用该友情链接吗?', 'friendLink.disable.confirm.title': '禁用确认', 'friendLink.disable.confirm.content': '确定要禁用该友情链接吗?', + 'friendLink.detail.failed': '获取友情链接详情失败', // 友情链接分类管理 'friendLink.category.title': '友情链接分类管理', diff --git a/src/pages/friendLinkManage/friendLinkManage.tsx b/src/pages/friendLinkManage/friendLinkManage.tsx index 201f1cc..b30bde2 100644 --- a/src/pages/friendLinkManage/friendLinkManage.tsx +++ b/src/pages/friendLinkManage/friendLinkManage.tsx @@ -1,12 +1,7 @@ import React, { useState, useEffect } from 'react'; import { useIntl } from 'umi'; -import { Table, Button, Modal, Form, Input, Space, message, Upload, Tag, TreeSelect } from 'antd'; -import { - PlusOutlined, - DeleteOutlined, - ExclamationCircleOutlined, - UploadOutlined, -} from '@ant-design/icons'; +import { Table, Button, Modal, Form, Input, Space, message, Tag, TreeSelect } from 'antd'; +import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons'; import type { UploadFile } from 'antd/es/upload/interface'; import { getLinkList, @@ -16,8 +11,10 @@ import { enableLink, disableLink, getAllCategories, + getLinkDetail, } from '@/servers/api/friendLink'; -import { useFriendLinkDict } from '@/dicts/friendLinkDict'; +import { LinkStatusMap } from '@/dicts/friendLinkDict'; +import FileUpload from '@/components/FileUpload'; import './friendLinkManage.less'; const FriendLinkManage: React.FC = () => { @@ -35,7 +32,6 @@ const FriendLinkManage: React.FC = () => { total: 0, }); const [form] = Form.useForm(); - const [fileList, setFileList] = useState([]); const [categoryMap, setCategoryMap] = useState>({}); // 获取友情链接列表 @@ -50,12 +46,7 @@ const FriendLinkManage: React.FC = () => { }); if (res.data && res.success) { - // 处理分类名称 - const links = res.data.records.map((item: API.LinkItem) => ({ - ...item, - categoryName: categoryMap[item.classificationId] || '', - })); - setLinkData(links); + setLinkData(res.data.records); setPagination({ current: res.data.current, pageSize: res.data.size, @@ -74,28 +65,7 @@ const FriendLinkManage: React.FC = () => { try { const res = await getAllCategories(); if (res.data && res.success) { - const flattenCategories = ( - categories: API.CategoryType[], - result: API.CategoryType[] = [], - ): API.CategoryType[] => { - categories.forEach((category) => { - result.push(category); - if (category.children && category.children.length > 0) { - flattenCategories(category.children, result); - } - }); - return result; - }; - - const flatCategories = flattenCategories(res.data); setCategoryData(res.data); - - // 创建分类ID到名称的映射 - const categoryNameMap: Record = {}; - flatCategories.forEach((category) => { - categoryNameMap[category.id] = category.name; - }); - setCategoryMap(categoryNameMap); } } catch (error) { console.error('Failed to fetch category list', error); @@ -119,7 +89,6 @@ const FriendLinkManage: React.FC = () => { setIsEdit(false); setCurrentId(''); form.resetFields(); - setFileList([]); form.setFieldsValue({ orderBy: 1, }); @@ -132,30 +101,34 @@ const FriendLinkManage: React.FC = () => { setCurrentId(record.id); setModalVisible(true); - // 设置表单初始值 - form.setFieldsValue({ - name: record.name, - nameEn: record.nameEn, - url: record.url, - orderBy: parseInt(record.orderBy), - classificationId: record.classificationId, - }); + // 清空表单 + form.resetFields(); - // 设置上传文件列表 - if (record.thumbnail) { - setFileList([ - { - uid: '-1', - name: 'thumbnail.png', - status: 'done', - url: record.thumbnail, - type: 'image/png', - size: 0, - } as UploadFile, - ]); - } else { - setFileList([]); - } + // 调用详情接口获取完整数据 + getLinkDetail(record.id) + .then((res: API.Response) => { + if (res.success && res.data) { + const detail = res.data; + console.log('Detail data:', detail); + + // 设置表单初始值,包括缩略图URL + form.setFieldsValue({ + name: detail.name, + nameEn: detail.nameEn, + url: detail.url, + orderBy: parseInt(detail.orderBy), + classificationId: detail.classificationId, + // 设置为URL字符串,组件会自动处理 + thumbnail: detail.thumbnail, + }); + } else { + message.error(intl.formatMessage({ id: 'friendLink.detail.failed' })); + } + }) + .catch((error: any) => { + console.error('Failed to get link detail', error); + message.error(intl.formatMessage({ id: 'friendLink.detail.failed' })); + }); }; // 处理删除 @@ -220,22 +193,24 @@ const FriendLinkManage: React.FC = () => { // 添加确认对话框 Modal.confirm({ title: intl.formatMessage({ - id: record.status === '1' - ? 'friendLink.disable.confirm.title' - : 'friendLink.enable.confirm.title' + id: + record.status === LinkStatusMap.ENABLED + ? 'friendLink.disable.confirm.title' + : 'friendLink.enable.confirm.title', }), icon: , content: intl.formatMessage({ - id: record.status === '1' - ? 'friendLink.disable.confirm.content' - : 'friendLink.enable.confirm.content' + id: + record.status === LinkStatusMap.ENABLED + ? 'friendLink.disable.confirm.content' + : 'friendLink.enable.confirm.content', }), okText: intl.formatMessage({ id: 'common.confirm' }), cancelText: intl.formatMessage({ id: 'common.cancel' }), onOk: async () => { try { let res; - if (record.status === '1') { + if (record.status === LinkStatusMap.ENABLED) { // 当前启用,需要禁用 res = await disableLink(record.id); } else { @@ -245,7 +220,7 @@ const FriendLinkManage: React.FC = () => { if (res.success) { message.success( - record.status === '1' + record.status === LinkStatusMap.ENABLED ? intl.formatMessage({ id: 'friendLink.disable.success' }) : intl.formatMessage({ id: 'friendLink.enable.success' }), ); @@ -261,19 +236,15 @@ const FriendLinkManage: React.FC = () => { // 处理表单提交 const handleModalSubmit = () => { form.validateFields().then(async (values) => { + console.log('Form values:', values); try { - // 确保thumbnail有值 - if (fileList.length === 0) { - message.error(intl.formatMessage({ id: 'friendLink.form.thumbnail.required' })); - return; - } - - // 准备提交数据 + // 现在thumbnail字段已经直接是URL字符串,不需要额外处理 const formData = { ...values, - thumbnail: fileList[0].url || fileList[0].thumbUrl || '', }; + console.log('Submitting form data:', formData); + let res; if (isEdit) { // 编辑模式 @@ -301,11 +272,6 @@ const FriendLinkManage: React.FC = () => { }); }; - // 处理文件上传变化 - const handleFileChange = (info: { fileList: UploadFile[] }) => { - setFileList(info.fileList); - }; - // 表格列定义 const columns = [ { @@ -345,7 +311,7 @@ const FriendLinkManage: React.FC = () => { dataIndex: 'status', key: 'status', render: (text: string) => - text === '1' ? ( + text === LinkStatusMap.ENABLED ? ( {intl.formatMessage({ id: 'friendLink.enabled' })} ) : ( {intl.formatMessage({ id: 'friendLink.disabled' })} @@ -361,7 +327,7 @@ const FriendLinkManage: React.FC = () => { {intl.formatMessage({ id: 'friendLink.edit' })} @@ -381,26 +347,28 @@ const FriendLinkManage: React.FC = () => { return (
-
+
- - {selectedRowKeys.length > 0 && ( - - {intl.formatMessage( - { id: 'friendLink.selectedCount' }, - { count: selectedRowKeys.length }, - )} - - )} +
+ + {selectedRowKeys.length > 0 && ( + + {intl.formatMessage( + { id: 'friendLink.selectedCount' }, + { count: selectedRowKeys.length }, + )} + + )} +
@@ -496,18 +464,30 @@ const FriendLinkManage: React.FC = () => { - - { + return e[0].url; + }} + rules={[ + { + required: true, + message: intl.formatMessage({ id: 'friendLink.form.thumbnail.required' }), + }, + ]} + > + false} - > - - + maxSize={2} + allowedTypes={['jpg', 'png']} + listType="picture" + tip={intl.formatMessage( + { id: 'component.fileUpload.fileTypeTip' }, + { types: 'JPG, PNG' }, + )} + buttonText={intl.formatMessage({ id: 'friendLink.form.thumbnail.upload' })} + /> { - console.log(file) const { LIST_IGNORE } = Upload; const isLtMaxSize = file.size / 1024 / 1024 < maxSize; if (!isLtMaxSize) {