diff --git a/apps/web-antd/src/adapter/vxe-table.ts b/apps/web-antd/src/adapter/vxe-table.ts index 777fe23cf..7afff14f0 100644 --- a/apps/web-antd/src/adapter/vxe-table.ts +++ b/apps/web-antd/src/adapter/vxe-table.ts @@ -12,6 +12,7 @@ import { } from '@vben/plugins/vxe-table'; import { erpNumberFormatter, + formatPast2, formatToFractionDigit, isFunction, isString, @@ -296,32 +297,7 @@ setupVbenVxeTable({ vxeUI.formats.add('formatPast2', { tableCellFormatMethod({ cellValue }) { - if (cellValue === null || cellValue === undefined) { - return ''; - } - // 定义时间单位常量,便于维护 - const SECOND = 1000; - const MINUTE = 60 * SECOND; - const HOUR = 60 * MINUTE; - const DAY = 24 * HOUR; - - // 计算各时间单位 - const day = Math.floor(cellValue / DAY); - const hour = Math.floor((cellValue % DAY) / HOUR); - const minute = Math.floor((cellValue % HOUR) / MINUTE); - const second = Math.floor((cellValue % MINUTE) / SECOND); - - // 根据时间长短返回不同格式 - if (day > 0) { - return `${day} 天${hour} 小时 ${minute} 分钟`; - } - if (hour > 0) { - return `${hour} 小时 ${minute} 分钟`; - } - if (minute > 0) { - return `${minute} 分钟`; - } - return second > 0 ? `${second} 秒` : `${0} 秒`; + return formatPast2(cellValue); }, }); diff --git a/apps/web-antd/src/api/ai/write/index.ts b/apps/web-antd/src/api/ai/write/index.ts index 1f98ca3d9..cd9413e12 100644 --- a/apps/web-antd/src/api/ai/write/index.ts +++ b/apps/web-antd/src/api/ai/write/index.ts @@ -1,6 +1,6 @@ import type { PageParam, PageResult } from '@vben/request'; -import type { AiWriteTypeEnum } from '#/utils/constants'; +import type { AiWriteTypeEnum } from '#/utils'; import { useAppConfig } from '@vben/hooks'; import { fetchEventSource } from '@vben/request'; diff --git a/apps/web-antd/src/utils/download.ts b/apps/web-antd/src/utils/download.ts deleted file mode 100644 index 1317aa62e..000000000 --- a/apps/web-antd/src/utils/download.ts +++ /dev/null @@ -1,98 +0,0 @@ -const download0 = (data: Blob, fileName: string, mineType: string) => { - // 创建 blob - const blob = new Blob([data], { type: mineType }); - // 创建 href 超链接,点击进行下载 - window.URL = window.URL || window.webkitURL; - const href = URL.createObjectURL(blob); - const downA = document.createElement('a'); - downA.href = href; - downA.download = fileName; - downA.click(); - // 销毁超连接 - window.URL.revokeObjectURL(href); -}; - -export const download = { - // 下载 Excel 方法 - excel: (data: Blob, fileName: string) => { - download0(data, fileName, 'application/vnd.ms-excel'); - }, - // 下载 Word 方法 - word: (data: Blob, fileName: string) => { - download0(data, fileName, 'application/msword'); - }, - // 下载 Zip 方法 - zip: (data: Blob, fileName: string) => { - download0(data, fileName, 'application/zip'); - }, - // 下载 Html 方法 - html: (data: Blob, fileName: string) => { - download0(data, fileName, 'text/html'); - }, - // 下载 Markdown 方法 - markdown: (data: Blob, fileName: string) => { - download0(data, fileName, 'text/markdown'); - }, - // 下载 Json 方法 - json: (data: Blob, fileName: string) => { - download0(data, fileName, 'application/json'); - }, - // 下载图片(允许跨域) - image: ({ - url, - canvasWidth, - canvasHeight, - drawWithImageSize = true, - }: { - canvasHeight?: number; // 指定画布高度 - canvasWidth?: number; // 指定画布宽度 - drawWithImageSize?: boolean; // 将图片绘制在画布上时带上图片的宽高值, 默认是要带上的 - url: string; - }) => { - const image = new Image(); - // image.setAttribute('crossOrigin', 'anonymous') - image.src = url; - image.addEventListener('load', () => { - const canvas = document.createElement('canvas'); - canvas.width = canvasWidth || image.width; - canvas.height = canvasHeight || image.height; - const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; - ctx?.clearRect(0, 0, canvas.width, canvas.height); - if (drawWithImageSize) { - ctx.drawImage(image, 0, 0, image.width, image.height); - } else { - ctx.drawImage(image, 0, 0); - } - const url = canvas.toDataURL('image/png'); - const a = document.createElement('a'); - a.href = url; - a.download = 'image.png'; - a.click(); - }); - }, - base64ToFile: (base64: any, fileName: string) => { - // 将base64按照 , 进行分割 将前缀 与后续内容分隔开 - const data = base64.split(','); - // 利用正则表达式 从前缀中获取图片的类型信息(image/png、image/jpeg、image/webp等) - const type = data[0].match(/:(.*?);/)[1]; - // 从图片的类型信息中 获取具体的文件格式后缀(png、jpeg、webp) - const suffix = type.split('/')[1]; - // 使用atob()对base64数据进行解码 结果是一个文件数据流 以字符串的格式输出 - const bstr = window.atob(data[1]); - // 获取解码结果字符串的长度 - let n = bstr.length; - // 根据解码结果字符串的长度创建一个等长的整形数字数组 - // 但在创建时 所有元素初始值都为 0 - const u8arr = new Uint8Array(n); - // 将整形数组的每个元素填充为解码结果字符串对应位置字符的UTF-16 编码单元 - while (n--) { - // charCodeAt():获取给定索引处字符对应的 UTF-16 代码单元 - u8arr[n] = bstr.charCodeAt(n); - } - - // 将File文件对象返回给方法的调用者 - return new File([u8arr], `${fileName}.${suffix}`, { - type, - }); - }, -}; diff --git a/apps/web-antd/src/utils/formatTime.ts b/apps/web-antd/src/utils/formatTime.ts deleted file mode 100644 index f51ba8793..000000000 --- a/apps/web-antd/src/utils/formatTime.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { formatDate } from '@vben/utils'; - -/** - * 将时间转换为 `几秒前`、`几分钟前`、`几小时前`、`几天前` - * @param param 当前时间,new Date() 格式或者字符串时间格式 - * @param format 需要转换的时间格式字符串 - * @description param 10秒: 10 * 1000 - * @description param 1分: 60 * 1000 - * @description param 1小时: 60 * 60 * 1000 - * @description param 24小时:60 * 60 * 24 * 1000 - * @description param 3天: 60 * 60* 24 * 1000 * 3 - * @returns 返回拼接后的时间字符串 - */ -export function formatPast( - param: Date | string, - format = 'YYYY-MM-DD HH:mm:ss', -): string { - // 传入格式处理、存储转换值 - let s: number, t: any; - // 获取js 时间戳 - let time: number = Date.now(); - // 是否是对象 - typeof param === 'string' || typeof param === 'object' - ? (t = new Date(param).getTime()) - : (t = param); - // 当前时间戳 - 传入时间戳 - time = Number.parseInt(`${time - t}`); - if (time < 10_000) { - // 10秒内 - return '刚刚'; - } else if (time < 60_000 && time >= 10_000) { - // 超过10秒少于1分钟内 - s = Math.floor(time / 1000); - return `${s}秒前`; - } else if (time < 3_600_000 && time >= 60_000) { - // 超过1分钟少于1小时 - s = Math.floor(time / 60_000); - return `${s}分钟前`; - } else if (time < 86_400_000 && time >= 3_600_000) { - // 超过1小时少于24小时 - s = Math.floor(time / 3_600_000); - return `${s}小时前`; - } else if (time < 259_200_000 && time >= 86_400_000) { - // 超过1天少于3天内 - s = Math.floor(time / 86_400_000); - return `${s}天前`; - } else { - // 超过3天 - const date = - typeof param === 'string' || typeof param === 'object' - ? new Date(param) - : param; - return formatDate(date, format); - } -} - -/** - * 将毫秒,转换成时间字符串。例如说,xx 分钟 - * - * @param ms 毫秒 - * @returns {string} 字符串 - */ -// TODO @xingyu:这个要融合到哪里去 date 么? -export function formatPast2(ms: number): string { - // 定义时间单位常量,便于维护 - const SECOND = 1000; - const MINUTE = 60 * SECOND; - const HOUR = 60 * MINUTE; - const DAY = 24 * HOUR; - - // 计算各时间单位 - const day = Math.floor(ms / DAY); - const hour = Math.floor((ms % DAY) / HOUR); - const minute = Math.floor((ms % HOUR) / MINUTE); - const second = Math.floor((ms % MINUTE) / SECOND); - - // 根据时间长短返回不同格式 - if (day > 0) { - return `${day} 天${hour} 小时 ${minute} 分钟`; - } - if (hour > 0) { - return `${hour} 小时 ${minute} 分钟`; - } - if (minute > 0) { - return `${minute} 分钟`; - } - return second > 0 ? `${second} 秒` : `${0} 秒`; -} - -/** - * @param {Date | number | string} time 需要转换的时间 - * @param {string} fmt 需要转换的格式 如 yyyy-MM-dd、yyyy-MM-dd HH:mm:ss - */ -export function formatTime(time: Date | number | string, fmt: string) { - if (time) { - const date = new Date(time); - const o = { - 'M+': date.getMonth() + 1, - 'd+': date.getDate(), - 'H+': date.getHours(), - 'm+': date.getMinutes(), - 's+': date.getSeconds(), - 'q+': Math.floor((date.getMonth() + 3) / 3), - S: date.getMilliseconds(), - }; - if (/(y+)/.test(fmt)) { - fmt = fmt.replace( - RegExp.$1, - `${date.getFullYear()}`.slice(4 - RegExp.$1.length), - ); - } - for (const k in o) { - if (new RegExp(`(${k})`).test(fmt)) { - fmt = fmt.replace( - RegExp.$1, - RegExp.$1.length === 1 ? o[k] : `00${o[k]}`.slice(`${o[k]}`.length), - ); - } - } - return fmt; - } else { - return ''; - } -} diff --git a/apps/web-antd/src/utils/index.ts b/apps/web-antd/src/utils/index.ts index e7bb80b7c..dcf28573f 100644 --- a/apps/web-antd/src/utils/index.ts +++ b/apps/web-antd/src/utils/index.ts @@ -3,5 +3,3 @@ export * from './dict'; export * from './formCreate'; export * from './rangePickerProps'; export * from './routerHelper'; -export * from './upload'; -export * from './utils'; diff --git a/apps/web-antd/src/utils/utils.ts b/apps/web-antd/src/utils/utils.ts deleted file mode 100644 index d08eb778f..000000000 --- a/apps/web-antd/src/utils/utils.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Created by 芋道源码 - * - * AI 枚举类 - * - * 问题:为什么不放在 src/utils/common-utils.ts 呢? - * 回答:主要 AI 是可选模块,考虑到独立、解耦,所以放在了 /views/ai/utils/common-utils.ts - */ - -/** 判断字符串是否包含中文 */ -export const hasChinese = (str: string) => { - return /[\u4E00-\u9FA5]/.test(str); -}; diff --git a/packages/@core/base/shared/src/utils/download.ts b/packages/@core/base/shared/src/utils/download.ts index 3e85339a2..3c14fa804 100644 --- a/packages/@core/base/shared/src/utils/download.ts +++ b/packages/@core/base/shared/src/utils/download.ts @@ -40,6 +40,44 @@ export async function downloadFileFromUrl({ openWindow(source, { target }); } +/** + * 下载图片(允许跨域) + * @param url - 图片 URL + * @param canvasWidth - 画布宽度 + * @param canvasHeight - 画布高度 + * @param drawWithImageSize - 将图片绘制在画布上时带上图片的宽高值, 默认是要带上的 + * @returns + */ +export function downloadImageByCanvas({ + url, + canvasWidth, + canvasHeight, + drawWithImageSize = true, +}: { + canvasHeight?: number; + canvasWidth?: number; + drawWithImageSize?: boolean; + url: string; +}) { + const image = new Image(); + // image.setAttribute('crossOrigin', 'anonymous') + image.src = url; + image.addEventListener('load', () => { + const canvas = document.createElement('canvas'); + canvas.width = canvasWidth || image.width; + canvas.height = canvasHeight || image.height; + const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; + ctx?.clearRect(0, 0, canvas.width, canvas.height); + if (drawWithImageSize) { + ctx.drawImage(image, 0, 0, image.width, image.height); + } else { + ctx.drawImage(image, 0, 0); + } + const url = canvas.toDataURL('image/png'); + downloadFileFromImageUrl({ source: url, fileName: 'image.png' }); + }); +} + /** * 通过 Base64 下载文件 */ diff --git a/packages/@core/base/shared/src/utils/index.ts b/packages/@core/base/shared/src/utils/index.ts index 8e57292f6..7a22c972d 100644 --- a/packages/@core/base/shared/src/utils/index.ts +++ b/packages/@core/base/shared/src/utils/index.ts @@ -14,6 +14,7 @@ export * from './to'; export * from './tree'; export * from './unique'; export * from './update-css-variables'; +export * from './upload'; export * from './util'; export * from './uuid'; // add by 芋艿:从 vben2.0 复制 export * from './window'; diff --git a/packages/@core/base/shared/src/utils/time.ts b/packages/@core/base/shared/src/utils/time.ts index cc3913c24..b73a211ef 100644 --- a/packages/@core/base/shared/src/utils/time.ts +++ b/packages/@core/base/shared/src/utils/time.ts @@ -1,5 +1,7 @@ import dayjs from 'dayjs'; +import { isEmpty } from '.'; + /** 时间段选择器拓展 */ export function rangePickerExtend() { return { @@ -41,3 +43,134 @@ export function rangePickerExtend() { valueFormat: 'YYYY-MM-DD HH:mm:ss', }; } + +/** + * 将时间转换为 `几秒前`、`几分钟前`、`几小时前`、`几天前` + * @param param 当前时间,new Date() 格式或者字符串时间格式 + * @param format 需要转换的时间格式字符串 + * @description param 10秒: 10 * 1000 + * @description param 1分: 60 * 1000 + * @description param 1小时: 60 * 60 * 1000 + * @description param 24小时:60 * 60 * 24 * 1000 + * @description param 3天: 60 * 60* 24 * 1000 * 3 + * @returns 返回拼接后的时间字符串 + */ +export function formatPast( + param: Date | string, + format = 'YYYY-MM-DD HH:mm:ss', +): string { + // 传入格式处理、存储转换值 + let s: number, t: any; + // 获取js 时间戳 + let time: number = Date.now(); + // 是否是对象 + typeof param === 'string' || typeof param === 'object' + ? (t = new Date(param).getTime()) + : (t = param); + // 当前时间戳 - 传入时间戳 + time = Number.parseInt(`${time - t}`); + if (time < 10_000) { + // 10秒内 + return '刚刚'; + } else if (time < 60_000 && time >= 10_000) { + // 超过10秒少于1分钟内 + s = Math.floor(time / 1000); + return `${s}秒前`; + } else if (time < 3_600_000 && time >= 60_000) { + // 超过1分钟少于1小时 + s = Math.floor(time / 60_000); + return `${s}分钟前`; + } else if (time < 86_400_000 && time >= 3_600_000) { + // 超过1小时少于24小时 + s = Math.floor(time / 3_600_000); + return `${s}小时前`; + } else if (time < 259_200_000 && time >= 86_400_000) { + // 超过1天少于3天内 + s = Math.floor(time / 86_400_000); + return `${s}天前`; + } else { + // 超过3天 + const date = + typeof param === 'string' || typeof param === 'object' + ? new Date(param) + : param; + return dayjs(date).format(format); + } +} + +/** + * 将毫秒,转换成时间字符串。例如说,xx 分钟 + * + * @param ms 毫秒 + * @returns {string} 字符串 + */ +export function formatPast2(ms: number): string { + if (isEmpty(ms)) { + return ''; + } + // 定义时间单位常量,便于维护 + const SECOND = 1000; + const MINUTE = 60 * SECOND; + const HOUR = 60 * MINUTE; + const DAY = 24 * HOUR; + + // 计算各时间单位 + const day = Math.floor(ms / DAY); + const hour = Math.floor((ms % DAY) / HOUR); + const minute = Math.floor((ms % HOUR) / MINUTE); + const second = Math.floor((ms % MINUTE) / SECOND); + + // 根据时间长短返回不同格式 + if (day > 0) { + return `${day} 天${hour} 小时 ${minute} 分钟`; + } + if (hour > 0) { + return `${hour} 小时 ${minute} 分钟`; + } + if (minute > 0) { + return `${minute} 分钟`; + } + return second > 0 ? `${second} 秒` : `${0} 秒`; +} + +/** + * @param {Date | number | string} time 需要转换的时间 + * @param {string} fmt 需要转换的格式 如 yyyy-MM-dd、yyyy-MM-dd HH:mm:ss + */ +export function formatTime(time: Date | number | string, fmt: string) { + if (time) { + const date = new Date(time); + const o = { + 'M+': date.getMonth() + 1, + 'd+': date.getDate(), + 'H+': date.getHours(), + 'm+': date.getMinutes(), + 's+': date.getSeconds(), + 'q+': Math.floor((date.getMonth() + 3) / 3), + S: date.getMilliseconds(), + }; + const yearMatch = fmt.match(/y+/); + if (yearMatch) { + fmt = fmt.replace( + yearMatch[0], + `${date.getFullYear()}`.slice(4 - yearMatch[0].length), + ); + } + for (const k in o) { + const match = fmt.match(new RegExp(`(${k})`)); + if (match) { + fmt = fmt.replace( + match[0], + match[0].length === 1 + ? (o[k as keyof typeof o] as any) + : `00${o[k as keyof typeof o]}`.slice( + `${o[k as keyof typeof o]}`.length, + ), + ); + } + } + return fmt; + } else { + return ''; + } +} diff --git a/apps/web-antd/src/utils/upload.ts b/packages/@core/base/shared/src/utils/upload.ts similarity index 97% rename from apps/web-antd/src/utils/upload.ts rename to packages/@core/base/shared/src/utils/upload.ts index 3c5759569..3a349ed69 100644 --- a/apps/web-antd/src/utils/upload.ts +++ b/packages/@core/base/shared/src/utils/upload.ts @@ -4,9 +4,9 @@ * @param supportedFileTypes 支持的文件类型数组,如 ['PDF', 'DOC', 'DOCX'] * @returns 用于文件上传组件 accept 属性的字符串 */ -export const generateAcceptedFileTypes = ( +export function generateAcceptedFileTypes( supportedFileTypes: string[], -): string => { +): string { const allowedExtensions = supportedFileTypes.map((ext) => ext.toLowerCase()); const mimeTypes: string[] = []; @@ -64,4 +64,4 @@ export const generateAcceptedFileTypes = ( const extensions = allowedExtensions.map((ext) => `.${ext}`); return [...mimeTypes, ...extensions].join(','); -}; +}