All files / src/stores permissions.js

70.83% Statements 85/120
38.09% Branches 32/84
60% Functions 15/25
79.79% Lines 79/99

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196                        2x 9x 9x 9x 9x 9x 9x 9x 9x 9x   9x 1x   9x 9x 9x 9x 10x                     13x 13x     13x 1x 1x 1x 1x   1x 1x   1x 1x 1x 1x 1x 1x     1x 1x 1x   1x 1x     12x 12x 9x   9x 9x 9x             9x 8x 9x 9x 9x 3x   9x 9x 9x 3x   9x       9x     9x 8x 8x   9x 9x                                               1x 1x 1x   1x 1x                                           4x 4x 4x 1x 1x 1x   4x 4x 4x 4x 4x       9x 11x                 9x                        
import { computed, ref, watch } from 'vue'
import { defineStore } from 'pinia'
import { loadDataApi } from '@/services/api'
import { useAuthStore } from './auth'
 
/**
 * 权限 store:负责拉取模块 / 用户授权数据并暴露访问与功能点判断。
 * 逻辑与 fms-vue-old2 完全一致:
 * - 管理员账号(g3soft)提权,直接可见全部启用模块
 * - 普通用户按 b_user_module / b_user_power 授权
 * - 同一主体(orgId:userId)加载结果去重,并发请求复用
 */
export const usePermissionStore = defineStore('permissions', () => {
  const authStore = useAuthStore()
  const loadedPrincipalKey = ref('')
  const modules = ref([])
  const grantedPageIds = ref([])
  const moduleCodes = ref([])
  const powerCodes = ref([])
  let loadingPromise = null
  let loadingPrincipalKey = ''
  let loadVersion = 0
 
  const moduleByCode = computed(
    () => new Map(modules.value.map((m) => [String(m.b_code || ''), m])),
  )
  const moduleById = computed(() => new Map(modules.value.map((m) => [String(m.b_id || ''), m])))
  const grantedPageSet = computed(() => new Set(grantedPageIds.value))
  const powerSet = computed(() => new Set(powerCodes.value))
  const isG3soft = computed(() => {
    return (
      String(authStore.loginInfo.user?.account || '')
        .trim()
        .toLowerCase() === 'g3soft'
    )
  })
 
  /**
   * 加载权限数据;重复调用对同一主体去重,并发调用共享同一请求。
   */
  async function load() {
    const userId = String(authStore.loginInfo.user?.id || '')
    const principalKey = `${String(authStore.userInfo.orgId || '')}:${userId}`
 
    // 管理员提权:直接加载全部启用模块,不查授权表
    if (isG3soft.value) {
      const elevatedKey = `g3soft:${principalKey}`
      Iif (loadedPrincipalKey.value === elevatedKey) return
      loadingPromise = null
      loadingPrincipalKey = ''
 
      const requestVersion = ++loadVersion
      const request = loadDataApi('s_module', 'b_canuse = 1', 'b_id ASC')
        .then((res) => {
          Iif (requestVersion !== loadVersion) return
          modules.value = res.data || []
          grantedPageIds.value = []
          moduleCodes.value = []
          powerCodes.value = []
          loadedPrincipalKey.value = elevatedKey
        })
        .finally(() => {
          Iif (requestVersion !== loadVersion) return
          loadingPromise = null
          loadingPrincipalKey = ''
        })
      loadingPromise = request
      return request
    }
 
    Iif (!/^\d+$/.test(userId)) throw new Error('登录用户 ID 无效,请重新登录')
    if (loadedPrincipalKey.value === principalKey) return
    Iif (loadingPromise && loadingPrincipalKey === principalKey) return loadingPromise
 
    const requestVersion = ++loadVersion
    loadingPrincipalKey = principalKey
    const request = Promise.all([
      loadDataApi('s_module', 'b_canuse = 1', 'b_id ASC'),
      loadDataApi('b_user_module', `b_user_id = ${userId}`, 'b_id ASC'),
      loadDataApi('b_user_power', `b_user_id = ${userId}`, 'b_id ASC'),
      loadDataApi('s_module_power', 'b_canuse = 1', 'b_id ASC'),
    ])
      .then(([moduleResponse, userModules, userPowers, powers]) => {
        if (requestVersion !== loadVersion) return
        const activeModules = moduleResponse.data || []
        const mById = new Map(activeModules.map((m) => [String(m.b_id), m]))
        const pById = new Map(powers.data.map((p) => [String(p.b_id), p]))
        const pageIds = userModules.data
          .map((item) => String(item.b_module_id || ''))
          .filter(Boolean)
        modules.value = activeModules
        grantedPageIds.value = pageIds
        moduleCodes.value = userModules.data
          .map((item) => mById.get(String(item.b_module_id))?.b_code)
          .filter(Boolean)
        powerCodes.value = userPowers.data
          .map((item) => pById.get(String(item.b_power_id)))
          .filter((p) => Boolean(p))
          .map((power) => `${String(power.b_module_id)}.${power.b_code}`)
        loadedPrincipalKey.value = principalKey
      })
      .finally(() => {
        if (requestVersion !== loadVersion) return
        loadingPromise = null
        loadingPrincipalKey = ''
      })
    loadingPromise = request
    return request
  }
 
  /**
   * 由模块编码向上查找其所属 page 类型模块的 ID。
   */
  function resolvePageId(code) {
    let module = moduleByCode.value.get(String(code || ''))
    const visited = new Set()
    while (module) {
      const moduleId = String(module.b_id || '')
      if (!moduleId || visited.has(moduleId)) return ''
      visited.add(moduleId)
      if (module.b_module_type === 'page') return moduleId
      const parentId = String(module.b_parent_id || '')
      module = parentId ? moduleById.value.get(parentId) : undefined
    }
    return ''
  }
 
  /**
   * 判断当前用户是否有某模块的访问权限(管理员恒为 true)。
   */
  function canAccess(moduleCode) {
    Iif (isG3soft.value || !moduleCode) return true
    const module = moduleByCode.value.get(String(moduleCode))
    Eif (!module) return false
    const moduleId = String(module.b_id || '')
    const pageId = resolvePageId(moduleCode)
    return grantedPageSet.value.has(moduleId) || (!!pageId && grantedPageSet.value.has(pageId))
  }
 
  /**
   * 判断当前用户是否有某模块的功能点权限。
   */
  function canPower(scopeCode, powerCode) {
    if (isG3soft.value) return true
    const module = moduleByCode.value.get(String(scopeCode || ''))
    if (!module || !canAccess(scopeCode)) return false
    const moduleId = String(module.b_id || '')
    const pageId = resolvePageId(scopeCode || '')
    return (
      powerSet.value.has(`${moduleId}.${powerCode}`) ||
      (!!pageId && powerSet.value.has(`${pageId}.${powerCode}`))
    )
  }
 
  function clear() {
    // 仅当“正在进行中的请求属于别的主体”时才作废它(如切换账号),
    // 否则保留进行中请求、只清空已加载数据,交由后续 load 结果填充。
    // 这避免登录(setSession 后守卫立即 load)被自身的 watch(clear) 误作废,导致菜单空白。
    const userId = String(authStore.loginInfo.user?.id || '')
    const principalKey = `${String(authStore.userInfo.orgId || '')}:${userId}`
    if (loadingPrincipalKey && loadingPrincipalKey !== principalKey) {
      loadVersion++
      loadingPromise = null
      loadingPrincipalKey = ''
    }
    loadedPrincipalKey.value = ''
    modules.value = []
    grantedPageIds.value = []
    moduleCodes.value = []
    powerCodes.value = []
  }
 
  // 登录主体变化(退出/切换账号)时清空权限缓存
  watch(
    () => [
      authStore.userInfo.orgId,
      authStore.loginInfo.user?.id,
      authStore.loginInfo.user?.account,
      authStore.loginInfo.token,
    ],
    clear,
  )
 
  return {
    load,
    canAccess,
    canPower,
    clear,
    modules,
    grantedPageIds,
    moduleCodes,
    powerCodes,
    isG3soft,
  }
})