webman迁移

This commit is contained in:
2026-03-18 11:22:12 +08:00
parent dab3b3148f
commit ea77c7b3a1
623 changed files with 38163 additions and 106 deletions

View File

@@ -0,0 +1,404 @@
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
}