feat: 适配5.x后端,重构前端架构
主要变更: 1. 重构API目录结构,采用模块化设计,适配5.x后端接口规范 2. 优化布局组件,提升用户界面体验 3. 增强国际化支持,完善多语言配置 4. 新增权限控制功能,支持前端路由权限管理 5. 优化认证流程,支持JWT认证机制 6. 新增中间件和插件,提升系统扩展性 7. 优化状态管理,采用更清晰的模块化设计 8. 新增搜索框、广告等组件,丰富页面功能 9. 更改包管理工具从npm到pnpm,提升依赖管理效率 10. 完善配置系统,支持多环境配置 适配5.x后端特性: - 采用新的API接口规范 - 支持JWT认证机制 - 适配后端权限控制体系 - 支持后端国际化接口 - 优化数据交互方式
This commit is contained in:
+207
-24
@@ -1,46 +1,229 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { getLoginConfig } from '@/api/auth'
|
||||
import { getWebGroupConfigs } from '~/api/site'
|
||||
import { navigateTo } from 'nuxt/app'
|
||||
|
||||
/**
|
||||
* 登录配置接口定义
|
||||
* 用于管理登录相关的配置选项
|
||||
*/
|
||||
interface loginConfig {
|
||||
is_username: number | boolean,
|
||||
is_mobile: number | boolean,
|
||||
is_auth_register: number | boolean,
|
||||
is_bind_mobile: number | boolean,
|
||||
agreement_show: number | boolean
|
||||
is_username: number | boolean, // 是否启用用户名登录 (1/0 或 true/false)
|
||||
is_mobile: number | boolean, // 是否启用手机号登录 (1/0 或 true/false)
|
||||
is_auth_register: number | boolean, // 是否启用授权注册 (1/0 或 true/false)
|
||||
is_bind_mobile: number | boolean, // 是否启用绑定手机号 (1/0 或 true/false)
|
||||
is_wechat_scan: number | boolean, // 是否启用微信扫描登录 (1/0 或 true/false)
|
||||
agreement_show: number | boolean // 是否显示用户协议 (1/0 或 true/false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 多语言配置接口定义
|
||||
* 用于管理多语言相关的配置选项
|
||||
*/
|
||||
interface languageConfig {
|
||||
is_enabled: number | boolean, // 是否启用多语言支持 (1/0 或 true/false)
|
||||
default_lang: string, // 默认语言代码
|
||||
available_langs: string[] // 可用语言代码数组
|
||||
}
|
||||
|
||||
/**
|
||||
* 主题配置接口定义
|
||||
* 用于管理主题相关的配置选项
|
||||
*/
|
||||
interface themeConfig {
|
||||
is_enabled: number | boolean, // 是否启用主题切换 (1/0 或 true/false)
|
||||
default_theme: string, // 默认主题 (light/dark)
|
||||
available_themes: string[] // 可用主题数组
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付配置接口定义
|
||||
* 用于管理支付相关的配置选项
|
||||
*/
|
||||
interface paymentConfig {
|
||||
is_wechat_enabled: number | boolean, // 是否启用微信支付 (1/0 或 true/false)
|
||||
is_alipay_enabled: number | boolean // 是否启用支付宝支付 (1/0 或 true/false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索配置接口定义
|
||||
* 用于管理搜索相关的配置选项
|
||||
*/
|
||||
interface searchConfig {
|
||||
keyword: string, // 搜索引擎关键字
|
||||
keyword_enabled: number | boolean // 是否启用关键字搜索 (0/1 或 true/false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计分析配置接口定义
|
||||
* 用于管理统计分析相关的配置选项
|
||||
*/
|
||||
interface analyticsConfig {
|
||||
baidu_tongji_code: string, // 百度统计代码
|
||||
baidu_tongji_enabled: number | boolean // 是否启用百度统计 (0/1 或 true/false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 站点设置配置接口定义
|
||||
* 用于管理站点基础设置相关的配置选项
|
||||
*/
|
||||
interface siteSettingConfig {
|
||||
copyright: string, // 版权信息
|
||||
icp: string, // ICP备案号
|
||||
icp_url: string, // ICP备案链接
|
||||
network_security: string, // 公安备案号
|
||||
network_security_url: string, // 公安备案链接
|
||||
|
||||
// SEO配置数组数据
|
||||
seo_title: string, // SEO标题
|
||||
seo_keywords: string[], // SEO关键词数组
|
||||
seo_description: string, // SEO描述
|
||||
site_name: string, // 站点名称
|
||||
site_url: string, // 站点URL
|
||||
share_image: string, // 分享图片
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局配置接口定义
|
||||
* 包含应用的所有配置模块
|
||||
*/
|
||||
interface Config {
|
||||
login: loginConfig
|
||||
login: loginConfig, // 登录配置模块
|
||||
language: languageConfig, // 多语言配置模块
|
||||
theme: themeConfig, // 主题配置模块
|
||||
payment: paymentConfig, // 支付配置模块
|
||||
search: searchConfig, // 搜索配置模块
|
||||
analytics: analyticsConfig, // 统计分析配置模块
|
||||
siteSetting: siteSettingConfig // 站点设置配置模块
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置状态管理 Store
|
||||
* 使用 Pinia 管理应用的全局配置状态
|
||||
*/
|
||||
export const useConfigStore = defineStore('config', {
|
||||
state: (): Config => {
|
||||
return {
|
||||
login: {
|
||||
is_username: 1,
|
||||
is_mobile: 0,
|
||||
is_auth_register: 1,
|
||||
is_bind_mobile: 0,
|
||||
agreement_show: 0
|
||||
is_username: 1, // 默认启用用户名登录
|
||||
is_mobile: 0, // 默认禁用手机号登录
|
||||
is_auth_register: 1, // 默认启用授权注册
|
||||
is_bind_mobile: 0, // 默认禁用绑定手机号
|
||||
is_wechat_scan: 0, // 默认禁用微信扫描登录
|
||||
agreement_show: 0 // 默认不显示用户协议
|
||||
},
|
||||
|
||||
language: {
|
||||
is_enabled: 1, // 默认启用多语言支持
|
||||
default_lang: 'zh-cn', // 默认语言为中文
|
||||
available_langs: ['zh-cn', 'en'] // 可用语言
|
||||
},
|
||||
theme: {
|
||||
is_enabled: 1, // 默认启用主题切换
|
||||
default_theme: 'light', // 默认主题为浅色
|
||||
available_themes: ['light', 'dark'] // 可用主题
|
||||
},
|
||||
payment: {
|
||||
is_wechat_enabled: 1, // 默认启用微信支付
|
||||
is_alipay_enabled: 1 // 默认启用支付宝支付
|
||||
},
|
||||
search: {
|
||||
keyword: '', // 默认搜索关键字为空
|
||||
keyword_enabled: 0 // 默认不启用关键字搜索
|
||||
},
|
||||
analytics: {
|
||||
baidu_tongji_code: '', // 默认百度统计代码为空
|
||||
baidu_tongji_enabled: 0 // 默认不启用百度统计
|
||||
},
|
||||
siteSetting: {
|
||||
copyright: '', // 默认版权信息为空
|
||||
icp: '', // 默认ICP备案号为空
|
||||
icp_url: 'https://beian.miit.gov.cn/', // 默认ICP备案链接
|
||||
network_security: '', // 默认公安备案号为空
|
||||
network_security_url: '', // 默认公安备案链接为空
|
||||
|
||||
// SEO配置默认值
|
||||
seo_title: 'madong', // 默认SEO标题
|
||||
seo_keywords: ['madong', 'madong-admin', 'madong工作流'], // 默认SEO关键词数组
|
||||
seo_description: 'madong通用快速开发框架是一个基于Vue3 + TypeScript + ElementPlus的快速开发框架,提供了一个完整的后台管理系统模板', // 默认SEO描述
|
||||
site_name: 'madong', // 默认站点名称
|
||||
site_url: '', // 默认站点URL(留空自动获取)
|
||||
share_image: '/images/share-default.png' // 默认分享图片
|
||||
}
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async getLoginConfig(router: any = null) {
|
||||
await getLoginConfig().then(({ data }:any) => {
|
||||
this.login.is_username = parseInt(data.is_username)
|
||||
this.login.is_mobile = parseInt(data.is_mobile)
|
||||
this.login.is_auth_register = parseInt(data.is_auth_register)
|
||||
this.login.is_bind_mobile = parseInt(data.is_bind_mobile)
|
||||
this.login.agreement_show = parseInt(data.agreement_show)
|
||||
if(data && router && router.currentRoute.value.path === '/site/close'){
|
||||
navigateTo('/', { replace: true })
|
||||
/**
|
||||
* 获取 Web 分组配置
|
||||
* @description 一次性获取所有 Web 分组配置(站点设置、搜索、统计分析、登录配置等)
|
||||
* @param router - 可选的路由器实例,用于页面跳转控制
|
||||
*/
|
||||
async getWebGroupConfigs(router?: any) {
|
||||
try {
|
||||
const response = await getWebGroupConfigs()
|
||||
const data = response?.data || response || {}
|
||||
|
||||
// 更新登录配置
|
||||
if (data.web_login_config && !Array.isArray(data.web_login_config)) {
|
||||
this.login.is_username = parseInt(data.web_login_config.is_username)
|
||||
this.login.is_mobile = parseInt(data.web_login_config.is_mobile)
|
||||
this.login.is_auth_register = parseInt(data.web_login_config.is_auth_register)
|
||||
this.login.is_bind_mobile = parseInt(data.web_login_config.is_bind_mobile)
|
||||
this.login.is_wechat_scan = parseInt(data.web_login_config.is_wechat_scan)
|
||||
this.login.agreement_show = parseInt(data.web_login_config.agreement_show)
|
||||
|
||||
// 如果当前在关闭页面且有配置数据,自动跳转到首页
|
||||
if(data.web_login_config && router && router.currentRoute.value.path === '/site/close'){
|
||||
navigateTo('/', { replace: true })
|
||||
}
|
||||
}
|
||||
}).catch(() => {
|
||||
})
|
||||
|
||||
// 更新站点设置
|
||||
if (data.web_site_setting && !Array.isArray(data.web_site_setting)) {
|
||||
this.siteSetting.copyright = data.web_site_setting.copyright || ''
|
||||
this.siteSetting.icp = data.web_site_setting.icp || ''
|
||||
this.siteSetting.icp_url = data.web_site_setting.icp_url || 'https://beian.miit.gov.cn/'
|
||||
this.siteSetting.network_security = data.web_site_setting.network_security || ''
|
||||
this.siteSetting.network_security_url = data.web_site_setting.network_security_url || ''
|
||||
|
||||
// 更新SEO配置(支持数组形式)
|
||||
this.siteSetting.seo_title = data.web_site_setting.seo_title || 'madong 通用快速框架'
|
||||
this.siteSetting.seo_keywords = data.web_site_setting.seo_keywords || ['madong', 'madong-admin', 'ingenious','工作流引擎']
|
||||
this.siteSetting.seo_description = data.web_site_setting.seo_description || '专业开源服务平台'
|
||||
this.siteSetting.site_name = data.web_site_setting.site_name || 'madong'
|
||||
this.siteSetting.site_url = data.web_site_setting.site_url || (typeof window !== 'undefined' ? window.location.origin : '')
|
||||
this.siteSetting.share_image = data.web_site_setting.share_image || '/images/share-default.png'
|
||||
}
|
||||
|
||||
// 更新搜索配置
|
||||
if (data.web_search_config && !Array.isArray(data.web_search_config)) {
|
||||
this.search.keyword = data.web_search_config.keyword || ''
|
||||
this.search.keyword_enabled = parseInt(data.web_search_config.keyword_enabled)
|
||||
}
|
||||
|
||||
// 更新统计分析配置
|
||||
if (data.web_analytics_config && !Array.isArray(data.web_analytics_config)) {
|
||||
this.analytics.baidu_tongji_code = data.web_analytics_config.baidu_tongji_code || ''
|
||||
this.analytics.baidu_tongji_enabled = parseInt(data.web_analytics_config.baidu_tongji_enabled)
|
||||
}
|
||||
|
||||
// 可继续添加其他配置的处理
|
||||
// 如有新的 web 分组配置,在此添加
|
||||
} catch (error) {
|
||||
console.error('获取 Web 分组配置失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化所有配置
|
||||
* @description 一次性获取所有配置信息
|
||||
*/
|
||||
async initAllConfigs(router?: any) {
|
||||
await this.getWebGroupConfigs(router)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 默认导出配置Store
|
||||
* 可在组件中使用 useConfigStore() 来访问配置状态
|
||||
*/
|
||||
export default useConfigStore
|
||||
|
||||
@@ -1,33 +1,56 @@
|
||||
export interface Globals {
|
||||
// 防抖计时器
|
||||
lazy: NodeJS.Timeout | null
|
||||
// 随机数生成时的唯一性自增种子
|
||||
unique: Ref<number>
|
||||
// 语言包懒加载句柄
|
||||
loadLangHandle: Record<string, any>
|
||||
}
|
||||
|
||||
export interface Menus {
|
||||
id: number
|
||||
name: string
|
||||
type: string
|
||||
path?: string
|
||||
title: string
|
||||
url: string
|
||||
icon: string
|
||||
meta: {
|
||||
/**
|
||||
* 在 meta 中存储多个可能用到的属性
|
||||
* 兼容 RouteRecordRaw 和 RouteLocationNormalizedLoaded 类型
|
||||
*/
|
||||
id: number
|
||||
type: string
|
||||
menu_type: 'tab' | 'link' | 'iframe'
|
||||
}
|
||||
children: Menus[]
|
||||
export enum MenuType {
|
||||
DIRECTORY = 'directory',
|
||||
PAGE = 'page',
|
||||
LINK = 'link',
|
||||
IFRAME = 'iframe'
|
||||
}
|
||||
|
||||
export interface SiteConfig {
|
||||
export enum MenuDisplayType {
|
||||
TAB = 'tab',
|
||||
LINK = 'link',
|
||||
IFRAME = 'iframe'
|
||||
}
|
||||
|
||||
export enum LinkTarget {
|
||||
SELF = '_self',
|
||||
BLANK = '_blank',
|
||||
PARENT = '_parent',
|
||||
TOP = '_top'
|
||||
}
|
||||
|
||||
export interface MenuMeta {
|
||||
type: MenuType
|
||||
target?: LinkTarget
|
||||
hidden?: boolean
|
||||
disabled?: boolean
|
||||
badge?: string | number
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface Menus {
|
||||
id: string
|
||||
name: string
|
||||
type: MenuType
|
||||
path?: string
|
||||
title: string
|
||||
url?: string
|
||||
icon?: string
|
||||
meta: MenuMeta
|
||||
children?: Menus[]
|
||||
sort?: number
|
||||
belong?: string
|
||||
belong_id?: number
|
||||
permissions?: string[]
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface SiteConfig {
|
||||
site_name: string
|
||||
record_number?: string
|
||||
version: string
|
||||
@@ -59,18 +82,11 @@ export interface Member {
|
||||
}
|
||||
|
||||
export interface PersonalCenter {
|
||||
// 是否开启会员中心
|
||||
open: boolean
|
||||
// 从后台加载到的会员中心菜单数据
|
||||
user_menus: Menus[]
|
||||
// 是否显示一级菜单标题(当有多个一级菜单分组时显示)
|
||||
show_headline: boolean
|
||||
// 权限节点
|
||||
auth_node: Map<string, string[]>
|
||||
// 收缩布局(小屏设备)
|
||||
shrink: boolean
|
||||
// 菜单展开状态(小屏设备)
|
||||
menu_expand: boolean
|
||||
// 顶栏会员菜单下拉项
|
||||
nav_user_menus: Menus[]
|
||||
}
|
||||
|
||||
+47
-39
@@ -1,71 +1,79 @@
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import { MEMBER_KEY } from './constant/keys'
|
||||
import { getMemberProfile } from '~/api/member'
|
||||
import { navigateTo } from 'nuxt/app'
|
||||
|
||||
|
||||
interface Member {
|
||||
token: string | null
|
||||
refreshToken: string | null
|
||||
info: Record<string, any> | null,
|
||||
loginPopup:boolean
|
||||
}
|
||||
|
||||
|
||||
const info={
|
||||
"id": 1001,
|
||||
"username": "john_doe",
|
||||
"nickname": "John",
|
||||
"email": "john.doe@example.com",
|
||||
"mobile": "13800138000",
|
||||
"gender": 1,
|
||||
"birthday": "1990-05-15",
|
||||
"money": 1500.5,
|
||||
"score": 2500,
|
||||
"avatar": "",
|
||||
"last_login_time": "2024-05-20T14:30:00Z",
|
||||
"last_login_ip": "192.168.1.100",
|
||||
"motto": "Stay curious, keep learning",
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMDAxIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
|
||||
"refresh_token": "rt_abc123def456ghi789jkl012mno345pqr678stu901vwx234yz"
|
||||
loginPopup: boolean
|
||||
}
|
||||
|
||||
|
||||
export const useMemberStore = defineStore('member', {
|
||||
state: (): Member => {
|
||||
return {
|
||||
token: useCookie('token').value||'',
|
||||
info: info,
|
||||
loginPopup:false
|
||||
token: '',
|
||||
refreshToken: '',
|
||||
info: null,
|
||||
loginPopup: false
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async setToken(token: string) {
|
||||
async setToken(token: string, refreshToken: string) {
|
||||
this.token = token
|
||||
useCookie('token').value = token
|
||||
await this.getMemberInfo()
|
||||
this.refreshToken = refreshToken
|
||||
this.info = null
|
||||
// 静默获取用户信息,不阻塞 token 设置
|
||||
// 如果失败不执行 logout,保留 refreshToken 以便后续刷新
|
||||
this.getMemberInfoSilent()
|
||||
},
|
||||
async getMemberInfo() {
|
||||
if (!this.token) return
|
||||
if (!this.token) {
|
||||
return
|
||||
}
|
||||
await getMemberProfile()
|
||||
.then((res: any) => {
|
||||
this.info = res.data
|
||||
this.info = res || null
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logout()
|
||||
console.error('[getMemberInfo] Error:', err)
|
||||
// 这里不调用 logout,保留 refreshToken 以便 token 刷新机制工作
|
||||
this.info = null
|
||||
})
|
||||
},
|
||||
logout() {
|
||||
if (!this.token) return
|
||||
this.token = ''
|
||||
this.info = null
|
||||
useCookie('token').value = null
|
||||
// logout().then().catch()
|
||||
async getMemberInfoSilent() {
|
||||
// 静默获取用户信息,不抛出错误
|
||||
if (!this.token) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res: any = await getMemberProfile()
|
||||
this.info = res || null
|
||||
} catch (err) {
|
||||
console.error('[getMemberInfoSilent] Error:', err)
|
||||
// 不执行 logout,只清除 info
|
||||
this.info = null
|
||||
}
|
||||
},
|
||||
logOpen(){
|
||||
logout() {
|
||||
this.token = ''
|
||||
this.refreshToken = ''
|
||||
this.info = null
|
||||
this.loginPopup = false
|
||||
// 跳转到首页
|
||||
navigateTo('/')
|
||||
},
|
||||
logOpen() {
|
||||
this.loginPopup = true
|
||||
},
|
||||
logClose(){
|
||||
logClose() {
|
||||
this.loginPopup = false
|
||||
}
|
||||
},
|
||||
persist: {
|
||||
key: 'member-store',
|
||||
pick: ['token', 'refreshToken', 'info', 'loginPopup']
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,393 +0,0 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import type { PersonalCenter, Menus } from '~/stores/interface'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 顶栏会员菜单下拉项(nav_user_menus)- 动态菜单
|
||||
const navUserMenus: Menus[] = [
|
||||
{
|
||||
"id": 101,
|
||||
"name": "dashboard",
|
||||
"type": "menu",
|
||||
"path": "/member/dashboard",
|
||||
"title": "仪表盘",
|
||||
"url": "",
|
||||
"icon": "i-dashboard",
|
||||
"meta": {
|
||||
"id": 101,
|
||||
"type": "menu",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": 102,
|
||||
"name": "orders",
|
||||
"type": "menu",
|
||||
"path": "/member/orders",
|
||||
"title": "我的订单",
|
||||
"url": "",
|
||||
"icon": "i-shopping-cart",
|
||||
"meta": {
|
||||
"id": 102,
|
||||
"type": "menu",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": 103,
|
||||
"name": "wallet",
|
||||
"type": "menu",
|
||||
"path": "/member/wallet",
|
||||
"title": "我的钱包",
|
||||
"url": "",
|
||||
"icon": "i-wallet",
|
||||
"meta": {
|
||||
"id": 103,
|
||||
"type": "menu",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": 104,
|
||||
"name": "messages",
|
||||
"type": "menu",
|
||||
"path": "/member/messages",
|
||||
"title": "消息中心",
|
||||
"url": "",
|
||||
"icon": "i-message",
|
||||
"meta": {
|
||||
"id": 104,
|
||||
"type": "menu",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
|
||||
// 会员中心菜单(user_menus)- 完整的会员中心菜单结构
|
||||
const userMenus: Menus[] = [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "member_center",
|
||||
"type": "directory",
|
||||
"path": "/member",
|
||||
"title": "会员中心",
|
||||
"url": "",
|
||||
"icon": "i-user",
|
||||
"meta": {
|
||||
"id": 1,
|
||||
"type": "directory",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": 11,
|
||||
"name": "profile",
|
||||
"type": "menu",
|
||||
"path": "/member/profile",
|
||||
"title": "个人资料",
|
||||
"url": "",
|
||||
"icon": "i-profile",
|
||||
"meta": {
|
||||
"id": 11,
|
||||
"type": "menu",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"name": "security",
|
||||
"type": "menu",
|
||||
"path": "/member/security",
|
||||
"title": "安全设置",
|
||||
"url": "",
|
||||
"icon": "i-shield",
|
||||
"meta": {
|
||||
"id": 12,
|
||||
"type": "menu",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"name": "notification",
|
||||
"type": "menu",
|
||||
"path": "/member/notification",
|
||||
"title": "通知设置",
|
||||
"url": "",
|
||||
"icon": "i-bell",
|
||||
"meta": {
|
||||
"id": 13,
|
||||
"type": "menu",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "order_management",
|
||||
"type": "directory",
|
||||
"path": "/member/orders",
|
||||
"title": "订单管理",
|
||||
"url": "",
|
||||
"icon": "i-shopping-cart",
|
||||
"meta": {
|
||||
"id": 2,
|
||||
"type": "directory",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": 21,
|
||||
"name": "order_list",
|
||||
"type": "menu",
|
||||
"path": "/member/orders/list",
|
||||
"title": "订单列表",
|
||||
"url": "",
|
||||
"icon": "i-list",
|
||||
"meta": {
|
||||
"id": 21,
|
||||
"type": "menu",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"name": "order_history",
|
||||
"type": "menu",
|
||||
"path": "/member/orders/history",
|
||||
"title": "历史订单",
|
||||
"url": "",
|
||||
"icon": "i-history",
|
||||
"meta": {
|
||||
"id": 22,
|
||||
"type": "menu",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"name": "refund_orders",
|
||||
"type": "menu",
|
||||
"path": "/member/orders/refund",
|
||||
"title": "退款订单",
|
||||
"url": "",
|
||||
"icon": "i-refresh",
|
||||
"meta": {
|
||||
"id": 23,
|
||||
"type": "menu",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "finance",
|
||||
"type": "directory",
|
||||
"path": "/member/finance",
|
||||
"title": "财务管理",
|
||||
"url": "",
|
||||
"icon": "i-wallet",
|
||||
"meta": {
|
||||
"id": 3,
|
||||
"type": "directory",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": 31,
|
||||
"name": "balance",
|
||||
"type": "menu",
|
||||
"path": "/member/finance/balance",
|
||||
"title": "账户余额",
|
||||
"url": "",
|
||||
"icon": "i-money",
|
||||
"meta": {
|
||||
"id": 31,
|
||||
"type": "menu",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"name": "recharge",
|
||||
"type": "menu",
|
||||
"path": "/member/finance/recharge",
|
||||
"title": "在线充值",
|
||||
"url": "",
|
||||
"icon": "i-credit-card",
|
||||
"meta": {
|
||||
"id": 32,
|
||||
"type": "menu",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"name": "transaction",
|
||||
"type": "menu",
|
||||
"path": "/member/finance/transaction",
|
||||
"title": "交易记录",
|
||||
"url": "",
|
||||
"icon": "i-file-text",
|
||||
"meta": {
|
||||
"id": 33,
|
||||
"type": "menu",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "support",
|
||||
"type": "directory",
|
||||
"path": "/member/support",
|
||||
"title": "客服支持",
|
||||
"url": "",
|
||||
"icon": "i-headphones",
|
||||
"meta": {
|
||||
"id": 4,
|
||||
"type": "directory",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": 41,
|
||||
"name": "help_center",
|
||||
"type": "menu",
|
||||
"path": "/member/support/help",
|
||||
"title": "帮助中心",
|
||||
"url": "",
|
||||
"icon": "i-help-circle",
|
||||
"meta": {
|
||||
"id": 41,
|
||||
"type": "menu",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": 42,
|
||||
"name": "contact_us",
|
||||
"type": "menu",
|
||||
"path": "/member/support/contact",
|
||||
"title": "联系我们",
|
||||
"url": "",
|
||||
"icon": "i-phone",
|
||||
"meta": {
|
||||
"id": 42,
|
||||
"type": "menu",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": 43,
|
||||
"name": "feedback",
|
||||
"type": "menu",
|
||||
"path": "/member/support/feedback",
|
||||
"title": "意见反馈",
|
||||
"url": "",
|
||||
"icon": "i-message-square",
|
||||
"meta": {
|
||||
"id": 43,
|
||||
"type": "menu",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
export const usePersonalCenterStore = defineStore('personal-center', () => {
|
||||
const state: PersonalCenter = reactive({
|
||||
open: true,
|
||||
user_menus: userMenus,
|
||||
show_headline: true,
|
||||
auth_node: new Map(),
|
||||
shrink: false,
|
||||
menu_expand: false,
|
||||
nav_user_menus: navUserMenus,
|
||||
})
|
||||
|
||||
const setNavUserMenus = (menus: Menus[]) => {
|
||||
state.nav_user_menus = menus
|
||||
}
|
||||
|
||||
const mergeNavUserMenus = (menus: Menus[]) => {
|
||||
state.nav_user_menus = [...state.nav_user_menus, ...menus]
|
||||
}
|
||||
|
||||
const setAuthNode = (key: string, data: string[]) => {
|
||||
state.auth_node.set(key, data)
|
||||
}
|
||||
|
||||
const mergeAuthNode = (authNode: Map<string, string[]>) => {
|
||||
state.auth_node = new Map([...state.auth_node, ...authNode])
|
||||
}
|
||||
|
||||
const setUserMenus = (menus: Menus[]): void => {
|
||||
state.user_menus = encodeRoutesURI(menus)
|
||||
}
|
||||
|
||||
const setShowHeadline = (show: boolean): void => {
|
||||
state.show_headline = show
|
||||
}
|
||||
|
||||
const setShrink = (shrink: boolean) => {
|
||||
state.shrink = shrink
|
||||
}
|
||||
|
||||
const setStatus = (status: boolean) => {
|
||||
state.open = status
|
||||
}
|
||||
|
||||
const toggleMenuExpand = (expand = !state.menu_expand) => {
|
||||
state.menu_expand = expand
|
||||
}
|
||||
|
||||
return {
|
||||
state,
|
||||
setAuthNode,
|
||||
setNavUserMenus,
|
||||
mergeNavUserMenus,
|
||||
mergeAuthNode,
|
||||
setUserMenus,
|
||||
setShowHeadline,
|
||||
setShrink,
|
||||
setStatus,
|
||||
toggleMenuExpand,
|
||||
}
|
||||
})
|
||||
|
||||
function encodeRoutesURI(data: Menus[]) {
|
||||
data.forEach((item) => {
|
||||
if (item.meta.menu_type == 'iframe') {
|
||||
item.path = '/user/iframe/' + encodeURIComponent(item.path)
|
||||
}
|
||||
|
||||
if (item.children && item.children.length) {
|
||||
item.children = encodeRoutesURI(item.children)
|
||||
}
|
||||
})
|
||||
return data
|
||||
}
|
||||
+371
-172
@@ -1,229 +1,123 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { useNuxtApp } from 'nuxt/app'
|
||||
import storage from '@/utils/storage'
|
||||
import type { Menus } from './interface'
|
||||
import { LinkTarget, MenuType } from './interface'
|
||||
import { getNavigationList } from '~/api/site'
|
||||
import { useMemberStore } from './member'
|
||||
import { useConfigStore } from './config'
|
||||
|
||||
interface System {
|
||||
lang: string
|
||||
site: Record<string, any>
|
||||
}
|
||||
|
||||
// 支持的语言列表
|
||||
const availableLanguages = [
|
||||
{ name: 'zh-cn', value: '中文' },
|
||||
{ name: 'en', value: 'English' }
|
||||
]
|
||||
|
||||
const menus = [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "community",
|
||||
"type": "menu",
|
||||
"path": "https://madong.tech",
|
||||
"title": "社区",
|
||||
"url": "https://www.baidu.com",
|
||||
"icon": "i-dashboard",
|
||||
"meta": {
|
||||
"id": 1,
|
||||
"type": "menu",
|
||||
"menu_type": "tab"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "community",
|
||||
"type": "menu",
|
||||
"path": "/",
|
||||
"title": "模块市场",
|
||||
"url": "",
|
||||
"icon": "i-dashboard",
|
||||
"meta": {
|
||||
"id": 2,
|
||||
"type": "menu",
|
||||
"menu_type": "tab"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "docs",
|
||||
"type": "menu",
|
||||
"path": "/",
|
||||
"title": "文档",
|
||||
"url": "",
|
||||
"icon": "i-dashboard",
|
||||
"meta": {
|
||||
"id": 3,
|
||||
"type": "menu",
|
||||
"menu_type": "tab"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "demo",
|
||||
"type": "directory",
|
||||
"title": "演示站",
|
||||
"url": "",
|
||||
"icon": "i-settings",
|
||||
"meta": {
|
||||
"id": 4,
|
||||
"type": "directory",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": 201,
|
||||
"name": "demo1",
|
||||
"type": "page",
|
||||
"path": "/demo/test1",
|
||||
"title": "演示站1",
|
||||
"url": "",
|
||||
"icon": "i-users",
|
||||
"meta": {
|
||||
"id": 201,
|
||||
"type": "page",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": 202,
|
||||
"name": "demo2",
|
||||
"type": "page",
|
||||
"path": "/demo/test2",
|
||||
"title": "演示站2",
|
||||
"url": "",
|
||||
"icon": "i-users",
|
||||
"meta": {
|
||||
"id": 202,
|
||||
"type": "page",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "code",
|
||||
"type": "directory",
|
||||
"title": "代码仓库",
|
||||
"url": "",
|
||||
"icon": "i-link",
|
||||
"meta": {
|
||||
"id": 5,
|
||||
"type": "directory",
|
||||
"menu_type": "tab"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": 501,
|
||||
"name": "gitee",
|
||||
"type": "page",
|
||||
"path": "/code/gitee",
|
||||
"title": "Gitee",
|
||||
"url": "https://gitee.com",
|
||||
"icon": "i-book",
|
||||
"meta": {
|
||||
"id": 501,
|
||||
"type": "page",
|
||||
"menu_type": "link"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": 502,
|
||||
"name": "github",
|
||||
"type": "page",
|
||||
"path": "/external/support",
|
||||
"title": "GitHub",
|
||||
"url": "https://github.com",
|
||||
"icon": "i-help-circle",
|
||||
"meta": {
|
||||
"id": 502,
|
||||
"type": "page",
|
||||
"menu_type": "link"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
] as Menus[]
|
||||
|
||||
export const useSystemStore = defineStore('system', {
|
||||
state: (): System => {
|
||||
const storedLang = storage.get('lang');
|
||||
return {
|
||||
lang: storage.get('lang') ?? 'zh-cn',
|
||||
lang: storedLang ?? 'zh-cn',
|
||||
site: {
|
||||
site_name: '通用多应用管理系统后台框架',
|
||||
site_name: 'madong',
|
||||
record_number: 'test',
|
||||
version: 'v1.0.1',
|
||||
upload: {
|
||||
mode: 'local',
|
||||
},
|
||||
head_nav: menus,
|
||||
nav_menu: [],
|
||||
member_menu: [],
|
||||
initialize: false,
|
||||
user_initialize: false,
|
||||
menu_expand: false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// persist: {
|
||||
// key: 'system-store',
|
||||
// pick: ['lang', 'site']
|
||||
// },
|
||||
|
||||
getters: {
|
||||
/**
|
||||
* 获取支持的语言列表
|
||||
*/
|
||||
getLanguages: () => availableLanguages,
|
||||
|
||||
/**
|
||||
* 获取当前语言信息
|
||||
*/
|
||||
getCurrentLanguage: (state) => {
|
||||
return availableLanguages.find(lang => lang.code === state.lang) || availableLanguages[0]
|
||||
return availableLanguages.find(lang => lang.name === state.lang) || availableLanguages[0]
|
||||
},
|
||||
|
||||
headNav: (state) => {
|
||||
return state.site.nav_menu || []
|
||||
},
|
||||
|
||||
navMenu: (state) => {
|
||||
return state.site.nav_menu || []
|
||||
},
|
||||
|
||||
memberMenu: (state) => {
|
||||
return state.site.member_menu || []
|
||||
},
|
||||
|
||||
isInitialized: (state) => {
|
||||
return state.site.initialize
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
/**
|
||||
* 切换语言
|
||||
* @param name 语言名称
|
||||
*/
|
||||
// 初始化语言设置(仅在客户端执行)
|
||||
initLanguage() {
|
||||
if (process.client) {
|
||||
const storedLang = storage.get('lang');
|
||||
if (storedLang && storedLang !== this.lang) {
|
||||
this.lang = storedLang;
|
||||
this.updateI18nLocale(storedLang);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setLanguage(name: string) {
|
||||
// 验证语言代码是否支持
|
||||
if (!availableLanguages.some(lang => lang.name === name)) {
|
||||
console.warn(`不支持的语言代码: ${name}`)
|
||||
return false
|
||||
}
|
||||
|
||||
this.lang = name
|
||||
// 保存到本地存储
|
||||
storage.set({ key: 'lang', data: name })
|
||||
|
||||
// 触发 i18n 语言切换
|
||||
this.updateI18nLocale(name)
|
||||
|
||||
// 触发语言切换事件,让组件重新渲染
|
||||
this.triggerLanguageChange()
|
||||
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新 i18n 语言设置
|
||||
* @param locale 语言代码
|
||||
*/
|
||||
updateI18nLocale(locale: string) {
|
||||
|
||||
// 获取 i18n 实例并切换语言
|
||||
const nuxtApp = useNuxtApp()
|
||||
const i18n = (nuxtApp as any).$i18n
|
||||
|
||||
// 检查 i18n 的 locale 是否是响应式的
|
||||
if (i18n && i18n.locale !== locale) {
|
||||
i18n.locale = locale
|
||||
// 尝试使用不同的方式更新 locale
|
||||
if (typeof i18n.setLocale === 'function') {
|
||||
i18n.setLocale(locale)
|
||||
} else if (i18n.locale && typeof i18n.locale.value !== 'undefined') {
|
||||
i18n.locale.value = locale
|
||||
} else {
|
||||
i18n.locale = locale
|
||||
}
|
||||
|
||||
// 手动触发组件重新渲染
|
||||
if (process.client) {
|
||||
window.dispatchEvent(new CustomEvent('languageChanged', {
|
||||
detail: { locale: locale }
|
||||
}))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 触发语言切换事件
|
||||
*/
|
||||
|
||||
triggerLanguageChange() {
|
||||
// 触发自定义事件,通知组件重新渲染
|
||||
if (process.client) {
|
||||
window.dispatchEvent(new CustomEvent('languageChanged', {
|
||||
detail: { locale: this.lang }
|
||||
@@ -231,9 +125,314 @@ export const useSystemStore = defineStore('system', {
|
||||
}
|
||||
},
|
||||
|
||||
async getSiteInfoFn() {
|
||||
// 原有逻辑保持不变
|
||||
toggleMenuExpand(_expand: boolean) {
|
||||
const status = this.site.menu_expand
|
||||
this.site.menu_expand = _expand !== undefined ? _expand : !status
|
||||
},
|
||||
|
||||
async getSiteInfoFn(forceRefresh = false) {
|
||||
try {
|
||||
// 检查是否已经初始化且不需要强制刷新
|
||||
if (this.site.initialize && !forceRefresh) {
|
||||
// 确保menu_expand状态被正确恢复
|
||||
const savedMenuExpand = storage.get('system-store')?.site?.menu_expand
|
||||
if (savedMenuExpand !== undefined) {
|
||||
this.site.menu_expand = savedMenuExpand
|
||||
}
|
||||
return {
|
||||
nav_menu: this.site.nav_menu || [],
|
||||
member_menu: this.site.member_menu || []
|
||||
}
|
||||
}
|
||||
|
||||
// 保存当前的menu_expand状态
|
||||
const currentMenuExpand = this.site.menu_expand
|
||||
|
||||
// 获取后端返回的二维数组菜单
|
||||
const allMenus = await getNavigationList() as any[] || []
|
||||
|
||||
// 根据 category 字段分类菜单
|
||||
// category = 1: 导航菜单 (NAV)
|
||||
// category = 2: 会员菜单 (MEMBER)
|
||||
const navMenuItems = allMenus.filter((menu: any) => menu.category === 1) || []
|
||||
const memberMenuItems = allMenus.filter((menu: any) => menu.category === 2) || []
|
||||
|
||||
// 构建层级菜单树
|
||||
const navMenu = this.buildMenuTree(navMenuItems)
|
||||
const memberMenu = this.buildMenuTree(memberMenuItems)
|
||||
|
||||
// 初始化时不做权限过滤,因为还没有登录,没有会员信息
|
||||
// 后续会在其他地方进行权限检查
|
||||
const processedNavMenu = this.processNavMenu(navMenu)
|
||||
|
||||
// 更新状态,保留当前的menu_expand状态
|
||||
this.site = {
|
||||
...this.site,
|
||||
nav_menu: processedNavMenu,
|
||||
member_menu: memberMenu,
|
||||
initialize: true,
|
||||
menu_expand: currentMenuExpand
|
||||
}
|
||||
|
||||
// 加载前端配置(搜索、统计等)
|
||||
const configStore = useConfigStore()
|
||||
await configStore.initAllConfigs()
|
||||
|
||||
return {
|
||||
nav_menu: processedNavMenu,
|
||||
member_menu: memberMenu
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取站点信息失败:', error)
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 构建层级菜单树
|
||||
*/
|
||||
buildMenuTree(menuItems: any[]): Menus[] {
|
||||
const menuMap = new Map<string, Menus>()
|
||||
const rootMenus: Menus[] = []
|
||||
|
||||
// 第一步:转换所有菜单项为前端格式并放入映射
|
||||
menuItems.forEach((item: any) => {
|
||||
const menu: Menus = {
|
||||
id: item.id,
|
||||
name: item.name || '',
|
||||
type: this.convertMenuType(item.type),
|
||||
path: item.url || item.path || '',
|
||||
title: item.name || '',
|
||||
url: item.url || '',
|
||||
icon: item.icon || '',
|
||||
meta: {
|
||||
type: this.convertMenuType(item.type),
|
||||
target: item.target ? this.convertTarget(item.target) : LinkTarget.SELF,
|
||||
permissions: item.meta?.permissions || item.code || [],
|
||||
...(item.meta || {})
|
||||
},
|
||||
children: []
|
||||
}
|
||||
// 统一 ID 类型为字符串,避免类型不匹配问题
|
||||
menuMap.set(String(item.id), menu)
|
||||
})
|
||||
|
||||
// 第二步:构建树结构
|
||||
menuItems.forEach((item: any) => {
|
||||
const menu = menuMap.get(String(item.id))
|
||||
if (!menu) return
|
||||
|
||||
// 检查是否为根菜单:pid 为 0、"0" 或空值
|
||||
const pid = String(item.pid).trim()
|
||||
if (pid === "0" || !pid) {
|
||||
// 根菜单
|
||||
rootMenus.push(menu)
|
||||
} else {
|
||||
// 子菜单
|
||||
const parent = menuMap.get(pid)
|
||||
if (parent) {
|
||||
if (!parent.children) {
|
||||
parent.children = []
|
||||
}
|
||||
parent.children.push(menu)
|
||||
} else {
|
||||
// 如果父菜单不存在,将当前菜单作为根菜单处理
|
||||
rootMenus.push(menu)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 第三步:排序菜单
|
||||
return this.sortMenus(rootMenus)
|
||||
},
|
||||
|
||||
/**
|
||||
* 处理导航菜单
|
||||
*/
|
||||
processNavMenu(navMenu: Menus[]): Menus[] {
|
||||
const processedMenu = [...navMenu]
|
||||
const hasHomeMenu = processedMenu.some((menu: Menus) => menu.url === '/' || menu.path === '/')
|
||||
|
||||
if (!hasHomeMenu) {
|
||||
const homeMenu: Menus = {
|
||||
id: '-1',
|
||||
name: '首页',
|
||||
type: MenuType.PAGE,
|
||||
path: '/',
|
||||
title: '首页',
|
||||
url: '/',
|
||||
meta: {
|
||||
type: MenuType.PAGE,
|
||||
target: LinkTarget.SELF
|
||||
}
|
||||
}
|
||||
processedMenu.unshift(homeMenu)
|
||||
} else {
|
||||
const homeIndex = processedMenu.findIndex((menu: Menus) => menu.url === '/' || menu.path === '/')
|
||||
if (homeIndex > 0) {
|
||||
const homeMenu = processedMenu.splice(homeIndex, 1)[0]
|
||||
if (homeMenu) {
|
||||
processedMenu.unshift(homeMenu)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return processedMenu
|
||||
},
|
||||
|
||||
/**
|
||||
* 转换菜单类型
|
||||
*/
|
||||
convertMenuType(type: any): MenuType {
|
||||
const typeMap: Record<string, MenuType> = {
|
||||
1: MenuType.DIRECTORY, // 目录
|
||||
2: MenuType.PAGE, // 导航菜单(内部链接)
|
||||
3: MenuType.LINK, // 外部链接
|
||||
4: MenuType.PAGE // 单页面(内部链接)
|
||||
}
|
||||
return typeMap[type] || MenuType.PAGE
|
||||
},
|
||||
|
||||
/**
|
||||
* 转换目标窗口
|
||||
*/
|
||||
convertTarget(target: any): LinkTarget {
|
||||
const targetMap: Record<string, LinkTarget> = {
|
||||
1: LinkTarget.SELF,
|
||||
2: LinkTarget.BLANK
|
||||
}
|
||||
return targetMap[target] || LinkTarget.SELF
|
||||
},
|
||||
|
||||
/**
|
||||
* 排序菜单
|
||||
*/
|
||||
sortMenus(menus: Menus[]): Menus[] {
|
||||
return menus.sort((a, b) => (a.sort || 0) - (b.sort || 0)).map(menu => {
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
menu.children = this.sortMenus(menu.children)
|
||||
}
|
||||
return menu
|
||||
})
|
||||
},
|
||||
|
||||
isExternalLink(menu: Menus): boolean {
|
||||
return menu.type === MenuType.LINK && !!menu.url
|
||||
},
|
||||
|
||||
isDirectory(menu: Menus): boolean {
|
||||
return menu.type === MenuType.DIRECTORY
|
||||
},
|
||||
|
||||
hasChildren(menu: Menus): boolean {
|
||||
return !!menu.children && menu.children.length > 0
|
||||
},
|
||||
|
||||
getTarget(menu: Menus): string {
|
||||
return menu.meta?.target || '_self'
|
||||
},
|
||||
|
||||
shouldNavigate(menu: Menus): boolean {
|
||||
if (this.isDirectory(menu)) {
|
||||
return false
|
||||
}
|
||||
if (this.isExternalLink(menu)) {
|
||||
return false
|
||||
}
|
||||
return !!menu.path
|
||||
},
|
||||
|
||||
findMenuById(id: number, menus?: Menus[]): Menus | null {
|
||||
const menuList = menus || this.headNav
|
||||
|
||||
for (const menu of menuList) {
|
||||
if (menu.id === id) {
|
||||
return menu
|
||||
}
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
const found = this.findMenuById(id, menu.children)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
findMenuByPath(path: string, menus?: Menus[]): Menus | null {
|
||||
const menuList = menus || this.headNav
|
||||
|
||||
for (const menu of menuList) {
|
||||
if (menu.path === path) {
|
||||
return menu
|
||||
}
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
const found = this.findMenuByPath(path, menu.children)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
getMenuTarget(menu: Menus): string | null {
|
||||
if (this.isDirectory(menu)) {
|
||||
return null
|
||||
}
|
||||
if (this.isExternalLink(menu)) {
|
||||
return menu.url || null
|
||||
}
|
||||
return menu.path || null
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查菜单权限
|
||||
*/
|
||||
checkMenuPermission(menu: Menus): boolean {
|
||||
const memberStore = useMemberStore()
|
||||
const permissions = memberStore.info?.permissions || []
|
||||
|
||||
// 如果用户有权限码 [*],表示所有权限都有
|
||||
if (permissions.includes('*')) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 获取菜单权限码(支持字符串和数组)
|
||||
let menuPermissions: string[] = []
|
||||
if (menu.meta?.permissions) {
|
||||
if (typeof menu.meta.permissions === 'string') {
|
||||
// 如果是字符串,按逗号分割
|
||||
menuPermissions = menu.meta.permissions.split(',').map(p => p.trim()).filter(p => p)
|
||||
} else if (Array.isArray(menu.meta.permissions)) {
|
||||
menuPermissions = menu.meta.permissions
|
||||
}
|
||||
}
|
||||
|
||||
// 如果菜单没有设置权限码,允许访问
|
||||
if (menuPermissions.length === 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查用户是否有菜单所需的任一权限
|
||||
return menuPermissions.some(permission => permissions.includes(permission))
|
||||
},
|
||||
|
||||
/**
|
||||
* 过滤有权限的菜单
|
||||
*/
|
||||
filterMenuByPermission(menus: Menus[]): Menus[] {
|
||||
return menus.filter(menu => {
|
||||
// 检查当前菜单权限
|
||||
if (!this.checkMenuPermission(menu)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 递归检查子菜单
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
menu.children = this.filterMenuByPermission(menu.children)
|
||||
// 如果子菜单都被过滤掉了,当前菜单也不显示
|
||||
return menu.children.length > 0
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user