From f6e2dc55ffc81c7f5b3589e0226446f982259d08 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 17 Apr 2025 23:30:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20file=20=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=B8=8A=E4=BC=A0=2050%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antd/src/adapter/component/index.ts | 6 + apps/web-antd/src/api/infra/file/index.ts | 9 +- .../src/api/system/oauth2/open/index.ts | 53 +++++ .../src/components/upload/file-upload.vue | 218 ++++++++++++++++++ apps/web-antd/src/components/upload/helper.ts | 18 ++ apps/web-antd/src/components/upload/index.ts | 2 + apps/web-antd/src/components/upload/typing.ts | 6 + .../src/components/upload/use-upload.ts | 61 +++++ packages/@core/base/icons/src/lucide.ts | 1 + packages/locales/src/langs/en-US/ui.json | 9 + packages/locales/src/langs/zh-CN/ui.json | 9 + 11 files changed, 390 insertions(+), 2 deletions(-) create mode 100644 apps/web-antd/src/api/system/oauth2/open/index.ts create mode 100644 apps/web-antd/src/components/upload/file-upload.vue create mode 100644 apps/web-antd/src/components/upload/helper.ts create mode 100644 apps/web-antd/src/components/upload/index.ts create mode 100644 apps/web-antd/src/components/upload/typing.ts create mode 100644 apps/web-antd/src/components/upload/use-upload.ts diff --git a/apps/web-antd/src/adapter/component/index.ts b/apps/web-antd/src/adapter/component/index.ts index 5394c8130..9aa0738f8 100644 --- a/apps/web-antd/src/adapter/component/index.ts +++ b/apps/web-antd/src/adapter/component/index.ts @@ -21,6 +21,8 @@ import { $t } from '@vben/locales'; import { notification } from 'ant-design-vue'; +import { FileUpload, ImageUpload } from '#/components/upload'; + const AutoComplete = defineAsyncComponent( () => import('ant-design-vue/es/auto-complete'), ); @@ -129,6 +131,8 @@ export type ComponentType = | 'TimePicker' | 'TreeSelect' | 'Upload' + | 'FileUpload' + | 'ImageUpload' | BaseFormComponentType; async function initComponentAdapter() { @@ -183,6 +187,8 @@ async function initComponentAdapter() { TimePicker, TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'), Upload, + FileUpload, + ImageUpload, }; // 将组件注册到全局共享状态中 diff --git a/apps/web-antd/src/api/infra/file/index.ts b/apps/web-antd/src/api/infra/file/index.ts index 577b2000d..f9fad1640 100644 --- a/apps/web-antd/src/api/infra/file/index.ts +++ b/apps/web-antd/src/api/infra/file/index.ts @@ -1,5 +1,9 @@ import { requestClient } from '#/api/request'; import type { PageParam, PageResult } from '@vben/request'; +import type { AxiosRequestConfig } from '@vben/request'; + +/** Axios 上传进度事件 */ +export type AxiosProgressEvent = AxiosRequestConfig['onUploadProgress']; export namespace InfraFileApi { /** 文件信息 */ @@ -46,7 +50,8 @@ export function createFile(data: InfraFileApi.InfraFile) { return requestClient.post('/infra/file/create', data); } +// TODO @芋艿:需要 data 自定义个类型; /** 上传文件 */ -export function uploadFile(data: any) { - return requestClient.upload('/infra/file/upload', data); +export function uploadFile(data: any, onUploadProgress?: AxiosProgressEvent) { + return requestClient.upload('/infra/file/upload', data, { onUploadProgress }); } diff --git a/apps/web-antd/src/api/system/oauth2/open/index.ts b/apps/web-antd/src/api/system/oauth2/open/index.ts new file mode 100644 index 000000000..7347001d5 --- /dev/null +++ b/apps/web-antd/src/api/system/oauth2/open/index.ts @@ -0,0 +1,53 @@ +import { requestClient } from '#/api/request'; + +/** OAuth2.0 授权信息响应 */ +export interface OAuth2OpenAuthorizeInfoRespVO { + client: { + name: string; + logo: string; + }; + scopes: { + key: string; + value: boolean; + }[]; +} + +/** 获得授权信息 */ +export function getAuthorize(clientId: string) { + return requestClient.get(`/system/oauth2/authorize?clientId=${clientId}`); +} + +/** 发起授权 */ +export function authorize( + responseType: string, + clientId: string, + redirectUri: string, + state: string, + autoApprove: boolean, + checkedScopes: string[], + uncheckedScopes: string[] +) { + // 构建 scopes + const scopes: Record = {}; + for (const scope of checkedScopes) { + scopes[scope] = true; + } + for (const scope of uncheckedScopes) { + scopes[scope] = false; + } + + // 发起请求 + return requestClient.post('/system/oauth2/authorize', null, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + params: { + response_type: responseType, + client_id: clientId, + redirect_uri: redirectUri, + state: state, + auto_approve: autoApprove, + scope: JSON.stringify(scopes) + } + }); +} diff --git a/apps/web-antd/src/components/upload/file-upload.vue b/apps/web-antd/src/components/upload/file-upload.vue new file mode 100644 index 000000000..04a75b873 --- /dev/null +++ b/apps/web-antd/src/components/upload/file-upload.vue @@ -0,0 +1,218 @@ + + + diff --git a/apps/web-antd/src/components/upload/helper.ts b/apps/web-antd/src/components/upload/helper.ts new file mode 100644 index 000000000..c65125d13 --- /dev/null +++ b/apps/web-antd/src/components/upload/helper.ts @@ -0,0 +1,18 @@ +export function checkFileType(file: File, accepts: string[]) { + let reg; + if (!accepts || accepts.length === 0) { + reg = /.(jpg|jpeg|png|gif|webp)$/i; + } else { + const newTypes = accepts.join('|'); + reg = new RegExp('\\.(' + newTypes + ')$', 'i'); + } + return reg.test(file.name); +} + +export function checkImgType(file: File) { + return isImgTypeByName(file.name); +} + +export function isImgTypeByName(name: string) { + return /\.(jpg|jpeg|png|gif|webp)$/i.test(name); +} diff --git a/apps/web-antd/src/components/upload/index.ts b/apps/web-antd/src/components/upload/index.ts new file mode 100644 index 000000000..a66b2fca6 --- /dev/null +++ b/apps/web-antd/src/components/upload/index.ts @@ -0,0 +1,2 @@ +export { default as FileUpload } from './file-upload.vue'; +export { default as ImageUpload } from './image-upload.vue'; diff --git a/apps/web-antd/src/components/upload/typing.ts b/apps/web-antd/src/components/upload/typing.ts new file mode 100644 index 000000000..c48d5fd69 --- /dev/null +++ b/apps/web-antd/src/components/upload/typing.ts @@ -0,0 +1,6 @@ +export enum UploadResultStatus { + DONE = 'done', + ERROR = 'error', + SUCCESS = 'success', + UPLOADING = 'uploading', +} diff --git a/apps/web-antd/src/components/upload/use-upload.ts b/apps/web-antd/src/components/upload/use-upload.ts new file mode 100644 index 000000000..1aef937db --- /dev/null +++ b/apps/web-antd/src/components/upload/use-upload.ts @@ -0,0 +1,61 @@ +import type { Ref } from 'vue'; + +import { computed, unref } from 'vue'; + +import { $t } from '@vben/locales'; + +export function useUploadType({ + acceptRef, + helpTextRef, + maxNumberRef, + maxSizeRef, +}: { + acceptRef: Ref; + helpTextRef: Ref; + maxNumberRef: Ref; + maxSizeRef: Ref; +}) { + // 文件类型限制 + const getAccept = computed(() => { + const accept = unref(acceptRef); + if (accept && accept.length > 0) { + return accept; + } + return []; + }); + const getStringAccept = computed(() => { + return unref(getAccept) + .map((item) => { + return item.indexOf('/') > 0 || item.startsWith('.') + ? item + : `.${item}`; + }) + .join(','); + }); + + // 支持jpg、jpeg、png格式,不超过2M,最多可选择10张图片,。 + const getHelpText = computed(() => { + const helpText = unref(helpTextRef); + if (helpText) { + return helpText; + } + const helpTexts: string[] = []; + + const accept = unref(acceptRef); + if (accept.length > 0) { + helpTexts.push($t('ui.upload.accept', [accept.join(',')])); + } + + const maxSize = unref(maxSizeRef); + if (maxSize) { + helpTexts.push($t('ui.upload.maxSize', [maxSize])); + } + + const maxNumber = unref(maxNumberRef); + if (maxNumber && maxNumber !== Infinity) { + helpTexts.push($t('ui.upload.maxNumber', [maxNumber])); + } + return helpTexts.join(','); + }); + return { getAccept, getStringAccept, getHelpText }; +} diff --git a/packages/@core/base/icons/src/lucide.ts b/packages/@core/base/icons/src/lucide.ts index a2e6da46a..d6747133b 100644 --- a/packages/@core/base/icons/src/lucide.ts +++ b/packages/@core/base/icons/src/lucide.ts @@ -67,5 +67,6 @@ export { X, Download, Upload, + CloudUpload, History, } from 'lucide-vue-next'; diff --git a/packages/locales/src/langs/en-US/ui.json b/packages/locales/src/langs/en-US/ui.json index 768dd798e..63d9cf955 100644 --- a/packages/locales/src/langs/en-US/ui.json +++ b/packages/locales/src/langs/en-US/ui.json @@ -106,5 +106,14 @@ "backToLogin": "Back to login", "entry": "Enter the system" } + }, + "upload": { + "upload": "Upload", + "accept": "Support {0} format", + "acceptUpload": "Only upload files in {0} format", + "maxSize": "A single file does not exceed {0}MB", + "maxSizeMultiple": "Only upload files up to {0}MB!", + "maxNumber": "Only upload up to {0} files", + "uploadSuccess": "Upload successfully" } } diff --git a/packages/locales/src/langs/zh-CN/ui.json b/packages/locales/src/langs/zh-CN/ui.json index 8bd7f65d7..78e544e21 100644 --- a/packages/locales/src/langs/zh-CN/ui.json +++ b/packages/locales/src/langs/zh-CN/ui.json @@ -106,5 +106,14 @@ "backToLogin": "返回登录", "entry": "进入系统" } + }, + "upload": { + "upload": "上传", + "accept": "支持{0}格式", + "acceptUpload": "只能上传{0}格式文件", + "maxSize": "单个文件不超过{0}MB", + "maxSizeMultiple": "只能上传不超过{0}MB的文件!", + "maxNumber": "最多只能上传{0}个文件", + "uploadSuccess": "上传成功" } }