前端
This commit is contained in:
42
frontend/app/web-gold/src/utils/auth.js
Normal file
42
frontend/app/web-gold/src/utils/auth.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useCache } from '@gold/hooks/web/useCache'
|
||||
import { getToken as getTokenFromManager } from './token-manager'
|
||||
|
||||
const { wsCache } = useCache()
|
||||
|
||||
const AccessTokenKey = 'ACCESS_TOKEN'
|
||||
const RefreshTokenKey = 'REFRESH_TOKEN'
|
||||
|
||||
// 获取token - 使用统一的 token 管理器
|
||||
export const getAccessToken = () => {
|
||||
return getTokenFromManager()
|
||||
}
|
||||
|
||||
// 刷新token
|
||||
export const getRefreshToken = () => {
|
||||
try {
|
||||
// 优先从 wsCache 读取
|
||||
const refreshToken = wsCache.get(RefreshTokenKey) || wsCache.get('refresh_token')
|
||||
if (refreshToken) {
|
||||
return refreshToken
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('获取 refresh token 失败:', e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
// 设置token
|
||||
export const setToken = (token) => {
|
||||
wsCache.set(RefreshTokenKey, token.refreshToken)
|
||||
wsCache.set(AccessTokenKey, token.accessToken)
|
||||
}
|
||||
|
||||
// 删除token
|
||||
export const removeToken = () => {
|
||||
wsCache.delete(AccessTokenKey)
|
||||
wsCache.delete(RefreshTokenKey)
|
||||
}
|
||||
|
||||
/** 格式化token(jwt格式) */
|
||||
export const formatToken = (token) => {
|
||||
return 'Bearer ' + token
|
||||
}
|
||||
20
frontend/app/web-gold/src/utils/markdown.js
Normal file
20
frontend/app/web-gold/src/utils/markdown.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import MarkdownIt from 'markdown-it'
|
||||
|
||||
// 创建 markdown 实例
|
||||
const md = new MarkdownIt()
|
||||
|
||||
/**
|
||||
* 渲染 markdown 内容为 HTML
|
||||
* @param {string} content - markdown 内容
|
||||
* @returns {string} HTML 字符串
|
||||
*/
|
||||
export function renderMarkdown(content) {
|
||||
if (!content) return ''
|
||||
return md.render(content)
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出 md 实例,供需要自定义配置的地方使用
|
||||
*/
|
||||
export { md }
|
||||
|
||||
56
frontend/app/web-gold/src/utils/storage.js
Normal file
56
frontend/app/web-gold/src/utils/storage.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import localforage from 'localforage'
|
||||
|
||||
// 统一的 IndexedDB 存储封装:API 与 localStorage 类似但为异步
|
||||
// 默认使用一个命名的存储实例,便于后续扩展多命名空间
|
||||
const store = localforage.createInstance({
|
||||
name: 'web-gold',
|
||||
storeName: 'app_store',
|
||||
description: 'App persistent storage powered by IndexedDB',
|
||||
})
|
||||
|
||||
export async function setJSON(key, value) {
|
||||
try {
|
||||
const json = JSON.stringify(value)
|
||||
await store.setItem(key, json)
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('[storage.setJSON] failed:', err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function getJSON(key, fallback = null) {
|
||||
try {
|
||||
const raw = await store.getItem(key)
|
||||
if (raw == null) return fallback
|
||||
return JSON.parse(raw)
|
||||
} catch (err) {
|
||||
console.error('[storage.getJSON] failed:', err)
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
export async function remove(key) {
|
||||
try {
|
||||
await store.removeItem(key)
|
||||
} catch (err) {
|
||||
console.error('[storage.remove] failed:', err)
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearAll() {
|
||||
try {
|
||||
await store.clear()
|
||||
} catch (err) {
|
||||
console.error('[storage.clearAll] failed:', err)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
setJSON,
|
||||
getJSON,
|
||||
remove,
|
||||
clearAll,
|
||||
}
|
||||
|
||||
|
||||
100
frontend/app/web-gold/src/utils/token-manager.js
Normal file
100
frontend/app/web-gold/src/utils/token-manager.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import { useCache } from '@gold/hooks/web/useCache'
|
||||
|
||||
/**
|
||||
* Token 统一管理模块
|
||||
*
|
||||
* 优先级顺序:
|
||||
* 1. 手动输入的 dev token (sessionStorage)
|
||||
* 2. 正式登录的 token (wsCache)
|
||||
* 3. 环境变量 VITE_DEV_TOKEN
|
||||
*/
|
||||
|
||||
// sessionStorage 中的手动 token key
|
||||
const DEV_MANUAL_TOKEN_KEY = 'DEV_MANUAL_TOKEN'
|
||||
|
||||
// 获取缓存实例
|
||||
let wsCache = null
|
||||
function getCache() {
|
||||
if (!wsCache) {
|
||||
wsCache = useCache().wsCache
|
||||
}
|
||||
return wsCache
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取完整的 Authorization Header 值
|
||||
* @returns {string} Bearer token 或空字符串
|
||||
*/
|
||||
export function getAuthHeader() {
|
||||
const token = getToken()
|
||||
return token ? `Bearer ${token}` : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 token
|
||||
* @returns {string} token 字符串
|
||||
*/
|
||||
export function getToken() {
|
||||
// 1. 优先使用手动输入的 dev token
|
||||
const manualToken = sessionStorage.getItem(DEV_MANUAL_TOKEN_KEY)
|
||||
if (manualToken) {
|
||||
return manualToken
|
||||
}
|
||||
|
||||
// 2. 使用正式登录的 token(从 wsCache 读取)
|
||||
try {
|
||||
const cache = getCache()
|
||||
const accessToken = cache.get('ACCESS_TOKEN') || cache.get('access_token')
|
||||
|
||||
if (accessToken) {
|
||||
return accessToken
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('获取 wsCache 失败:', e)
|
||||
}
|
||||
|
||||
// 3. 兜底:环境变量中的 token
|
||||
const envToken = import.meta?.env?.VITE_DEV_TOKEN
|
||||
if (envToken) {
|
||||
return envToken
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置手动输入的 dev token
|
||||
* @param {string} token
|
||||
*/
|
||||
export function setDevToken(token) {
|
||||
if (token) {
|
||||
sessionStorage.setItem(DEV_MANUAL_TOKEN_KEY, token)
|
||||
} else {
|
||||
sessionStorage.removeItem(DEV_MANUAL_TOKEN_KEY)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取手动输入的 dev token(用于显示)
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getDevToken() {
|
||||
return sessionStorage.getItem(DEV_MANUAL_TOKEN_KEY) || ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有 token
|
||||
*/
|
||||
export function clearAllTokens() {
|
||||
sessionStorage.removeItem(DEV_MANUAL_TOKEN_KEY)
|
||||
try {
|
||||
const cache = getCache()
|
||||
cache.delete('ACCESS_TOKEN')
|
||||
cache.delete('access_token')
|
||||
cache.delete('REFRESH_TOKEN')
|
||||
cache.delete('refresh_token')
|
||||
} catch (e) {
|
||||
console.warn('清除 wsCache 失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
55
frontend/app/web-gold/src/utils/url.js
Normal file
55
frontend/app/web-gold/src/utils/url.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import { match as pathMatch } from 'path-to-regexp'
|
||||
|
||||
|
||||
/**
|
||||
* 安全解析 URL,失败返回 null
|
||||
*/
|
||||
export function safeParseUrl(input) {
|
||||
const raw = (input || '').trim()
|
||||
if (!raw) return null
|
||||
try {
|
||||
return new URL(raw)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 URL 对象按顺序读取第一个非空的 query 值
|
||||
*/
|
||||
export function getFirstQuery(urlObj, keys = []) {
|
||||
if (!urlObj || !urlObj.searchParams) return ''
|
||||
for (const key of keys) {
|
||||
const v = urlObj.searchParams.get(key)
|
||||
if (v) return v
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
// 组合解析:优先从 queryKeys 取值,其次按 path-to-regexp 的模式从 pathname 匹配(返回首个命名参数),最后可回退 raw
|
||||
export function resolveId(input, options = {}) {
|
||||
const raw = (input || '').trim()
|
||||
if (!raw) return ''
|
||||
const { queryKeys = [], pathPatterns = [], fallbackRaw = true } = options
|
||||
const urlObj = safeParseUrl(raw)
|
||||
const q = getFirstQuery(urlObj, queryKeys)
|
||||
if (q) return q
|
||||
if (urlObj) {
|
||||
for (const pattern of pathPatterns) {
|
||||
if (typeof pattern === 'string') {
|
||||
const m = pathMatch(pattern)(urlObj.pathname)
|
||||
if (m && m.params) {
|
||||
const firstKey = Object.keys(m.params)[0]
|
||||
const val = firstKey ? m.params[firstKey] : ''
|
||||
if (val) return String(val)
|
||||
}
|
||||
} else if (pattern instanceof RegExp) {
|
||||
const m = urlObj.pathname.match(pattern)
|
||||
if (m && m[1]) return m[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return fallbackRaw ? raw : ''
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user