first commit
This commit is contained in:
19
src/utils/auth.ts
Normal file
19
src/utils/auth.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
const TOKEN_KEY = 'token'
|
||||
|
||||
const isLogin = () => {
|
||||
return !!localStorage.getItem(TOKEN_KEY)
|
||||
}
|
||||
|
||||
const getToken = () => {
|
||||
return localStorage.getItem(TOKEN_KEY)
|
||||
}
|
||||
|
||||
const setToken = (token: string) => {
|
||||
localStorage.setItem(TOKEN_KEY, token)
|
||||
}
|
||||
|
||||
const clearToken = () => {
|
||||
localStorage.removeItem(TOKEN_KEY)
|
||||
}
|
||||
|
||||
export { isLogin, getToken, setToken, clearToken }
|
||||
16
src/utils/avatar.ts
Normal file
16
src/utils/avatar.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import Unknown from '../assets/images/avatar/unknown.png'
|
||||
import Male from '../assets/images/avatar/male.png'
|
||||
import Female from '../assets/images/avatar/female.png'
|
||||
|
||||
export default function getAvatar(avatar: string | undefined, gender: number | undefined) {
|
||||
if (avatar) {
|
||||
return avatar
|
||||
}
|
||||
if (gender === 1) {
|
||||
return Male
|
||||
}
|
||||
if (gender === 2) {
|
||||
return Female
|
||||
}
|
||||
return Unknown
|
||||
}
|
||||
7
src/utils/date.ts
Normal file
7
src/utils/date.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export function getTime(dateString: string) {
|
||||
const date = new Date(dateString)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
72
src/utils/downloadFile.ts
Normal file
72
src/utils/downloadFile.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 根据文件url获取文件名
|
||||
* @param url 文件url
|
||||
*/
|
||||
function getFileName(url: string) {
|
||||
const num = url.lastIndexOf('/') + 1
|
||||
let fileName = url.substring(num)
|
||||
// 把参数和文件名分割开
|
||||
fileName = decodeURI(fileName.split('?')[0])
|
||||
return fileName
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件地址下载文件
|
||||
* @param {*} sUrl
|
||||
*/
|
||||
export function downloadByUrl({
|
||||
url,
|
||||
target = '_blank',
|
||||
fileName,
|
||||
}: {
|
||||
url: string
|
||||
target?: '_self' | '_blank'
|
||||
fileName?: string
|
||||
}): Promise<boolean> {
|
||||
// 是否同源
|
||||
const isSameHost = new URL(url).host === location.host
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
if (isSameHost) {
|
||||
// 同源资源,直接使用 <a> 标签下载
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.target = target
|
||||
|
||||
if (link.download !== undefined) {
|
||||
link.download = fileName || getFileName(url)
|
||||
}
|
||||
|
||||
if (document.createEvent) {
|
||||
const e = document.createEvent('MouseEvents')
|
||||
e.initEvent('click', true, true)
|
||||
link.dispatchEvent(e)
|
||||
return resolve(true)
|
||||
}
|
||||
|
||||
if (!url.includes('?')) {
|
||||
url += '?download'
|
||||
}
|
||||
|
||||
window.open(url, target)
|
||||
return resolve(true)
|
||||
} else {
|
||||
// 跨域资源,使用 fetch 获取文件并下载
|
||||
fetch(url)
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => {
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = fileName || getFileName(url)
|
||||
link.style.display = 'none'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(link.href)
|
||||
resolve(true)
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
39
src/utils/encrypt.ts
Normal file
39
src/utils/encrypt.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import Base64 from 'crypto-js/enc-base64'
|
||||
import UTF8 from 'crypto-js/enc-utf8'
|
||||
import { JSEncrypt } from 'jsencrypt'
|
||||
import md5 from 'crypto-js/md5'
|
||||
import CryptoJS from 'crypto-js'
|
||||
|
||||
export function encodeByBase64(txt: string) {
|
||||
return UTF8.parse(txt).toString(Base64)
|
||||
}
|
||||
|
||||
export function decodeByBase64(txt: string) {
|
||||
return Base64.parse(txt).toString(UTF8)
|
||||
}
|
||||
|
||||
export function encryptByMd5(txt: string) {
|
||||
return md5(txt).toString()
|
||||
}
|
||||
|
||||
const publicKey
|
||||
= 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAM51dgYtMyF+tTQt80sfFOpSV27a7t9u'
|
||||
+ 'aUVeFrdGiVxscuizE7H8SMntYqfn9lp8a5GH5P1/GGehVjUD2gF/4kcCAwEAAQ=='
|
||||
|
||||
export function encryptByRsa(txt: string) {
|
||||
const encryptor = new JSEncrypt()
|
||||
encryptor.setPublicKey(publicKey) // 设置公钥
|
||||
return encryptor.encrypt(txt) // 对数据进行加密
|
||||
}
|
||||
|
||||
const defaultKeyWork = 'XwKsGlMcdPMEhR1B'
|
||||
|
||||
export function encryptByAes(word, keyWord = defaultKeyWork) {
|
||||
const key = CryptoJS.enc.Utf8.parse(keyWord)
|
||||
const arcs = CryptoJS.enc.Utf8.parse(word)
|
||||
const encrypted = CryptoJS.AES.encrypt(arcs, key, {
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
})
|
||||
return encrypted.toString()
|
||||
}
|
||||
52
src/utils/has.ts
Normal file
52
src/utils/has.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { useUserStore } from '@/stores'
|
||||
|
||||
function authPermission(permission: string) {
|
||||
const all_permission = '*:*:*'
|
||||
const permissions = useUserStore().permissions
|
||||
if (permission && permission.length > 0) {
|
||||
return permissions.some((v) => {
|
||||
return all_permission === v || v === permission
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function authRole(role: string) {
|
||||
const super_admin = 'role_admin'
|
||||
const roles = useUserStore().roles
|
||||
if (role && role.length > 0) {
|
||||
return roles.some((v) => {
|
||||
return super_admin === v || v === role
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
/** 验证用户是否具备某权限 */
|
||||
hasPerm(permission: string) {
|
||||
return authPermission(permission)
|
||||
},
|
||||
/** 验证用户是否含有指定权限,只需包含其中一个 */
|
||||
hasPermOr(permissions: string[]) {
|
||||
return permissions.some((item) => authPermission(item))
|
||||
},
|
||||
/** 验证用户是否含有指定权限,必须全部拥有 */
|
||||
hasPermAnd(permissions: string[]) {
|
||||
return permissions.every((item) => authPermission(item))
|
||||
},
|
||||
/** 验证用户是否具备某角色 */
|
||||
hasRole(role: string) {
|
||||
return authRole(role)
|
||||
},
|
||||
/** 验证用户是否含有指定角色,只需包含其中一个 */
|
||||
hasRoleOr(roles: string[]) {
|
||||
return roles.some((item) => authRole(item))
|
||||
},
|
||||
/** 验证用户是否含有指定角色,必须全部拥有 */
|
||||
hasRoleAnd(roles: string[]) {
|
||||
return roles.every((item) => authRole(item))
|
||||
},
|
||||
}
|
||||
168
src/utils/http.ts
Normal file
168
src/utils/http.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import axios from 'axios'
|
||||
import qs from 'query-string'
|
||||
import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import { useUserStore } from '@/stores'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import modalErrorWrapper from '@/utils/modal-error-wrapper'
|
||||
import messageErrorWrapper from '@/utils/message-error-wrapper'
|
||||
import notificationErrorWrapper from '@/utils/notification-error-wrapper'
|
||||
import router from '@/router'
|
||||
|
||||
interface ICodeMessage {
|
||||
[propName: number]: string
|
||||
}
|
||||
|
||||
const StatusCodeMessage: ICodeMessage = {
|
||||
200: '服务器成功返回请求的数据',
|
||||
201: '新建或修改数据成功。',
|
||||
202: '一个请求已经进入后台排队(异步任务)',
|
||||
204: '删除数据成功',
|
||||
400: '请求错误(400)',
|
||||
401: '未授权,请重新登录(401)',
|
||||
403: '拒绝访问(403)',
|
||||
404: '请求出错(404)',
|
||||
408: '请求超时(408)',
|
||||
500: '服务器错误(500)',
|
||||
501: '服务未实现(501)',
|
||||
502: '网络错误(502)',
|
||||
503: '服务不可用(503)',
|
||||
504: '网络超时(504)',
|
||||
}
|
||||
|
||||
const http: AxiosInstance = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_PREFIX ?? import.meta.env.VITE_API_BASE_URL,
|
||||
timeout: 30 * 1000,
|
||||
})
|
||||
|
||||
const handleError = (msg: string) => {
|
||||
if (msg.length >= 15) {
|
||||
return notificationErrorWrapper(msg || '服务器端错误')
|
||||
}
|
||||
return messageErrorWrapper({
|
||||
content: msg || '服务器端错误',
|
||||
duration: 5 * 1000,
|
||||
})
|
||||
}
|
||||
|
||||
// 请求拦截器
|
||||
http.interceptors.request.use(
|
||||
(config: AxiosRequestConfig) => {
|
||||
const token = getToken()
|
||||
if (token) {
|
||||
if (!config.headers) {
|
||||
config.headers = {}
|
||||
}
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
(error) => Promise.reject(error),
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
http.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
const { data } = response
|
||||
const { success, code, msg } = data
|
||||
|
||||
if (response.request.responseType === 'blob') {
|
||||
const contentType = data.type
|
||||
if (contentType.startsWith('application/json')) {
|
||||
const reader = new FileReader()
|
||||
reader.readAsText(data)
|
||||
reader.onload = () => {
|
||||
const { success, msg } = JSON.parse(reader.result as string)
|
||||
if (!success) {
|
||||
handleError(msg)
|
||||
}
|
||||
}
|
||||
return Promise.reject(msg)
|
||||
} else {
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
return response
|
||||
}
|
||||
|
||||
// Token 失效
|
||||
if (code === '401' && response.config.url !== '/auth/user/info') {
|
||||
modalErrorWrapper({
|
||||
title: '提示',
|
||||
content: msg,
|
||||
maskClosable: false,
|
||||
escToClose: false,
|
||||
okText: '重新登录',
|
||||
async onOk() {
|
||||
const userStore = useUserStore()
|
||||
await userStore.logoutCallBack()
|
||||
await router.replace('/login')
|
||||
},
|
||||
})
|
||||
} else {
|
||||
handleError(msg)
|
||||
}
|
||||
return Promise.reject(new Error(msg || '服务器端错误'))
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
if (!error.response) {
|
||||
handleError('网络连接失败,请检查您的网络')
|
||||
return Promise.reject(error)
|
||||
}
|
||||
const status = error.response?.status
|
||||
const errorMsg = StatusCodeMessage[status] || '服务器暂时未响应,请刷新页面并重试。若无法解决,请联系管理员'
|
||||
handleError(errorMsg)
|
||||
return Promise.reject(error)
|
||||
},
|
||||
)
|
||||
|
||||
const request = async <T = unknown>(config: AxiosRequestConfig): Promise<ApiRes<T>> => {
|
||||
return http.request<T>(config)
|
||||
.then((res: AxiosResponse) => res.data)
|
||||
.catch((err: { msg: string }) => Promise.reject(err))
|
||||
}
|
||||
|
||||
const requestNative = async <T = unknown>(config: AxiosRequestConfig): Promise<AxiosResponse> => {
|
||||
return http.request<T>(config)
|
||||
.then((res: AxiosResponse) => res)
|
||||
.catch((err: { msg: string }) => Promise.reject(err))
|
||||
}
|
||||
|
||||
const createRequest = (method: string) => {
|
||||
return <T = any>(url: string, params?: object, config?: AxiosRequestConfig): Promise<ApiRes<T>> => {
|
||||
return request({
|
||||
method,
|
||||
url,
|
||||
[method === 'get' ? 'params' : 'data']: params,
|
||||
...(method === 'get'
|
||||
? {
|
||||
paramsSerializer: (obj) => qs.stringify(obj),
|
||||
}
|
||||
: {}),
|
||||
...config,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const download = (url: string, params?: object, config?: AxiosRequestConfig): Promise<AxiosResponse> => {
|
||||
return requestNative({
|
||||
method: 'get',
|
||||
url,
|
||||
responseType: 'blob',
|
||||
params,
|
||||
paramsSerializer: (obj) => qs.stringify(obj),
|
||||
...config,
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
get: createRequest('get'),
|
||||
post: createRequest('post'),
|
||||
put: createRequest('put'),
|
||||
patch: createRequest('patch'),
|
||||
del: createRequest('delete'),
|
||||
request,
|
||||
requestNative,
|
||||
download,
|
||||
}
|
||||
358
src/utils/index.ts
Normal file
358
src/utils/index.ts
Normal file
@@ -0,0 +1,358 @@
|
||||
import { browse, mapTree } from 'xe-utils'
|
||||
import { camelCase, upperFirst } from 'lodash-es'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import CronParser from 'cron-parser'
|
||||
import { isExternal } from '@/utils/validate'
|
||||
|
||||
export function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
|
||||
return obj[key]
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 去除空格
|
||||
* @param {string} str - 字符串
|
||||
* @param {string} pos - 去除空格的位置
|
||||
* pos="both": 去除两边空格
|
||||
* pos="left": 去除左边空格
|
||||
* pos="right": 去除右边空格
|
||||
* pos="all": 去除所有空格
|
||||
*/
|
||||
type Pos = 'both' | 'left' | 'right' | 'all'
|
||||
export function trim(str: string, pos: Pos = 'both'): string {
|
||||
if (pos === 'both') {
|
||||
return str.replace(/^\s+|\s+$/g, '')
|
||||
} else if (pos === 'left') {
|
||||
return str.replace(/^\s*/, '')
|
||||
} else if (pos === 'right') {
|
||||
return str.replace(/(\s*$)/g, '')
|
||||
} else if (pos === 'all') {
|
||||
return str.replace(/\s+/g, '')
|
||||
} else {
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据数字获取对应的汉字
|
||||
* @param {number} num - 数字(0-10)
|
||||
*/
|
||||
export function getHanByNumber(num: number): string {
|
||||
const str = '零一二三四五六七八九十'
|
||||
return str.charAt(num)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定整数范围内的随机整数
|
||||
* @param {number} start - 开始范围
|
||||
* @param {number} end - 结束范围
|
||||
*/
|
||||
export function getRandomInterger(start = 0, end: number): number {
|
||||
const range = end - start
|
||||
return Math.floor(Math.random() * range + start)
|
||||
}
|
||||
|
||||
/** @desc 千分位格式化 */
|
||||
export function formatMoney(money: string) {
|
||||
return money.replace(new RegExp(`(?!^)(?=(\\d{3})+${money.includes('.') ? '\\.' : '$'})`, 'g'), ',')
|
||||
}
|
||||
|
||||
/** @desc 数据类型检测方法 */
|
||||
export function getTypeOf(value: any) {
|
||||
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 格式化电话号码
|
||||
@demo 183-7983-6654 */
|
||||
export function formatPhone(mobile: string, formatStr = '-') {
|
||||
return mobile.replace(/(?=(\d{4})+$)/g, formatStr)
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 手机号脱敏
|
||||
@demo 155****8810 */
|
||||
export function hidePhone(phone: string) {
|
||||
return phone.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2')
|
||||
}
|
||||
|
||||
/** @desc 检测数据是否为空数据 */
|
||||
export function isEmpty(data: unknown) {
|
||||
if (data === '' || data === 'undefined' || data === undefined || data == null || data === 'null') {
|
||||
return true
|
||||
}
|
||||
return JSON.stringify(data) === '{}' || JSON.stringify(data) === '[]' || JSON.stringify(data) === '[{}]'
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 大小写转换
|
||||
* @param {string} str 待转换的字符串
|
||||
* @param {number} type 1:全大写 2:全小写 3:首字母大写
|
||||
*/
|
||||
export function toCase(str: string, type: number) {
|
||||
switch (type) {
|
||||
case 1:
|
||||
return str.toUpperCase()
|
||||
case 2:
|
||||
return str.toLowerCase()
|
||||
case 3:
|
||||
return str[0].toUpperCase() + str.substring(1).toLowerCase()
|
||||
default:
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 获取随机数
|
||||
* @param {number} min 最小值
|
||||
* @param {number} max 最大值
|
||||
*/
|
||||
export const randomNum = (min: number, max: number) => {
|
||||
return Math.floor(min + Math.random() * (max + 1 - min))
|
||||
}
|
||||
|
||||
/**
|
||||
@desc 获取最大值 */
|
||||
export const max = (arr: number[]) => {
|
||||
return Math.max.apply(null, arr)
|
||||
}
|
||||
|
||||
/**
|
||||
@desc 获取最小值 */
|
||||
export const min = (arr: number[]) => {
|
||||
return Math.min.apply(null, arr)
|
||||
}
|
||||
|
||||
/**
|
||||
@desc 求和 */
|
||||
export const sum = (arr: number[]) => {
|
||||
return arr.reduce((pre, cur) => pre + cur)
|
||||
}
|
||||
|
||||
/**
|
||||
@desc 获取平均值 */
|
||||
export const average = (arr: number[]) => {
|
||||
return sum(arr) / arr.length
|
||||
}
|
||||
|
||||
/**
|
||||
@desc 深拷贝 */
|
||||
export const deepClone = (data: any) => {
|
||||
if (typeof data !== 'object' || data == null) return '不是对象'
|
||||
const newData: any = Array.isArray(data) ? [] : {}
|
||||
for (const key in data) {
|
||||
newData[key] = typeof data[key] === 'object' ? deepClone(data[key]) : data[key]
|
||||
}
|
||||
return newData
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 判断是否是闰年
|
||||
* @param {number} year 年份
|
||||
*/
|
||||
export const isLeapYear = (year: number) => {
|
||||
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 判断是否是奇数
|
||||
* @param {number} num 数字
|
||||
*/
|
||||
export const isOdd = (num: number) => {
|
||||
return num % 2 !== 0
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 判断是否是偶数
|
||||
* @param {number} num 数字
|
||||
*/
|
||||
export const isEven = (num: number) => {
|
||||
return !isOdd(num)
|
||||
}
|
||||
|
||||
/**
|
||||
@desc 将RGB转化为十六机制 */
|
||||
export const rgbToHex = (r: number, g: number, b: number) => {
|
||||
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`
|
||||
}
|
||||
|
||||
/**
|
||||
@desc 获取随机十六进制颜色 */
|
||||
export const randomHex = () => {
|
||||
return `#${Math.floor(Math.random() * 0xFFFFFF)
|
||||
.toString(16)
|
||||
.padEnd(6, '0')}`
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 动态路由 path 转 name
|
||||
* @demo /system => System
|
||||
* @demo /system/menu => SystemMenu
|
||||
* @demo /data-manage/detail => DataManageDetail
|
||||
*/
|
||||
export const transformPathToName = (path: string) => {
|
||||
if (!path) return ''
|
||||
if (isExternal(path)) return ''
|
||||
return upperFirst(camelCase(path))
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 过滤树
|
||||
* @param { values } 数组
|
||||
*/
|
||||
type FilterTree = <T extends { children?: T[] }>(
|
||||
array: T[],
|
||||
iterate: (item: T, index?: number, items?: T[]) => boolean
|
||||
) => T[]
|
||||
export const filterTree: FilterTree = (values, fn) => {
|
||||
const arr = values.filter(fn)
|
||||
const data = mapTree(arr, (item) => {
|
||||
if (item.children && item.children.length) {
|
||||
item.children = item.children.filter(fn)
|
||||
}
|
||||
return item
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
type SortTree = <T extends { sort: number, children?: T[] }>(array: T[]) => T[]
|
||||
/**
|
||||
* @desc 排序树
|
||||
* @param values /
|
||||
*/
|
||||
export const sortTree: SortTree = (values) => {
|
||||
values?.sort((a, b) => (a?.sort ?? 0) - (b?.sort ?? 0)) // 排序
|
||||
return mapTree(values, (item) => {
|
||||
item.children?.sort((a, b) => (a?.sort ?? 0) - (b?.sort ?? 0)) // 排序
|
||||
return item
|
||||
})
|
||||
}
|
||||
|
||||
/** @desc 是否是h5环境 */
|
||||
export const isMobile = () => {
|
||||
return browse().isMobile
|
||||
}
|
||||
|
||||
/** @desc 问候 */
|
||||
export function goodTimeText() {
|
||||
const time = new Date()
|
||||
const hour = time.getHours()
|
||||
return hour < 9 ? '早上好' : hour <= 11 ? '上午好' : hour <= 13 ? '中午好' : hour <= 18 ? '下午好' : '晚上好'
|
||||
}
|
||||
|
||||
/** @desc 格式化文件大小 */
|
||||
export const formatFileSize = (fileSize: number) => {
|
||||
if (fileSize == null || fileSize === 0) {
|
||||
return '0 Bytes'
|
||||
}
|
||||
const unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
let index = 0
|
||||
const srcSize = Number.parseFloat(fileSize.toString())
|
||||
index = Math.floor(Math.log(srcSize) / Math.log(1024))
|
||||
const size = srcSize / 1024 ** index
|
||||
return `${size.toFixed(2)} ${unitArr[index]}`
|
||||
}
|
||||
|
||||
/** @desc 复制文本 */
|
||||
export const copyText = (text: any) => {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = text
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
Message.success('复制成功')
|
||||
}
|
||||
|
||||
/** @desc 文件的转换base64 */
|
||||
export const fileToBase64 = (file: File): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => {
|
||||
if (reader.result) {
|
||||
resolve(reader.result.toString())
|
||||
} else {
|
||||
reject(new Error('文件转base64失败'))
|
||||
}
|
||||
}
|
||||
reader.onerror = (error) => {
|
||||
reject(error)
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
}
|
||||
export const YMD_HMS = 'yyyy-MM-dd HH:mm:ss'
|
||||
|
||||
/**
|
||||
* 格式化时间
|
||||
*/
|
||||
export function dateFormat(date = new Date(), pattern = YMD_HMS) {
|
||||
if (!date) {
|
||||
return ''
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
|
||||
let formattedDate = pattern // Start with the pattern
|
||||
|
||||
// Year Handling
|
||||
const yearMatch = formattedDate.match(/(y+)/)
|
||||
if (yearMatch) {
|
||||
formattedDate = formattedDate.replace(yearMatch[0], (`${date.getFullYear()}`).substring(4 - yearMatch[0].length))
|
||||
}
|
||||
|
||||
// Other Formatters
|
||||
for (const k in o) {
|
||||
const reg = new RegExp(`(${k})`)
|
||||
const match = formattedDate.match(reg)
|
||||
if (match) {
|
||||
formattedDate = formattedDate.replace(match[0], (match[0].length === 1) ? o[k] : (`00${o[k]}`).substring((`${o[k]}`).length))
|
||||
}
|
||||
}
|
||||
|
||||
return formattedDate
|
||||
}
|
||||
|
||||
/**
|
||||
* 不含年的 cron 表达式
|
||||
* @param cron
|
||||
*/
|
||||
const expressionNoYear = (cron: string) => {
|
||||
const vs = cron.split(' ')
|
||||
// 长度=== 7 包含年表达式 不解析
|
||||
if (vs.length === 7) {
|
||||
return vs.slice(0, vs.length - 1).join(' ')
|
||||
}
|
||||
return cron
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析cron表达式预计未来运行时间
|
||||
* @param cron cron表达式
|
||||
*/
|
||||
export function parseCron(cron: string) {
|
||||
try {
|
||||
const parse = expressionNoYear(cron)
|
||||
const iter = CronParser.parseExpression(parse, {
|
||||
currentDate: dateFormat(new Date()),
|
||||
})
|
||||
const result: string[] = []
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const nextDate = iter.next()
|
||||
if (nextDate) {
|
||||
result.push(dateFormat(new Date(nextDate as any)))
|
||||
}
|
||||
}
|
||||
|
||||
return result.length > 0 ? result.join('\n') : '无执行时间'
|
||||
} catch (e) {
|
||||
return '表达式错误'
|
||||
}
|
||||
}
|
||||
11
src/utils/message-error-wrapper.ts
Normal file
11
src/utils/message-error-wrapper.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Message, type MessageReturn } from '@arco-design/web-vue'
|
||||
|
||||
let messageInstance: MessageReturn | null
|
||||
const messageErrorWrapper = (options: any) => {
|
||||
if (messageInstance) {
|
||||
messageInstance.close()
|
||||
}
|
||||
messageInstance = Message.error(options)
|
||||
}
|
||||
|
||||
export default messageErrorWrapper
|
||||
11
src/utils/mitt.ts
Normal file
11
src/utils/mitt.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import mitt from 'mitt'
|
||||
|
||||
interface Events {
|
||||
// 自定义事件名称
|
||||
event: void
|
||||
// 任意传递的参数
|
||||
[parmas: string]: any
|
||||
}
|
||||
|
||||
const mittBus = mitt<Events>()
|
||||
export default mittBus
|
||||
11
src/utils/modal-error-wrapper.ts
Normal file
11
src/utils/modal-error-wrapper.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Modal, type ModalReturn } from '@arco-design/web-vue'
|
||||
|
||||
let modalInstance: ModalReturn | null
|
||||
const modalErrorWrapper = (options: any) => {
|
||||
if (modalInstance) {
|
||||
modalInstance.close()
|
||||
}
|
||||
modalInstance = Modal.error(options)
|
||||
}
|
||||
|
||||
export default modalErrorWrapper
|
||||
11
src/utils/notification-error-wrapper.ts
Normal file
11
src/utils/notification-error-wrapper.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Notification, type NotificationReturn } from '@arco-design/web-vue'
|
||||
|
||||
let notificationInstance: NotificationReturn | null
|
||||
const notificationErrorWrapper = (options: any) => {
|
||||
if (notificationInstance) {
|
||||
notificationInstance.close()
|
||||
}
|
||||
notificationInstance = Notification.error(options)
|
||||
}
|
||||
|
||||
export default notificationErrorWrapper
|
||||
32
src/utils/regexp.ts
Normal file
32
src/utils/regexp.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/** @desc 正则-手机号码 */
|
||||
export const Phone = /^1[3-9]\d{9}$/
|
||||
|
||||
/** @desc 正则-邮箱 */
|
||||
export const Email = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/
|
||||
|
||||
/** @desc 正则-密码(密码为8-18位数字/字符/符号的组合) */
|
||||
// export const Password =
|
||||
// /^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/
|
||||
|
||||
/** @desc 正则-密码(密码为6位数字) */
|
||||
export const Password = /^\d{6}$/
|
||||
|
||||
/** @desc 正则-6位数字验证码正则 */
|
||||
export const Code_6 = /^\d{6}$/
|
||||
|
||||
/** @desc 正则-4位数字验证码正则 */
|
||||
export const Code_4 = /^\d{4}$/
|
||||
|
||||
/** @desc 正则-url链接 */
|
||||
export const Url
|
||||
// eslint-disable-next-line regexp/no-useless-quantifier
|
||||
= /(((^https?:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-]*)?\??[-+=&;%@.\w]*#?\w*)?)$/
|
||||
|
||||
/** @desc 正则-16进颜色值 #333 #8c8c8c */
|
||||
export const ColorRegex = /^#?([a-f0-9]{6}|[a-f0-9]{3})$/i
|
||||
|
||||
/** @desc 正则-只能是中文 */
|
||||
export const OnlyCh = /^[\u4E00-\u9FA5]+$/g
|
||||
|
||||
/** @desc 正则-只能是英文 */
|
||||
export const OnlyEn = /^[a-z]*$/i
|
||||
45
src/utils/typeof.ts
Normal file
45
src/utils/typeof.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/** 判断变量类型 */
|
||||
|
||||
export function isNumber(value: unknown) {
|
||||
return Object.prototype.toString.call(value) === '[object Number]'
|
||||
}
|
||||
|
||||
export function isString(value: unknown) {
|
||||
return Object.prototype.toString.call(value) === '[object String]'
|
||||
}
|
||||
|
||||
export function isBoolean(value: unknown) {
|
||||
return Object.prototype.toString.call(value) === '[object Boolean]'
|
||||
}
|
||||
|
||||
export function isNull(value: unknown) {
|
||||
return Object.prototype.toString.call(value) === '[object Null]'
|
||||
}
|
||||
|
||||
export function isUndefined(value: unknown) {
|
||||
return Object.prototype.toString.call(value) === '[object Undefined]'
|
||||
}
|
||||
|
||||
export function isObject(value: unknown) {
|
||||
return Object.prototype.toString.call(value) === '[object Object]'
|
||||
}
|
||||
|
||||
export function isArray(value: unknown) {
|
||||
return Object.prototype.toString.call(value) === '[object Array]'
|
||||
}
|
||||
|
||||
export function isDate(data: unknown) {
|
||||
return Object.prototype.toString.call(data) === '[object Date]'
|
||||
}
|
||||
|
||||
export function isRegExp(value: unknown) {
|
||||
return Object.prototype.toString.call(value) === '[object RegExp]'
|
||||
}
|
||||
|
||||
export function isSet(value: unknown) {
|
||||
return Object.prototype.toString.call(value) === '[object Set]'
|
||||
}
|
||||
|
||||
export function isMap(value: unknown) {
|
||||
return Object.prototype.toString.call(value) === '[object Map]'
|
||||
}
|
||||
16
src/utils/validate.ts
Normal file
16
src/utils/validate.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/** 判断 path 是否为外链 */
|
||||
export const isExternal = (path: string) => {
|
||||
const reg = /^(https?:|mailto:|tel:)/
|
||||
return reg.test(path)
|
||||
}
|
||||
|
||||
/** 判断 url 是否是 http 或 https */
|
||||
export function isHttp(url: string) {
|
||||
return url.includes('http://') || url.includes('https://')
|
||||
}
|
||||
|
||||
/** 判断 字符串 是否是 ipv4 */
|
||||
export function isIPv4(ip: string): boolean {
|
||||
const ipv4Pattern = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/
|
||||
return ipv4Pattern.test(ip)
|
||||
}
|
||||
39
src/utils/verify.ts
Normal file
39
src/utils/verify.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
export function resetSize(vm) {
|
||||
let img_width
|
||||
let img_height
|
||||
let bar_width
|
||||
let bar_height // 图片的宽度、高度,移动条的宽度、高度
|
||||
|
||||
const parentWidth = vm.$el.parentNode.offsetWidth || window.innerWidth
|
||||
const parentHeight = vm.$el.parentNode.offsetHeight || window.innerHeight
|
||||
if (vm.imgSize.width.includes('%')) {
|
||||
img_width = `${(Number.parseInt(vm.imgSize.width, 10) / 100) * parentWidth}px`
|
||||
} else {
|
||||
img_width = vm.imgSize.width
|
||||
}
|
||||
|
||||
if (vm.imgSize.height.includes('%')) {
|
||||
img_height = `${(Number.parseInt(vm.imgSize.height, 10) / 100) * parentHeight}px`
|
||||
} else {
|
||||
img_height = vm.imgSize.height
|
||||
}
|
||||
|
||||
if (vm.barSize.width.includes('%')) {
|
||||
bar_width = `${(Number.parseInt(vm.barSize.width, 10) / 100) * parentWidth}px`
|
||||
} else {
|
||||
bar_width = vm.barSize.width
|
||||
}
|
||||
|
||||
if (vm.barSize.height.includes('%')) {
|
||||
bar_height = `${(Number.parseInt(vm.barSize.height, 10) / 100) * parentHeight}px`
|
||||
} else {
|
||||
bar_height = vm.barSize.height
|
||||
}
|
||||
|
||||
return {
|
||||
imgWidth: img_width,
|
||||
imgHeight: img_height,
|
||||
barWidth: bar_width,
|
||||
barHeight: bar_height,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user