BUG A: APIKey 申请搜企业搜不到 - ApplyApiKeyDialog.vue loadCustomers 加 name 参数调后端, 不再本地 filter - 去掉 filteredCustomers computed, 表格直接用 customers - searchKey 输入加 debounce 300ms watch 调 loadCustomers BUG B: 企业列表/行业/标签管理等菜单不显示 - menu.ts filterByFrontendTruth 加 url 规范化匹配 - 后端 url 带 / (如 /user/enterprise/enterprise), MENU_LABELS 不带 / - normalizeUrl = url.replace(/^\//,"") 后再 has() 匹配 BUG C: baseURL 空串 巨变数字 nginx 不通 - request.ts baseURL:"" 改 "/api" - 巨变数字 nginx 只有 /api/ location (没独立 /sys/ /auth/ 等) - devbox nginx /api/ 也去前缀 (rewrite ^/api/(.*)\$ /\$1), 兼容 dist 验证: baseURL:"/api" ✓ devbox 实测: name=郑州巨变 返 1 条命中
111 lines
4.0 KiB
TypeScript
111 lines
4.0 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
import { ref } from 'vue'
|
|
import axios from 'axios'
|
|
import { httpGet } from '@/utils/request'
|
|
import { transformMenu, MENU_LABELS, type ResolvedMenu, type BackendMenuItem } from '@/config/menu-labels'
|
|
|
|
/**
|
|
* 菜单 Store
|
|
* W3.7 改用 httpGet 统一封装
|
|
* W2 D3 改造:从后端 /sys/menu/nav 拉真实菜单树
|
|
* - 后端返回 name=null,改前端 MENU_LABELS 映射表(详见 config/menu-labels.ts)
|
|
* - 用 transformMenu 转后端结构 -> 前端 ResolvedMenu
|
|
* - 同 menu store 暴露给 AppLayout 渲染
|
|
*/
|
|
export type MenuItem = ResolvedMenu
|
|
|
|
export const useMenuStore = defineStore('menu', () => {
|
|
const list = ref<MenuItem[]>([])
|
|
const loading = ref(false)
|
|
const error = ref<string | null>(null)
|
|
const collapsed = ref<boolean>(false)
|
|
/** 上次拉取时间(毫秒) */
|
|
const fetchedAt = ref<number>(0)
|
|
|
|
function toggleCollapsed() {
|
|
collapsed.value = !collapsed.value
|
|
}
|
|
|
|
/**
|
|
* F006 W2 D3:调 /sys/menu/nav 拿后端菜单树
|
|
* 走 vite proxy 转发到 gateway,header 带 Bearer(token 由 httpGet 拦截器自动加)
|
|
* 5 分钟内不重复拉
|
|
*/
|
|
/**
|
|
* 过滤无效菜单项(测试数据、数字名称等)
|
|
* super_admin=1 会返回所有菜单,包含很多测试/占位数据
|
|
*/
|
|
function filterInvalidMenus(items: ResolvedMenu[]): ResolvedMenu[] {
|
|
return items.filter(item => {
|
|
const title = item.title?.trim() || ''
|
|
// 过滤纯数字标题(如 "2", "70", "1111111111")
|
|
if (/^\d+$/.test(title)) return false
|
|
// 过滤"未知菜单"
|
|
if (title === '未知菜单') return false
|
|
// 过滤过长的数字串(超过8位)
|
|
if (/^\d{8,}$/.test(title)) return false
|
|
// 递归过滤子菜单
|
|
if (item.children?.length) {
|
|
item.children = filterInvalidMenus(item.children)
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
/**
|
|
* V2 R2 (方案 B 实施): 后端拉到的菜单, url 不在 MENU_LABELS 里的全砍
|
|
* 父菜单(无 url 但有 children): 递归砍子菜单, 若砍光连父也删
|
|
* 极致干净: 前端 MENU_LABELS 是真实意图, 后端 super_admin 全集只是临时数据
|
|
*/
|
|
function filterByFrontendTruth(items: ResolvedMenu[]): ResolvedMenu[] {
|
|
const allowedUrls = new Set(MENU_LABELS.map(m => m.url))
|
|
const seenUrls = new Set<string>() // W5.x.x+3.1: 跨父菜单去重 (老 vue 一个 url 可能在多个父菜单下重复)
|
|
return items
|
|
.map(item => {
|
|
// 父菜单: 无 url, 递归处理 children
|
|
if (!item.url) {
|
|
if (!item.children?.length) return null
|
|
const filtered = filterByFrontendTruth(item.children)
|
|
if (!filtered.length) return null
|
|
return { ...item, children: filtered }
|
|
}
|
|
// F002-W18 修: 后端 url 可能带 / 前缀 (如 /user/enterprise/enterprise),
|
|
// MENU_LABELS 不带 / (如 user/enterprise/enterprise), 规范化后匹配
|
|
const normalizedUrl = item.url.replace(/^\//, '')
|
|
if (!allowedUrls.has(item.url) && !allowedUrls.has(normalizedUrl)) return null
|
|
if (seenUrls.has(normalizedUrl)) return null
|
|
seenUrls.add(normalizedUrl)
|
|
return item
|
|
})
|
|
.filter((item): item is ResolvedMenu => item !== null)
|
|
}
|
|
|
|
async function fetchMenu(force = false) {
|
|
if (loading.value) return
|
|
if (!force && fetchedAt.value && Date.now() - fetchedAt.value < 5 * 60 * 1000) {
|
|
return
|
|
}
|
|
loading.value = true
|
|
error.value = null
|
|
try {
|
|
const data = await httpGet<BackendMenuItem[]>('/sys/menu/nav')
|
|
const transformed = transformMenu(Array.isArray(data) ? data : [])
|
|
const frontendTruth = filterByFrontendTruth(transformed)
|
|
list.value = filterInvalidMenus(frontendTruth)
|
|
fetchedAt.value = Date.now()
|
|
} catch (e: any) {
|
|
error.value = e?.msg || e?.message || '菜单加载失败'
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
function clear() {
|
|
list.value = []
|
|
fetchedAt.value = 0
|
|
error.value = null
|
|
}
|
|
|
|
return { list, loading, error, collapsed, fetchedAt, toggleCollapsed, fetchMenu, clear }
|
|
})
|