20260816220132
This commit is contained in:
1 parent
b6f9a4c912
commit
72993ecdcd
179 files changed
+20050
-633
No files matched your search
@@ -0,0 +1,79 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { watch } from 'vue'
|
||||
import { DEFAULT_PRIMARY } from '@/theme/presets'
|
||||
import { applyPrimaryColor, applyDarkMode } from '@/theme/index'
|
||||
|
||||
export const useAppStore = defineStore('app', {
|
||||
state: () => ({
|
||||
darkMode: false,
|
||||
primaryColor: DEFAULT_PRIMARY,
|
||||
/** 侧边栏是否折叠为图标栏 */
|
||||
sidebarCollapsed: false,
|
||||
/** 界面语言(占位:项目暂无 i18n 文案,仅保留切换状态) */
|
||||
locale: 'zh-CN',
|
||||
/** 已访问页签列表;工作台固定不可关闭 */
|
||||
visitedTabs: [{ key: '/dashboard', label: '工作台', closable: false }],
|
||||
/** 路由刷新计数表:key 为路由 fullPath,值变化即强制重渲染该路由 */
|
||||
routeRefreshKeys: {},
|
||||
}),
|
||||
|
||||
actions: {
|
||||
setDark(value) {
|
||||
this.darkMode = !!value
|
||||
},
|
||||
toggleDark() {
|
||||
this.darkMode = !this.darkMode
|
||||
},
|
||||
setPrimary(color) {
|
||||
this.primaryColor = color
|
||||
},
|
||||
toggleSidebar() {
|
||||
this.sidebarCollapsed = !this.sidebarCollapsed
|
||||
},
|
||||
setSidebar(collapsed) {
|
||||
this.sidebarCollapsed = !!collapsed
|
||||
},
|
||||
toggleLocale() {
|
||||
this.locale = this.locale === 'zh-CN' ? 'en-US' : 'zh-CN'
|
||||
},
|
||||
addVisitedTab(tab) {
|
||||
if (this.visitedTabs.some((t) => t.key === tab.key)) return
|
||||
this.visitedTabs.push(tab)
|
||||
},
|
||||
removeVisitedTab(key) {
|
||||
const index = this.visitedTabs.findIndex((t) => t.key === key)
|
||||
if (index >= 0) this.visitedTabs.splice(index, 1)
|
||||
},
|
||||
getRouteRefreshKey(key) {
|
||||
return this.routeRefreshKeys[key] ?? 0
|
||||
},
|
||||
refreshRoute(key) {
|
||||
this.routeRefreshKeys[key] = (this.routeRefreshKeys[key] ?? 0) + 1
|
||||
},
|
||||
},
|
||||
|
||||
// 持久化状态:刷新后保持主题/侧栏/语言/页签;routeRefreshKeys 不持久化(仅本次会话有效)
|
||||
persist: {
|
||||
pick: ['darkMode', 'primaryColor', 'sidebarCollapsed', 'locale', 'visitedTabs'],
|
||||
},
|
||||
})
|
||||
|
||||
// 在 store 定义后立即建立 watch,将状态同步到 DOM
|
||||
// 需在 pinia 实例激活后才能调用,故封装成初始化函数
|
||||
export function initThemeSync(pinia) {
|
||||
const store = useAppStore(pinia)
|
||||
|
||||
watch(
|
||||
() => store.darkMode,
|
||||
(val) => applyDarkMode(val),
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
() => store.primaryColor,
|
||||
(val) => applyPrimaryColor(val),
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
return store
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
const AUTH_STORAGE_KEY = 'fms-auth'
|
||||
|
||||
// 兼容旧版 sessionStorage 会话:将历史会话迁移到 localStorage
|
||||
try {
|
||||
const session = sessionStorage.getItem(AUTH_STORAGE_KEY)
|
||||
if (session && !localStorage.getItem(AUTH_STORAGE_KEY)) {
|
||||
localStorage.setItem(AUTH_STORAGE_KEY, session)
|
||||
}
|
||||
} catch {
|
||||
// Storage can be unavailable in restricted browser contexts.
|
||||
}
|
||||
|
||||
export const useAuthStore = defineStore(
|
||||
'auth',
|
||||
() => {
|
||||
const token = ref('')
|
||||
const orgId = ref('')
|
||||
const user = ref(null)
|
||||
const isAuthenticated = computed(() => Boolean(token.value))
|
||||
|
||||
function setSession(session, organizationId) {
|
||||
token.value = session.token ?? session.access_token ?? ''
|
||||
orgId.value = session.orgid ?? organizationId ?? ''
|
||||
user.value = session.user ?? session.user_info ?? null
|
||||
}
|
||||
|
||||
function clearSession() {
|
||||
token.value = ''
|
||||
}
|
||||
|
||||
return {
|
||||
token,
|
||||
orgId,
|
||||
user,
|
||||
isAuthenticated,
|
||||
setSession,
|
||||
clearSession,
|
||||
}
|
||||
},
|
||||
{
|
||||
persist: {
|
||||
key: AUTH_STORAGE_KEY,
|
||||
storage: localStorage,
|
||||
pick: ['token', 'orgId', 'user'],
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,183 @@
|
||||
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.user?.account || '')
|
||||
.trim()
|
||||
.toLowerCase() === 'g3soft'
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* 加载权限数据;重复调用对同一主体去重,并发调用共享同一请求。
|
||||
*/
|
||||
async function load() {
|
||||
const userId = String(authStore.user?.id || '')
|
||||
const principalKey = `${String(authStore.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 || []
|
||||
grantedPageIds.value = []
|
||||
moduleCodes.value = []
|
||||
powerCodes.value = []
|
||||
loadedPrincipalKey.value = elevatedKey
|
||||
})
|
||||
.finally(() => {
|
||||
if (requestVersion !== loadVersion) return
|
||||
loadingPromise = null
|
||||
loadingPrincipalKey = ''
|
||||
})
|
||||
loadingPromise = request
|
||||
return request
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(userId)) throw new Error('登录用户 ID 无效,请重新登录')
|
||||
if (loadedPrincipalKey.value === principalKey) return
|
||||
if (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) {
|
||||
if (isG3soft.value || !moduleCode) return true
|
||||
const module = moduleByCode.value.get(String(moduleCode))
|
||||
if (!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() {
|
||||
loadVersion++
|
||||
loadingPromise = null
|
||||
loadingPrincipalKey = ''
|
||||
loadedPrincipalKey.value = ''
|
||||
modules.value = []
|
||||
grantedPageIds.value = []
|
||||
moduleCodes.value = []
|
||||
powerCodes.value = []
|
||||
}
|
||||
|
||||
// 登录主体变化(退出/切换账号)时清空权限缓存
|
||||
watch(
|
||||
() => [authStore.orgId, authStore.user?.id, authStore.user?.account, authStore.token],
|
||||
clear,
|
||||
)
|
||||
|
||||
return {
|
||||
load,
|
||||
canAccess,
|
||||
canPower,
|
||||
clear,
|
||||
modules,
|
||||
grantedPageIds,
|
||||
moduleCodes,
|
||||
powerCodes,
|
||||
isG3soft,
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user