import { defineStore } from 'pinia' import { DEFAULT_ADMIN_ACCESS, ROLE_ACCESS_CEILING } from '~/utils/menus' export const IDENTITY_LABELS: Record = { 1: '超管', 2: '管理员', 4: '运营', 5: '渠道商', } // 身份常量(与后端 pb.Identity 一致) export const IDENTITY_ADMIN = 1 export const IDENTITY_MANAGER = 2 // ⚠️ identity=3(品牌商/代理)已于 2026-09-12 随 brand 表下线:后端不再接受新建, // 存量账号由 migrateBrandAccountsToOperator 禁用并降级为运营。这里不再导出对应常量。 export const IDENTITY_OPERATOR = 4 export const IDENTITY_DEALER = 5 // 渠道商账号 interface AppBind { id: number name: string // 部署注册名 app_name?: string // 所属应用名(应用切换按它去重) region: string } interface LoginData { account: string account_id?: number identity: number token: string access: string apps: string regions: string products: string channelid?: string appbinds: AppBind[] } const STORAGE_KEY = 'auth_state' function parseComma(val: string): string[] { if (!val) return [] return val.split(',').map((s) => s.trim()).filter(Boolean) } function saveToStorage(data: object) { if (import.meta.client) { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)) } } function loadFromStorage(): Record | null { if (!import.meta.client) return null try { const raw = localStorage.getItem(STORAGE_KEY) if (!raw) return null return JSON.parse(raw) } catch { return null } } export const useAuthStore = defineStore('auth', { state: () => ({ token: '' as string, account: '' as string, accountId: 0 as number, // 账号表 id(0 = yaml 里的引导超管,库里没有记录) identity: 0 as number, access: [] as string[], apps: [] as string[], regions: [] as string[], products: [] as string[], // 渠道分成体系的归属绑定:渠道商账号有 channelid。 // 仅用于界面收敛(隐藏无关筛选/按钮),真正的数据隔离由后端 scope 强制执行。 channelid: '' as string, appbinds: [] as AppBind[], currentAppId: 0 as number, siteTitle: '' as string, }), getters: { isSuperAdmin(): boolean { return this.identity === 1 }, isAdmin(): boolean { return this.identity === IDENTITY_ADMIN || this.identity === IDENTITY_MANAGER }, // 渠道商账号:锁定到单个渠道商 isDealer(): boolean { return this.identity === IDENTITY_DEALER }, identityLabel(): string { return IDENTITY_LABELS[this.identity] ?? '' }, isLoggedIn(): boolean { return !!this.token }, }, actions: { // 登录态落盘(三处写入共用一份,加字段只改这里) persist() { saveToStorage({ token: this.token, account: this.account, accountId: this.accountId, identity: this.identity, access: this.access, apps: this.apps, regions: this.regions, products: this.products, channelid: this.channelid, appbinds: this.appbinds, currentAppId: this.currentAppId, siteTitle: this.siteTitle, }) }, setLoginData(data: LoginData) { this.token = data.token this.account = data.account this.accountId = data.account_id ?? 0 this.identity = data.identity this.access = parseComma(data.access) this.apps = parseComma(data.apps) this.regions = parseComma(data.regions) this.products = parseComma(data.products) this.channelid = data.channelid ?? '' this.appbinds = data.appbinds ?? [] if (this.appbinds.length > 0 && !this.currentAppId) { this.currentAppId = this.appbinds[0].id } this.persist() }, setCurrentApp(id: number) { this.currentAppId = id this.persist() }, setSiteTitle(title: string) { this.siteTitle = title this.persist() }, logout() { this.token = '' this.account = '' this.accountId = 0 this.identity = 0 this.access = [] this.apps = [] this.regions = [] this.products = [] this.channelid = '' this.appbinds = [] this.currentAppId = 0 this.siteTitle = '' if (import.meta.client) { localStorage.removeItem(STORAGE_KEY) } }, restore() { const stored = loadFromStorage() if (!stored) return this.token = (stored.token as string) ?? '' this.account = (stored.account as string) ?? '' this.accountId = (stored.accountId as number) ?? 0 this.identity = (stored.identity as number) ?? 0 this.access = (stored.access as string[]) ?? [] this.apps = (stored.apps as string[]) ?? [] this.regions = (stored.regions as string[]) ?? [] this.products = (stored.products as string[]) ?? [] this.channelid = (stored.channelid as string) ?? '' this.appbinds = (stored.appbinds as AppBind[]) ?? [] this.currentAppId = (stored.currentAppId as number) ?? 0 this.siteTitle = (stored.siteTitle as string) ?? '' }, hasAccess(pageId: string): boolean { if (this.identity === IDENTITY_ADMIN) return true // 超管全量 // 2026-09-12「应用环境配置」并入「服务与环境配置」(svcconfig):只勾过旧 id 的存量账号 // 不该因为一次合并就失去入口。反向不成立——勾了 svcconfig 本来就包含这一页。 if (pageId === 'svcconfig' && this.access.includes('appconfig')) return true // 管理员未显式配置权限时,按默认权限集放行(除 SaaS 管理 / 系统管理 外全部)。 if (this.identity === IDENTITY_MANAGER && this.access.length === 0) { return DEFAULT_ADMIN_ACCESS.includes(pageId) } // 渠道商账号:可见页面不允许超出角色上限(仪表盘/用户查询/月结算)。 // 未显式勾权限时上限即默认集,免得新开的账号空白一片。 const ceiling = ROLE_ACCESS_CEILING[this.identity] if (ceiling) { if (!ceiling.includes(pageId)) return false if (this.access.length === 0) return true } return this.access.includes(pageId) }, }, })