整合前端仓库并保留提交历史
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { useMemberStore } from '~/stores/member'
|
||||
import { getMemberProfile } from '~/api/member'
|
||||
import { refreshToken } from '~/api/auth'
|
||||
|
||||
export default defineNuxtPlugin(async () => {
|
||||
// 仅在客户端执行
|
||||
if (!import.meta.client) return
|
||||
|
||||
// 等待一下让 pinia 持久化插件完成初始化
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
|
||||
const memberStore = useMemberStore()
|
||||
|
||||
// 如果没有 token 但有 refreshToken,尝试刷新
|
||||
if (memberStore.refreshToken && !memberStore.token) {
|
||||
try {
|
||||
const response: any = await refreshToken(memberStore.refreshToken)
|
||||
|
||||
if (response.code === 0 && response.data) {
|
||||
memberStore.token = response.data.access_token
|
||||
memberStore.refreshToken = response.data.refresh_token
|
||||
} else {
|
||||
memberStore.logout()
|
||||
}
|
||||
} catch (error) {
|
||||
memberStore.logout()
|
||||
}
|
||||
} else if (memberStore.token && memberStore.refreshToken) {
|
||||
// 如果有 token 和 refreshToken,尝试获取用户信息
|
||||
try {
|
||||
const data: any = await getMemberProfile()
|
||||
|
||||
if (data && data.id) {
|
||||
memberStore.info = data
|
||||
} else {
|
||||
memberStore.info = null
|
||||
}
|
||||
} catch (error: any) {
|
||||
// 如果是 401 错误,尝试刷新 token
|
||||
if (error?.message?.includes('401') || error?.response?.status === 401) {
|
||||
try {
|
||||
const refreshResponse: any = await refreshToken(memberStore.refreshToken)
|
||||
|
||||
if (refreshResponse.code === 0 && refreshResponse.data) {
|
||||
memberStore.token = refreshResponse.data.access_token
|
||||
memberStore.refreshToken = refreshResponse.data.refresh_token
|
||||
|
||||
// 刷新成功后重新获取用户信息
|
||||
const userInfo: any = await getMemberProfile()
|
||||
if (userInfo && userInfo.id) {
|
||||
memberStore.info = userInfo
|
||||
}
|
||||
} else {
|
||||
memberStore.logout()
|
||||
}
|
||||
} catch (refreshError) {
|
||||
memberStore.logout()
|
||||
}
|
||||
} else {
|
||||
memberStore.info = null
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
// 引入 Element Plus 图标组件@element-plus/icons-vue
|
||||
//@ts-ignore
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
|
||||
export default defineNuxtPlugin((NuxtApp) => {
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
NuxtApp.vueApp.component(key, component)
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,181 @@
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import storage from '@/utils/storage'
|
||||
|
||||
// 定义语言文件模块接口
|
||||
interface LanguageFileModule {
|
||||
default: Record<string, any>
|
||||
}
|
||||
|
||||
// 类型保护函数,确保模块符合预期格式
|
||||
function isLanguageFileModule(module: any): module is LanguageFileModule {
|
||||
return module && typeof module === 'object' && 'default' in module && typeof module.default === 'object'
|
||||
}
|
||||
|
||||
// 递归处理嵌套结构
|
||||
const flattenMessages = (obj: any, path: string = '') => {
|
||||
let result: Record<string, string> = {}
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
const newPath = path ? `${path}.${key}` : key
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
Object.assign(result, flattenMessages(value, newPath))
|
||||
} else {
|
||||
result[newPath] = String(value)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// 自动扫描加载所有语言文件
|
||||
const loadAllLanguageFiles = () => {
|
||||
const messages: Record<string, Record<string, any>> = {}
|
||||
|
||||
try {
|
||||
// 加载所有语言文件(包括子目录)
|
||||
const languageFiles = import.meta.glob('~/lang/**/*.json', {
|
||||
eager: true
|
||||
}) as Record<string, LanguageFileModule>
|
||||
|
||||
// 处理语言文件
|
||||
for (const [filePath, module] of Object.entries(languageFiles)) {
|
||||
if (isLanguageFileModule(module)) {
|
||||
const pathParts = filePath.split('/')
|
||||
const langIndex = pathParts.indexOf('lang')
|
||||
|
||||
if (langIndex >= 0 && pathParts.length > langIndex + 1) {
|
||||
const langCode = pathParts[langIndex + 1] ?? ''
|
||||
|
||||
if (langCode) {
|
||||
if (!messages[langCode]) {
|
||||
messages[langCode] = {}
|
||||
}
|
||||
|
||||
// 只使用文件名作为键前缀,不包含目录名
|
||||
const fileName = (pathParts[pathParts.length - 1] ?? '').replace('.json', '')
|
||||
const fileKey = fileName
|
||||
|
||||
// 处理嵌套结构
|
||||
const flattenedMessages = flattenMessages(module.default)
|
||||
|
||||
// 如果有文件键前缀,添加到所有消息键前
|
||||
if (fileKey) {
|
||||
const prefixedMessages: Record<string, string> = {}
|
||||
for (const [key, value] of Object.entries(flattenedMessages)) {
|
||||
prefixedMessages[`${fileKey}.${key}`] = value
|
||||
}
|
||||
messages[langCode] = {
|
||||
...messages[langCode],
|
||||
...prefixedMessages
|
||||
}
|
||||
} else {
|
||||
// 没有前缀,直接合并
|
||||
messages[langCode] = {
|
||||
...messages[langCode],
|
||||
...flattenedMessages
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 加载 apps 目录下的语言文件
|
||||
const appsLanguageFiles = import.meta.glob('~/apps/**/lang/**/*.json', {
|
||||
eager: true
|
||||
}) as Record<string, LanguageFileModule>
|
||||
|
||||
// 处理 apps 语言文件
|
||||
for (const [filePath, module] of Object.entries(appsLanguageFiles)) {
|
||||
if (isLanguageFileModule(module)) {
|
||||
const pathParts = filePath.split('/')
|
||||
const appsIndex = pathParts.indexOf('apps')
|
||||
const langIndex = pathParts.indexOf('lang')
|
||||
|
||||
if (appsIndex >= 0 && langIndex >= 0 && pathParts.length > langIndex + 1) {
|
||||
const langCode = pathParts[langIndex + 1] ?? ''
|
||||
|
||||
if (langCode) {
|
||||
if (!messages[langCode]) {
|
||||
messages[langCode] = {}
|
||||
}
|
||||
|
||||
// 提取模块名(apps目录下的子目录名)
|
||||
const moduleName = pathParts[appsIndex + 1] ?? ''
|
||||
|
||||
// 只使用文件名作为键前缀,不包含目录名
|
||||
const fileName = (pathParts[pathParts.length - 1] ?? '').replace('.json', '')
|
||||
|
||||
// 构建文件键,包含模块名前缀
|
||||
let fileKey = moduleName
|
||||
if (fileName) {
|
||||
fileKey = `${moduleName}.${fileName}`
|
||||
}
|
||||
|
||||
// 处理嵌套结构
|
||||
const flattenedMessages = flattenMessages(module.default)
|
||||
|
||||
// 如果有文件键前缀,添加到所有消息键前
|
||||
if (fileKey) {
|
||||
const prefixedMessages: Record<string, string> = {}
|
||||
for (const [key, value] of Object.entries(flattenedMessages)) {
|
||||
prefixedMessages[`${fileKey}.${key}`] = value
|
||||
}
|
||||
messages[langCode] = {
|
||||
...messages[langCode],
|
||||
...prefixedMessages
|
||||
}
|
||||
} else {
|
||||
// 没有前缀,直接合并
|
||||
messages[langCode] = {
|
||||
...messages[langCode],
|
||||
...flattenedMessages
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有任何语言文件,使用默认配置
|
||||
if (Object.keys(messages).length === 0) {
|
||||
console.warn('未找到任何语言文件,使用默认语言配置')
|
||||
messages['zh-cn'] = { common: {} }
|
||||
messages['en'] = { common: {} }
|
||||
}
|
||||
|
||||
return messages
|
||||
} catch (error) {
|
||||
console.error('加载语言文件时出错:', error)
|
||||
// 返回默认配置
|
||||
return {
|
||||
'zh-cn': { common: {} },
|
||||
'en': { common: {} }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default defineNuxtPlugin((NuxtApp) => {
|
||||
// 加载所有语言消息
|
||||
const messages = loadAllLanguageFiles()
|
||||
|
||||
// 从存储中读取语言设置,默认为 zh-cn
|
||||
const savedLocale = storage.get('lang') ?? 'zh-cn'
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false, // 使用Composition API
|
||||
locale: savedLocale, // 使用存储中的语言设置
|
||||
fallbackLocale: 'zh-cn', // 回退语言
|
||||
globalInjection: true, // 全局注入
|
||||
messages: messages,
|
||||
silentFallbackWarn: true,
|
||||
silentTranslationWarn: true
|
||||
})
|
||||
|
||||
NuxtApp.vueApp.use(i18n)
|
||||
|
||||
return {
|
||||
provide: {
|
||||
i18n: i18n.global, // 提供全局实例
|
||||
getI18n: () => i18n
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
export default defineNuxtPlugin(() => {
|
||||
// console.log('Icon plugin loaded')
|
||||
})
|
||||
@@ -0,0 +1,70 @@
|
||||
import type { App, Directive, Plugin } from 'vue'
|
||||
import { useMemberStore } from '~/stores/member'
|
||||
|
||||
/**
|
||||
* 权限指令配置
|
||||
*/
|
||||
interface AuthDirectiveOptions {
|
||||
permissions: string | string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否有权限
|
||||
* @param permissions 权限码或权限码数组
|
||||
* @returns 是否有权限
|
||||
*/
|
||||
export function hasAuth(permissions: string | string[]): boolean {
|
||||
const memberStore = useMemberStore()
|
||||
const userPermissions = memberStore.info?.permissions || []
|
||||
|
||||
// 如果用户拥有所有权限,直接返回 true
|
||||
if (userPermissions.includes('*')) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 处理字符串权限
|
||||
if (typeof permissions === 'string') {
|
||||
return userPermissions.includes(permissions)
|
||||
}
|
||||
|
||||
// 处理数组权限,只要拥有任一权限就返回 true
|
||||
return permissions.some(permission => userPermissions.includes(permission))
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限指令
|
||||
*/
|
||||
const authDirective: Directive = {
|
||||
mounted(el: HTMLElement, binding: any) {
|
||||
const hasPermission = hasAuth(binding.value)
|
||||
if (!hasPermission) {
|
||||
el.style.display = 'none'
|
||||
}
|
||||
},
|
||||
updated(el: HTMLElement, binding: any) {
|
||||
const hasPermission = hasAuth(binding.value)
|
||||
if (!hasPermission) {
|
||||
el.style.display = 'none'
|
||||
} else {
|
||||
el.style.display = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限插件
|
||||
*/
|
||||
const permissionPlugin: Plugin = {
|
||||
install(app: App) {
|
||||
// 注册 v-auth 指令
|
||||
app.directive('auth', authDirective)
|
||||
|
||||
// 注册全局 hasAuth 函数
|
||||
app.config.globalProperties.$hasAuth = hasAuth
|
||||
|
||||
// 提供给组合式 API 使用
|
||||
app.provide('hasAuth', hasAuth)
|
||||
}
|
||||
}
|
||||
|
||||
export default permissionPlugin
|
||||
@@ -0,0 +1,11 @@
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
import { initDark } from '~/utils/dark'
|
||||
|
||||
export default defineNuxtPlugin(({ $pinia }) => {
|
||||
// 仅在客户端使用持久化插件
|
||||
if (import.meta.client) {
|
||||
// @ts-ignore
|
||||
$pinia.use(piniaPluginPersistedstate)
|
||||
initDark()
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user