Files
dafuweng-buildadmin/dafuweng-webman/web/src/utils/common.ts
2026-03-18 11:22:12 +08:00

405 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import * as elIcons from '@element-plus/icons-vue'
import { useTitle } from '@vueuse/core'
import type { FormInstance } from 'element-plus'
import { isArray, isNull, trim, trimStart } from 'lodash-es'
import type { App } from 'vue'
import { nextTick } from 'vue'
import type { TranslateOptions } from 'vue-i18n'
import { i18n } from '../lang'
import { useSiteConfig } from '../stores/siteConfig'
import { getUrl } from './axios'
import Icon from '/@/components/icon/index.vue'
import router from '/@/router/index'
import { adminBaseRoutePath } from '/@/router/static/adminBase'
import { useMemberCenter } from '/@/stores/memberCenter'
import { useNavTabs } from '/@/stores/navTabs'
export function registerIcons(app: App) {
/*
* 全局注册 Icon
* 使用方式: <Icon name="name" size="size" color="color" />
* 详见<待完善>
*/
app.component('Icon', Icon)
/*
* 全局注册element Plus的icon
*/
const icons = elIcons as any
for (const i in icons) {
app.component(`el-icon-${icons[i].name}`, icons[i])
}
}
/**
* 加载网络css文件
* @param url css资源url
*/
export function loadCss(url: string): void {
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = url
link.crossOrigin = 'anonymous'
document.getElementsByTagName('head')[0].appendChild(link)
}
/**
* 加载网络js文件
* @param url js资源url
*/
export function loadJs(url: string): void {
const link = document.createElement('script')
link.src = url
document.body.appendChild(link)
}
/**
* 根据路由 meta.title 设置浏览器标题
*/
export function setTitleFromRoute() {
nextTick(() => {
if (typeof router.currentRoute.value.meta.title != 'string') {
return
}
const webTitle = i18n.global.te(router.currentRoute.value.meta.title)
? i18n.global.t(router.currentRoute.value.meta.title)
: router.currentRoute.value.meta.title
const title = useTitle()
const siteConfig = useSiteConfig()
title.value = `${webTitle}${siteConfig.siteName ? ' - ' + siteConfig.siteName : ''}`
})
}
/**
* 设置浏览器标题
* @param webTitle 新的标题
*/
export function setTitle(webTitle: string) {
if (router.currentRoute.value) {
router.currentRoute.value.meta.title = webTitle
}
nextTick(() => {
const title = useTitle()
const siteConfig = useSiteConfig()
title.value = `${webTitle}${siteConfig.siteName ? ' - ' + siteConfig.siteName : ''}`
})
}
/**
* 是否是外部链接
* @param path
*/
export function isExternal(path: string): boolean {
return /^(https?|ftp|mailto|tel):/.test(path)
}
/**
* 全局防抖
* 与 _.debounce 不同的是,间隔期间如果再次传递不同的函数,两个函数也只会执行一次
* @param fn 执行函数
* @param ms 间隔毫秒数
*/
export const debounce = (fn: Function, ms: number) => {
return (...args: any[]) => {
if (window.lazy) {
clearTimeout(window.lazy)
}
window.lazy = window.setTimeout(() => {
fn(...args)
}, ms)
}
}
/**
* 根据pk字段的值从数组中获取key
* @param arr
* @param pk
* @param value
*/
export const getArrayKey = (arr: any, pk: string, value: any): any => {
for (const key in arr) {
if (arr[key][pk] == value) {
return key
}
}
return false
}
/**
* 表单重置
* @param formEl
*/
export const onResetForm = (formEl?: FormInstance | null) => {
typeof formEl?.resetFields == 'function' && formEl.resetFields()
}
/**
* 将数据构建为ElTree的data {label:'', children: []}
* @param data
*/
export const buildJsonToElTreeData = (data: any): ElTreeData[] => {
if (typeof data == 'object') {
const childrens = []
for (const key in data) {
childrens.push({
label: key + ': ' + data[key],
children: buildJsonToElTreeData(data[key]),
})
}
return childrens
} else {
return []
}
}
/**
* 是否在后台应用内
* @param path 不传递则通过当前路由 path 检查
*/
export const isAdminApp = (path = '') => {
const regex = new RegExp(`^${adminBaseRoutePath}`)
if (path) {
return regex.test(path)
}
if (regex.test(getCurrentRoutePath())) {
return true
}
return false
}
/**
* 是否为手机设备
*/
export const isMobile = () => {
return !!navigator.userAgent.match(
/android|webos|ip(hone|ad|od)|opera (mini|mobi|tablet)|iemobile|windows.+(phone|touch)|mobile|fennec|kindle (Fire)|Silk|maemo|blackberry|playbook|bb10\; (touch|kbd)|Symbian(OS)|Ubuntu Touch/i
)
}
/**
* 从一个文件路径中获取文件名
* @param path 文件路径
*/
export const getFileNameFromPath = (path: string) => {
const paths = path.split('/')
return paths[paths.length - 1]
}
export function auth(node: string): boolean
export function auth(node: { name: string; subNodeName?: string }): boolean
/**
* 鉴权
* 提供 string 将根据当前路由 path 自动拼接和鉴权,还可以提供路由的 name 对象进行鉴权
* @param node
*/
export function auth(node: string | { name: string; subNodeName?: string }) {
const store = isAdminApp() ? useNavTabs() : useMemberCenter()
if (typeof node === 'string') {
const path = getCurrentRoutePath()
if (store.state.authNode.has(path)) {
const subNodeName = path + (path == '/' ? '' : '/') + node
if (store.state.authNode.get(path)!.some((v: string) => v == subNodeName)) {
return true
}
}
} else {
// 节点列表中没有找到 name
if (!node.name || !store.state.authNode.has(node.name)) return false
// 无需继续检查子节点或未找到子节点
if (!node.subNodeName || store.state.authNode.get(node.name)?.includes(node.subNodeName)) return true
}
return false
}
/**
* 获取资源完整地址
* @param relativeUrl 资源相对地址
* @param domain 指定域名
*/
export const fullUrl = (relativeUrl: string, domain = '') => {
const siteConfig = useSiteConfig()
if (!domain) {
domain = siteConfig.cdnUrl ? siteConfig.cdnUrl : getUrl()
}
if (!relativeUrl) return domain
const regUrl = new RegExp(/^http(s)?:\/\//)
const regexImg = new RegExp(/^((?:[a-z]+:)?\/\/|data:image\/)(.*)/i)
if (!domain || regUrl.test(relativeUrl) || regexImg.test(relativeUrl)) {
return relativeUrl
}
let url = domain + relativeUrl
if (domain === siteConfig.cdnUrl && siteConfig.cdnUrlParams) {
const separator = url.includes('?') ? '&' : '?'
url += separator + siteConfig.cdnUrlParams
}
return url
}
/**
* 获取路由 path
*/
export const getCurrentRoutePath = () => {
let path = router.currentRoute.value.path
if (path == '/') path = trimStart(window.location.hash, '#')
if (path.indexOf('?') !== -1) path = path.replace(/\?.*/, '')
return path
}
/**
* 获取根据当前路由路径动态加载的语言翻译
* @param key 无需语言路径的翻译key亦可使用完整路径
* @param named — 命名插值的值
* @param options — 其他翻译选项
* @returns — Translated message
*/
export const __ = (key: string, named?: Record<string, unknown>, options?: TranslateOptions<string>) => {
let langPath = ''
const path = getCurrentRoutePath()
if (isAdminApp()) {
langPath = path.slice(path.indexOf(adminBaseRoutePath) + adminBaseRoutePath.length)
langPath = trim(langPath, '/').replaceAll('/', '.')
} else {
langPath = trim(path, '/').replaceAll('/', '.')
}
langPath = langPath ? langPath + '.' + key : key
return i18n.global.te(langPath)
? i18n.global.t(langPath, named ?? {}, options ? options : {})
: i18n.global.t(key, named ?? {}, options ? options : {})
}
/**
* 文件类型效验,前端根据服务端配置进行初步检查
* @param fileName 文件名
* @param fileType 文件 mimeType不一定存在
*/
export const checkFileMimetype = (fileName: string, fileType: string) => {
if (!fileName) return false
const siteConfig = useSiteConfig()
const allowedSuffixes = isArray(siteConfig.upload.allowedSuffixes)
? siteConfig.upload.allowedSuffixes
: siteConfig.upload.allowedSuffixes.toLowerCase().split(',')
const allowedMimeTypes = isArray(siteConfig.upload.allowedMimeTypes)
? siteConfig.upload.allowedMimeTypes
: siteConfig.upload.allowedMimeTypes.toLowerCase().split(',')
const fileSuffix = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase()
if (allowedSuffixes.includes(fileSuffix) || allowedSuffixes.includes('.' + fileSuffix)) {
return true
}
if (fileType && allowedMimeTypes.includes(fileType)) {
return true
}
return false
}
/**
* 获取一组资源的完整地址
* @param relativeUrls 资源相对地址
* @param domain 指定域名
*/
export const arrayFullUrl = (relativeUrls: string | string[], domain = '') => {
if (typeof relativeUrls === 'string') {
relativeUrls = relativeUrls == '' ? [] : relativeUrls.split(',')
}
for (const key in relativeUrls) {
relativeUrls[key] = fullUrl(relativeUrls[key], domain)
}
return relativeUrls
}
/**
* 格式化时间戳
* @param dateTime 时间戳,默认使用当前时间戳
* @param fmt 格式化方式默认yyyy-mm-dd hh:MM:ss
*/
export const timeFormat = (dateTime: string | number | null = null, fmt = 'yyyy-mm-dd hh:MM:ss') => {
if (dateTime == 'none') {
return i18n.global.t('None')
}
if (isNull(dateTime)) {
dateTime = Number(new Date())
}
/**
* 1. 秒级时间戳10位需要转换为毫秒级才能供 Date 对象直接使用
* 2. yyyy-mm-dd 也是10位使用 isFinite 进行排除
*/
if (String(dateTime).length === 10 && isFinite(Number(dateTime))) {
dateTime = +dateTime * 1000
}
let date = new Date(dateTime)
if (isNaN(date.getTime())) {
date = new Date(Number(dateTime))
if (isNaN(date.getTime())) {
return 'Invalid Date'
}
}
let ret
const opt: anyObj = {
'y+': date.getFullYear().toString(), // 年
'm+': (date.getMonth() + 1).toString(), // 月
'd+': date.getDate().toString(), // 日
'h+': date.getHours().toString(), // 时
'M+': date.getMinutes().toString(), // 分
's+': date.getSeconds().toString(), // 秒
}
for (const k in opt) {
ret = new RegExp('(' + k + ')').exec(fmt)
if (ret) {
fmt = fmt.replace(ret[1], ret[1].length == 1 ? opt[k] : padStart(opt[k], ret[1].length, '0'))
}
}
return fmt
}
/**
* 字符串补位
*/
const padStart = (str: string, maxLength: number, fillString = ' ') => {
if (str.length >= maxLength) return str
const fillLength = maxLength - str.length
let times = Math.ceil(fillLength / fillString.length)
while ((times >>= 1)) {
fillString += fillString
if (times === 1) {
fillString += fillString
}
}
return fillString.slice(0, fillLength) + str
}
/**
* 根据当前时间生成问候语
*/
export const getGreet = () => {
const now = new Date()
const hour = now.getHours()
let greet = ''
if (hour < 5) {
greet = i18n.global.t('utils.Late at night, pay attention to your body!')
} else if (hour < 9) {
greet = i18n.global.t('utils.good morning!') + i18n.global.t('utils.welcome back')
} else if (hour < 12) {
greet = i18n.global.t('utils.Good morning!') + i18n.global.t('utils.welcome back')
} else if (hour < 14) {
greet = i18n.global.t('utils.Good noon!') + i18n.global.t('utils.welcome back')
} else if (hour < 18) {
greet = i18n.global.t('utils.good afternoon') + i18n.global.t('utils.welcome back')
} else if (hour < 24) {
greet = i18n.global.t('utils.Good evening') + i18n.global.t('utils.welcome back')
} else {
greet = i18n.global.t('utils.Hello!') + i18n.global.t('utils.welcome back')
}
return greet
}