Files
MrleeAdmin/app/utils/common.ts
T
Mr.April d84d972e73 feat: 适配5.x后端,重构前端架构
主要变更:
1. 重构API目录结构,采用模块化设计,适配5.x后端接口规范
2. 优化布局组件,提升用户界面体验
3. 增强国际化支持,完善多语言配置
4. 新增权限控制功能,支持前端路由权限管理
5. 优化认证流程,支持JWT认证机制
6. 新增中间件和插件,提升系统扩展性
7. 优化状态管理,采用更清晰的模块化设计
8. 新增搜索框、广告等组件,丰富页面功能
9. 更改包管理工具从npm到pnpm,提升依赖管理效率
10. 完善配置系统,支持多环境配置

适配5.x后端特性:
- 采用新的API接口规范
- 支持JWT认证机制
- 适配后端权限控制体系
- 支持后端国际化接口
- 优化数据交互方式
2026-04-07 11:01:11 +08:00

309 lines
8.6 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 type { FormInstance } from 'element-plus'
import { isNull, trim } from 'lodash-es'
import type { CSSProperties } from 'vue'
import type { TranslateOptions } from 'vue-i18n'
import { useMemberStore } from '~/stores/member'
import { useConfigStore } from '~/stores/config'
type anyObj = { [key: string]: any }
/**
* 获取token
* @param type token类型,默认为Bearer
* @returns 完整的token字符串
*/
export function getToken(type: string = 'Bearer'): null | string {
// 优先从 store 获取
let token = useMemberStore().token
// 如果 store 中有token且不为空,直接返回
if (token && token.trim() !== '') {
return `${type} ${token}`
}
// 如果 store 中没有,尝试从 cookie 获取(处理页面刷新后的情况)
const cookieToken = useCookie('token').value
if (cookieToken && cookieToken.trim() !== '') {
return `${type} ${cookieToken}`
}
return null
}
/**
* 获取refresh token
* @param type token类型,默认为Bearer
* @returns 完整的refresh token字符串
*/
export function getRefreshToken(type: string = 'Bearer'): null | string {
// 优先从 store 获取
let refreshToken = useMemberStore().refreshToken
// 如果 store 中有token且不为空,直接返回
if (refreshToken && refreshToken.trim() !== '') {
return `${type} ${refreshToken}`
}
// 如果 store 中没有,尝试从 cookie 获取(处理页面刷新后的情况)
const cookieRefreshToken = useCookie('refreshToken').value
if (cookieRefreshToken && cookieRefreshToken.trim() !== '') {
return `${type} ${cookieRefreshToken}`
}
return null
}
/**
* 获取资源完整地址
* @param relativeUrl 资源相对地址
* @param domain 指定域名
*/
export const fullUrl = (relativeUrl: string, domain = '') => {
const configStore = useConfigStore()
if (!domain) {
domain = configStore?.cdn_url ? configStore?.cdn_url : import.meta.env.VITE_API_BASE_URL
}
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
}
return domain + relativeUrl
}
/**
* 将Markdown内容中的绝对URL转换为相对路径
* @param markdown Markdown内容
*/
export const convertMarkdownToRelative = (markdown: string): string => {
if (!markdown) return markdown
const configStore = useConfigStore()
const domain = configStore?.cdn_url || import.meta.env.VITE_API_BASE_URL || window.location.origin
return markdown.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, url) => {
// 跳过 base64 图片和相对路径
if (url.startsWith('data:') || !url.startsWith('http://') && !url.startsWith('https://')) {
return match
}
// 将绝对URL转换为相对路径
try {
const urlObj = new URL(url)
const pathname = urlObj.pathname
return `![${alt}](${pathname})`
} catch {
return match
}
})
}
/**
* 将Markdown内容中的相对路径转换为绝对URL
* @param markdown Markdown内容
*/
export const convertMarkdownToFull = (markdown: string): string => {
if (!markdown) return markdown
const configStore = useConfigStore()
const domain = configStore?.cdn_url || import.meta.env.VITE_API_BASE_URL || window.location.origin
return markdown.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, url) => {
// 跳过 base64 图片和外部URL(OSS/CDN)
if (url.startsWith('data:') || url.startsWith('http://') || url.startsWith('https://')) {
return match
}
// 如果是相对路径,转换为绝对URL
const fullUrl = url.startsWith('/') ? `${domain}${url}` : `${domain}/${url}`
return `![${alt}](${fullUrl})`
})
}
/**
* 判断是否为外部URL(OSS/CDN等)
*/
export const isExternalUrl = (url: string): boolean => {
return /^https?:\/\//.test(url) || /^data:image\//.test(url)
}
/**
* 获取一组资源的完整地址
* @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 path 文件路径
*/
export const getFileNameFromPath = (path: string) => {
const paths = path.split('/')
return paths[paths.length - 1]
}
/**
* 是否是外部链接
* @param path
*/
export function isExternal(path: string): boolean {
return /^(https?|ftp|mailto|tel):/.test(path)
}
/**
* 是否为手机设备
*/
export const isMobile = () => {
const event = useRequestEvent()
const userAgent = import.meta.client ? navigator.userAgent : event?.node.req.headers['user-agent']
if (!userAgent) return false
return !!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 fn 执行函数
* @param ms 间隔毫秒数
*/
export const debounce = (fn: Function, ms: number) => {
return (...args: any[]) => {
if (globals.lazy) {
clearTimeout(globals.lazy)
}
globals.lazy = setTimeout(() => {
fn(...args)
}, ms)
}
}
/**
* 表单重置
* @param formEl
*/
export function onResetForm(formEl?: FormInstance | null) {
typeof formEl?.resetFields == 'function' && formEl?.resetFields()
}
/**
* 根据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
}
/**
* 获取简洁的路由 path
*/
export const getCurrentRoutePath = () => {
const router = useRouter()
let path = router.currentRoute.value.path
if (path.indexOf('?') !== -1) path = path.replace(/\?.*/, '')
return path
}
export function auth(node: string): boolean
export function auth(node: { name: string; subNodeName?: string }): boolean
/**
* 权限检查函数
* @param node 权限节点,可以是字符串或对象
* @returns 是否有权限
*/
export function auth(node: string | { name: string; subNodeName?: string }): boolean {
// TODO: 实现权限检查逻辑
// 目前先返回 true,表示所有用户都有权限
return true
}
/*
* 格式化时间戳
*/
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())
}
let date: Date
if (typeof dateTime === 'string') {
date = new Date(dateTime)
} else {
if (dateTime.toString().length === 10) {
dateTime = +dateTime * 1000
}
date = new Date(Number(dateTime))
}
if (isNaN(date.getTime())) {
return ''
}
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
}
/**
* calc(100vh - minusHeight)
* @param minusHeight 需要减去的高度
* @returns CSSProperties
*/
export function calcHeight(minusHeight = 60): CSSProperties {
return {
height: 'calc(100vh - ' + minusHeight.toString() + 'px)',
}
}