190 lines
7.1 KiB
JavaScript
190 lines
7.1 KiB
JavaScript
import { computed, ref, watch } from 'vue'
|
||
import { defineStore } from 'pinia'
|
||
import { loadDataApi } from '@/services/api'
|
||
import {
|
||
buildActionPowerCode,
|
||
buildMenuPowerCode,
|
||
buildModulePowerCode,
|
||
isStandardModuleAction,
|
||
} from '@/utils/powerCodes'
|
||
import { useAuthStore } from './auth'
|
||
|
||
/**
|
||
* 权限 store:按《FMS新系统核心表结构设计》§14 读取新表并暴露访问与功能点判断。
|
||
*
|
||
* s_power 系统有哪些权限点(menu.* / module.* / action.*)
|
||
* s_user_power 当前用户拥有哪些权限点(白名单:无记录即无权限)
|
||
*
|
||
* 判定口径(与后端 SqlPermissionService 一致):
|
||
* - s_power 中不存在该权限点、或任一侧 b_canuse = 0,授权一律不生效;
|
||
* - 标准操作(read / create / update / delete / export)由「模块使用权限」统一表达,
|
||
* 有 module.* 即允许(见 @/utils/powerCodes);
|
||
* - 业务动作(审核 / 反审 / 结算 …)按 action.{模块}.{动作} 单独授权;
|
||
* - 菜单入口(menu.*)只控制「能否进入页面」,与模块权限相互独立。
|
||
*
|
||
* - 管理员账号(g3soft)提权:全部启用模块可见、一切判断放行,不查授权表;
|
||
* - 同一主体(orgId:userId)加载结果去重,并发请求复用。
|
||
*/
|
||
export const usePermissionStore = defineStore('permissions', () => {
|
||
const authStore = useAuthStore()
|
||
const loadedPrincipalKey = ref('')
|
||
const modules = ref([])
|
||
/** 当前用户拥有的权限点编码(s_user_power ∩ s_power 启用行) */
|
||
const grantedPowerCodes = ref([])
|
||
let loadingPromise = null
|
||
let loadingPrincipalKey = ''
|
||
let loadVersion = 0
|
||
|
||
// 字符串业务键:模块编码即主键 b_id
|
||
const moduleByCode = computed(() => new Map(modules.value.map((m) => [String(m.b_id || ''), m])))
|
||
const grantedPowerSet = computed(() => new Set(grantedPowerCodes.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}`
|
||
if (loadedPrincipalKey.value === elevatedKey) return
|
||
loadingPromise = null
|
||
loadingPrincipalKey = ''
|
||
|
||
const requestVersion = ++loadVersion
|
||
const request = loadDataApi('s_module', 'b_canuse = 1', 'b_id ASC')
|
||
.then((res) => {
|
||
if (requestVersion !== loadVersion) return
|
||
modules.value = res.data || []
|
||
grantedPowerCodes.value = []
|
||
loadedPrincipalKey.value = elevatedKey
|
||
})
|
||
.finally(() => {
|
||
if (requestVersion !== loadVersion) return
|
||
loadingPromise = null
|
||
loadingPrincipalKey = ''
|
||
})
|
||
loadingPromise = request
|
||
return request
|
||
}
|
||
|
||
if (loadedPrincipalKey.value === principalKey) return
|
||
if (loadingPromise && loadingPrincipalKey === principalKey) return loadingPromise
|
||
|
||
const requestVersion = ++loadVersion
|
||
loadingPrincipalKey = principalKey
|
||
// 授权表在部分机构库可能尚未建立:失败时按无授权处理,不阻塞登录主流程。
|
||
const failSafe = () => ({ data: [] })
|
||
const quotedUserId = `N'${String(userId).replaceAll("'", "''")}'`
|
||
const request = Promise.all([
|
||
loadDataApi('s_module', 'b_canuse = 1', 'b_id ASC'),
|
||
loadDataApi('s_power', 'b_canuse = 1', 'b_id ASC').catch(failSafe),
|
||
loadDataApi(
|
||
's_user_power',
|
||
`b_user_id = ${quotedUserId} AND b_canuse = 1`,
|
||
'b_power_id ASC',
|
||
).catch(failSafe),
|
||
])
|
||
.then(([moduleResponse, powerResponse, userPowerResponse]) => {
|
||
if (requestVersion !== loadVersion) return
|
||
// 只有 s_power 中启用的权限点、且用户被授权才算数(设计文档 14.2 判定规则 2)
|
||
const activePowers = new Set(
|
||
(powerResponse.data || []).map((row) => String(row.b_id || '')),
|
||
)
|
||
modules.value = moduleResponse.data || []
|
||
grantedPowerCodes.value = (userPowerResponse.data || [])
|
||
.map((row) => String(row.b_power_id || ''))
|
||
.filter((code) => code && activePowers.has(code))
|
||
loadedPrincipalKey.value = principalKey
|
||
})
|
||
.finally(() => {
|
||
if (requestVersion !== loadVersion) return
|
||
loadingPromise = null
|
||
loadingPrincipalKey = ''
|
||
})
|
||
loadingPromise = request
|
||
return request
|
||
}
|
||
|
||
/**
|
||
* 判断当前用户是否有某模块的使用权限(管理员恒为 true)。
|
||
* 白名单语义:没有 module.{模块编码} 授权即不可访问。
|
||
*/
|
||
function canAccess(moduleCode) {
|
||
if (isG3soft.value || !moduleCode) return true
|
||
if (!moduleByCode.value.has(String(moduleCode))) return false
|
||
return grantedPowerSet.value.has(buildModulePowerCode(moduleCode))
|
||
}
|
||
|
||
/**
|
||
* 判断当前用户是否有某模块的功能点权限。
|
||
* 标准操作(read / create / update / delete / export)由模块使用权限统一表达,
|
||
* 有 module.* 即允许;业务动作(audit 等)要求单独的 action.{模块}.{动作} 授权。
|
||
*/
|
||
function canPower(scopeCode, action) {
|
||
if (isG3soft.value) return true
|
||
const code = String(scopeCode || '')
|
||
const actionCode = String(action || '')
|
||
if (!code || !actionCode) return false
|
||
if (!canAccess(code)) return false
|
||
if (isStandardModuleAction(actionCode)) return true
|
||
return grantedPowerSet.value.has(buildActionPowerCode(code, actionCode))
|
||
}
|
||
|
||
/**
|
||
* 判断当前用户能否进入某个菜单页面(侧栏渲染与路由入口共用)。
|
||
* 白名单语义:没有 menu.{菜单编码} 授权即不可见、不可进入。
|
||
*/
|
||
function canEnterMenu(menuCode) {
|
||
if (isG3soft.value || !menuCode) return true
|
||
return grantedPowerSet.value.has(buildMenuPowerCode(menuCode))
|
||
}
|
||
|
||
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 = []
|
||
grantedPowerCodes.value = []
|
||
}
|
||
|
||
// 登录主体变化(退出/切换账号)时清空权限缓存
|
||
watch(
|
||
() => [
|
||
authStore.userInfo.orgId,
|
||
authStore.loginInfo.user?.id,
|
||
authStore.loginInfo.user?.account,
|
||
authStore.loginInfo.token,
|
||
],
|
||
clear,
|
||
)
|
||
|
||
return {
|
||
load,
|
||
canAccess,
|
||
canPower,
|
||
canEnterMenu,
|
||
clear,
|
||
modules,
|
||
grantedPowerCodes,
|
||
isG3soft,
|
||
}
|
||
})
|