You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

145 lines
11 KiB

// 侧边栏菜单目录(唯一权威定义)。
// 布局(layouts/default.vue)按它渲染侧栏;账号管理(pages/accounts.vue)按它渲染「菜单权限」勾选;
// 权限判定(stores/auth.ts hasAccess)与路由守卫(middleware/access.global.ts)按它取默认权限集。
// 新增页面 = 在此加一行 item,权限编辑器与守卫自动跟上。
export interface MenuItem {
id: string // 权限键(存账号 access CSV)
label: string
to: string
icon: string
}
export interface MenuGroup {
label: string
restricted?: boolean // true = 管理员默认不开放(仅超管/显式授权)
items: MenuItem[]
}
export const MENU_GROUPS: MenuGroup[] = [
{
label: '数据概览',
items: [
{ id: 'dashboard', label: '仪表盘', to: '/dashboard', icon: 'M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z' },
{ id: 'svcinspect', label: '服务巡检', to: '/svcinspect', icon: 'M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z' },
],
},
{
label: '生产管理',
items: [
// 顺序 = 生产链路的上下游:芯片厂商 → 方案商 → 代工厂 → 产品 → 发 MAC → 铺渠道。
// 这一组就是完整的六项,不多不少。
//
// ⚠️【2026-09-12 已彻底删除】品牌商管理(/factory) 与 公码管理(/code):
// 页面、console/api 接口、brand 与 factory_public_code 两张表都已删除(console 启动时 DROP),
// 不再是「注释掉但还在」的状态,放开注释也恢复不了。品牌商账号角色(identity=3) 一并下线。
// 存量账号 access CSV 里残留的 'factory'/'code' 不再对应任何菜单项,无害但也无效。
{ id: 'chipvendor', label: '芯片管理', to: '/chipvendor', icon: 'M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z' },
{ id: 'provider', label: '方案商管理', to: '/provider', icon: 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z' },
{ id: 'oemfactory', label: '工厂管理', to: '/oemfactory', icon: 'M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10' },
{ id: 'product', label: '产品管理', to: '/product', icon: 'M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4' },
// 原「生产管理」,2026-09-01 改名:这个页面实际干的就是按产品发 MAC 段/授权码并记生产批次
// (production_batch 的字段就是 startmac/endmac),叫生产管理太泛。
// ⚠️ 权限键仍是 device,不能跟着改名——它已存进各账号的 access CSV,改了等于把所有人的权限清零。
{ id: 'device', label: 'MAC生成管理', to: '/device', icon: 'M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z' },
{ id: 'channel', label: '渠道商管理', to: '/dealers', icon: 'M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z' },
],
},
{
label: '结算管理',
items: [
{ id: 'settlement', label: '月结算', to: '/settlements', icon: 'M9 7h6m-6 4h6m-6 4h4M5 3h14a2 2 0 012 2v14a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2z' },
],
},
{
label: '用户管理',
items: [
{ id: 'user', label: '用户查询', to: '/users', icon: 'M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z' },
{ id: 'userfeedback', label: '用户反馈', to: '/feedback', icon: 'M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z' },
],
},
{
label: '服务配置',
items: [
// 2026-09-12 这一组从 11 项收敛到 6 项,收敛的依据是「这个入口背后有没有活数据、
// 客户端读不读它」,逐项核过库:
// · 全局环境配置(globalconfig) —— 473 行按区域复制的凭据,且**悄悄覆盖**应用环境配置里的同名键,
// 同一个 AZURE_SPEECH_REGION/OPENAI_API_KEY 两边值不一样、运营改哪边都像没生效。
// 凭据已归一到 svc_config(见 services 的 migrate_envtosvc.go),页面下线只留重定向。
// · 应用环境配置(appconfig) —— 先并入「服务与环境配置」的「应用参数」标签页,2026-09-14 连标签页也下线:
// 客户端读的键搬进 svc_config(服务 llm_mobile_elf / app_params),config 表不再有后台裸编辑入口。
// · Agent 配置(agents) / 通话翻译配置(calltranslate) —— agent_config、call_translate_rule
// 两张表在测试机与正式机上**都是 0 行**,客户端的通话翻译选路全在端侧
// (language_manager.findBestMatchingProvider),后台这两页从未产生过数据。
// · 会议记录服务(meetingsvc) —— 2026-09-12 下架,2026-09-17 以「会议配置」的形式回来:
// 转写/总结两段的服务选型并进了会议模板那一页(翻译段仍只能改库)。
{ id: 'svcconfig', label: '第三方服务配置', to: '/serviceconfig', icon: 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z M15 12a3 3 0 11-6 0 3 3 0 016 0z' },
{ id: 'appgoods', label: '应用商品配置', to: '/appgoods', icon: 'M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z' },
{ id: 'compute', label: '会员与算力', to: '/compute', icon: 'M13 10V3L4 14h7v7l9-11h-7z' },
// 2026-09-17「会议模板」→「会议配置」:同一页里管会议转写(录音文件识别)、会议总结(大模型)、会议模板三块。
// ⚠️ 菜单 id 仍是 meettemplate:存量后台账号的 access 清单里存的是这个串,改 id 等于让这些账号丢入口。
{ id: 'meettemplate', label: '会议配置', to: '/meetconfig', icon: 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z' },
// 版本控制 / 游客显隐控制:按应用配、不分渠道(存 app_release 表)。
// 这两组开关原先埋在「SaaS 管理 → 渠道分发」里按渠道各存一份,同一个开关有 8 处能改,
// 客户端只读一处,出过「后台开了游客登录但 App 里没有」的事故,故拆到这里单独管。
// 2026-09-23:「渠道分发」并入本页,标签与路由改了,**菜单 id 仍是 appversion** ——
// 存量后台账号的 access 清单里存的是这个串,改 id 等于让这些账号丢入口
// (同「会议模板 → 会议配置」那次的处理)。只勾过旧 id 'channels' 的账号
// 由 auth.ts 的 hasAccess 兼容放行。
{ id: 'appversion', label: '版本与分发', to: '/appdist', icon: 'M12 3v12m0 0l-4-4m4 4l4-4M4 17v2a2 2 0 002 2h12a2 2 0 002-2v-2' },
{ id: 'guestaccess', label: '游客显隐控制', to: '/guestaccess', icon: 'M15 12a3 3 0 11-6 0 3 3 0 016 0z M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z' },
],
},
{
label: 'SaaS 管理',
restricted: true,
items: [
{ id: 'apps', label: '应用中心', to: '/apps', icon: 'M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10' },
{ id: 'environments', label: '环境节点', to: '/environments', icon: 'M5 3h14a2 2 0 012 2v4a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2zM5 13h14a2 2 0 012 2v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4a2 2 0 012-2zM7 7h.01M7 17h.01' },
],
},
{
label: '系统管理',
restricted: true,
items: [
{ id: 'systemconfig', label: '系统配置', to: '/systemconfig', icon: 'M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4' },
{ id: 'statlog', label: '统计日志', to: '/statlog', icon: 'M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4' },
{ id: 'admin', label: '后台账号', to: '/accounts', icon: 'M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z' },
],
},
]
// 全部菜单 id
export const ALL_MENU_IDS: string[] = MENU_GROUPS.flatMap((g) => g.items.map((m) => m.id))
// 管理员默认权限集:除 restricted 分组(SaaS 管理 / 系统管理)以外的全部菜单。
export const DEFAULT_ADMIN_ACCESS: string[] = MENU_GROUPS
.filter((g) => !g.restricted)
.flatMap((g) => g.items.map((m) => m.id))
// 渠道商账号(identity=5)默认页面集:只看自己渠道的数据与结算单,不含渠道商管理。
export const DEFAULT_DEALER_ACCESS: string[] = ['dashboard', 'user', 'settlement']
// 角色菜单上限:渠道商账号是外部角色,可见页面不允许超出默认集——
// 即便账号里存了更多 access(历史数据或误勾),也一律按上限收敛。
// 未列出的角色(管理员/运营)不设上限,按显式勾选生效。
//
// ⚠️ 2026-09-12 品牌商账号(identity=3) 下线,这里原先还有一条 `3: DEFAULT_BRAND_ACCESS`。
// 后端启动迁移已把存量 identity=3 的账号禁用并降级(migrateBrandAccountsToOperator),
// 所以不会再有这类账号登录进来落到「无上限」那一支。
export const ROLE_ACCESS_CEILING: Record<number, string[]> = {
5: DEFAULT_DEALER_ACCESS,
}
// 按路由路径反查菜单项(前缀匹配,取最长命中,供路由守卫用)。
export function menuItemByPath(path: string): MenuItem | null {
let best: MenuItem | null = null
for (const g of MENU_GROUPS) {
for (const m of g.items) {
if (path === m.to || path.startsWith(m.to + '/')) {
if (!best || m.to.length > best.to.length) best = m
}
}
}
return best
}