/** * VIP 权限判断(全局方法) * - 默认读登录态 userInfo.vip * - 接诊/复诊等场景务必传入「履约诊所」vip,避免用错门店权益 * - 模板显隐优先用 */ import { computed, type ComputedRef, type Ref } from 'vue'; import { useUserStore } from '@vben/stores'; /** 与 xk_vip_feature.code / 等级 permissions 对齐的常用功能码 */ export const VIP_FEATURE = { MEDICAL_RECORD: 'medical_record', AI_MEDICAL_RECORD: 'ai_medical_record', AI_PRESCRIPTION: 'ai_prescription', GOLDEN_FORMULA: 'golden_formula', } as const; export type VipFeatureCode = (typeof VIP_FEATURE)[keyof typeof VIP_FEATURE] | string; export interface StoreVipInfo { level_code?: string; level_name?: string; level_weight?: number; badge_url?: string; duration_type?: string; duration_label?: string; duration_badge_url?: string; permissions?: string[]; expire_at?: number; is_lifetime?: boolean; } /** 读取当前登录用户 VIP 信息 */ export function getCurrentVip(): StoreVipInfo | null { const userStore = useUserStore(); return ((userStore.userInfo as any)?.vip as StoreVipInfo) || null; } /** * 从 get-current-store-type / 同类接口响应中取出 vip 对象 * 兼容 result / data / 已解包 */ export function pickVipFromApiPayload(payload: unknown): StoreVipInfo | null { if (!payload || typeof payload !== 'object') return null; const raw = payload as Record; const data = raw.result ?? raw.data ?? raw; if (!data || typeof data !== 'object') return null; const vip = (data as any).vip; return vip && typeof vip === 'object' ? (vip as StoreVipInfo) : null; } /** 规范化 permissions 列表 */ export function normalizeVipPermissions( vipOrPermissions?: StoreVipInfo | string[] | null, ): string[] { if (Array.isArray(vipOrPermissions)) { return vipOrPermissions.filter((x) => typeof x === 'string' && x); } if (vipOrPermissions && typeof vipOrPermissions === 'object') { const list = vipOrPermissions.permissions; return Array.isArray(list) ? list.filter((x) => typeof x === 'string' && x) : []; } const cur = getCurrentVip()?.permissions; return Array.isArray(cur) ? cur.filter((x) => typeof x === 'string' && x) : []; } /** * 是否拥有指定权限码(全局判断方法) * @param code 权限码,如 VIP_FEATURE.MEDICAL_RECORD * @param vipOrPermissions 挂号侧 vip / permissions; * - 不传:用登录态 userInfo.vip * - 显式传 null:视为无履约诊所权益(不回落登录态,避免接诊未就绪时误放行) */ export function hasVipPermission( code: string, vipOrPermissions?: StoreVipInfo | string[] | null, ): boolean { if (!code) return false; if (arguments.length < 2) { return normalizeVipPermissions().includes(code); } if (vipOrPermissions == null) { return false; } return normalizeVipPermissions(vipOrPermissions).includes(code); } /** * 等级是否不低于目标(按 level_weight;若无 weight 则仅精确匹配编码时返回 true) */ export function levelAtLeast(minCode: string, vip?: StoreVipInfo | null): boolean { const current = vip ?? getCurrentVip(); if (!current?.level_code) return false; if (current.level_code === minCode) return true; const weight = Number(current.level_weight ?? 0); const minWeight = parseLevelWeight(minCode); if (minWeight >= 0) { return weight >= minWeight; } return false; } /** 粗略解析 V0-V11 / BLACK_GOLD / CUSTOM 权重 */ function parseLevelWeight(code: string): number { if (/^V\d+$/i.test(code)) { return Number(code.slice(1)); } if (code === 'BLACK_GOLD') return 12; if (code === 'CUSTOM') return 13; return -1; } /** * 组合式:基于可选的门店 vip ref 做权限判断(接诊台推荐) * @example * const { has, canUseMedicalRecord } = useVipPermission(receptionVip) * const ok = has(VIP_FEATURE.MEDICAL_RECORD) */ export function useVipPermission( vipSource?: Ref | ComputedRef, ) { /** 传入了 vipSource 时始终走履约诊所分支(含 null=无权益) */ const has = (code: string) => { if (vipSource) { return hasVipPermission(code, vipSource.value ?? null); } return hasVipPermission(code); }; return { vip: computed(() => (vipSource ? vipSource.value : getCurrentVip())), has, hasVipPermission: has, levelAtLeast: (minCode: string) => levelAtLeast(minCode, vipSource ? vipSource.value ?? null : getCurrentVip()), canUseMedicalRecord: computed(() => has(VIP_FEATURE.MEDICAL_RECORD)), canUseAiMedicalRecord: computed(() => has(VIP_FEATURE.AI_MEDICAL_RECORD)), canUseAiPrescription: computed(() => has(VIP_FEATURE.AI_PRESCRIPTION)), canUseGoldenFormula: computed(() => has(VIP_FEATURE.GOLDEN_FORMULA)), }; } /** @deprecated 请用 useVipPermission;保留兼容旧调用 */ export function useVipGate() { return useVipPermission(); }