20260813222656
This commit is contained in:
1 parent
ab30604fe4
commit
68de86aee9
3668 files changed
+3921
-216192
No files matched your search
@@ -0,0 +1,144 @@
|
||||
import { ref } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
import { loadDataApi } from "@/services/api";
|
||||
|
||||
const createDefaultVisitedTabs = () => [
|
||||
{ key: "/dashboard", label: "工作台", i18nKey: "common.dashboard", closable: false },
|
||||
];
|
||||
|
||||
export const useAppStore = defineStore(
|
||||
"app",
|
||||
() => {
|
||||
const locale = ref("");
|
||||
const languages = ref([]);
|
||||
const languagesLoading = ref(false);
|
||||
const translations = ref({});
|
||||
const translationLoading = ref(false);
|
||||
const sidebarCollapsed = ref(false);
|
||||
const visitedTabs = ref(createDefaultVisitedTabs());
|
||||
const contentMaximized = ref(false);
|
||||
const routeRefreshKeys = ref({});
|
||||
|
||||
function toggleSidebar() {
|
||||
sidebarCollapsed.value = !sidebarCollapsed.value;
|
||||
}
|
||||
|
||||
function toggleContentMaximized() {
|
||||
contentMaximized.value = !contentMaximized.value;
|
||||
}
|
||||
|
||||
function refreshRoute(routeKey) {
|
||||
const key = String(routeKey || "");
|
||||
routeRefreshKeys.value[key] = (routeRefreshKeys.value[key] || 0) + 1;
|
||||
}
|
||||
|
||||
function getRouteRefreshKey(routeKey) {
|
||||
return routeRefreshKeys.value[String(routeKey || "")] || 0;
|
||||
}
|
||||
|
||||
async function loadLanguages() {
|
||||
if (languagesLoading.value) return;
|
||||
languagesLoading.value = true;
|
||||
try {
|
||||
const response = await loadDataApi(
|
||||
"b_i18n_type",
|
||||
"b_canuse = 1",
|
||||
"b_default DESC, b_xh ASC, b_id ASC",
|
||||
[],
|
||||
);
|
||||
languages.value = response.data || [];
|
||||
|
||||
let selectedLanguage = languages.value.find(
|
||||
(item) => String(item.b_code) === String(locale.value),
|
||||
);
|
||||
if (!selectedLanguage) {
|
||||
const defaultLanguage = languages.value.find((item) => Number(item.b_default) === 1);
|
||||
selectedLanguage = defaultLanguage || languages.value[0];
|
||||
}
|
||||
|
||||
const code = String(selectedLanguage?.b_code || "");
|
||||
await loadTranslations(code);
|
||||
locale.value = code;
|
||||
} finally {
|
||||
languagesLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTranslations(code) {
|
||||
if (!code) {
|
||||
translations.value = {};
|
||||
return;
|
||||
}
|
||||
|
||||
translationLoading.value = true;
|
||||
try {
|
||||
const escapedCode = String(code).replaceAll("'", "''");
|
||||
const response = await loadDataApi(
|
||||
"b_i18n",
|
||||
`b_locale = N'${escapedCode}'`,
|
||||
"b_key ASC",
|
||||
[],
|
||||
);
|
||||
const values = {};
|
||||
for (const item of response.data || []) {
|
||||
if (item.b_key) values[item.b_key] = item.b_value;
|
||||
}
|
||||
translations.value = values;
|
||||
} finally {
|
||||
translationLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function setLocale(value) {
|
||||
const code = String(value || "");
|
||||
const exists = languages.value.some((item) => String(item.b_code) === code);
|
||||
if (!exists) return;
|
||||
await loadTranslations(code);
|
||||
locale.value = code;
|
||||
}
|
||||
|
||||
function t(key, fallback = "") {
|
||||
const value = translations.value[String(key || "")];
|
||||
if (value !== undefined && value !== null && value !== "") return value;
|
||||
return fallback || String(key || "");
|
||||
}
|
||||
|
||||
function addVisitedTab(tab) {
|
||||
if (!visitedTabs.value.some((item) => item.key === tab.key)) {
|
||||
visitedTabs.value.push(tab);
|
||||
}
|
||||
}
|
||||
|
||||
function removeVisitedTab(key) {
|
||||
visitedTabs.value = visitedTabs.value.filter((item) => item.key !== key);
|
||||
delete routeRefreshKeys.value[key];
|
||||
}
|
||||
|
||||
return {
|
||||
locale,
|
||||
languages,
|
||||
languagesLoading,
|
||||
translations,
|
||||
translationLoading,
|
||||
sidebarCollapsed,
|
||||
visitedTabs,
|
||||
contentMaximized,
|
||||
toggleSidebar,
|
||||
toggleContentMaximized,
|
||||
refreshRoute,
|
||||
getRouteRefreshKey,
|
||||
setLocale,
|
||||
loadLanguages,
|
||||
loadTranslations,
|
||||
t,
|
||||
addVisitedTab,
|
||||
removeVisitedTab,
|
||||
};
|
||||
},
|
||||
{
|
||||
persist: {
|
||||
key: "fms-app",
|
||||
pick: ["locale"],
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,52 @@
|
||||
import { computed, ref } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
const AUTH_STORAGE_KEY = "fms-auth";
|
||||
|
||||
try {
|
||||
// Retain a session created before persistence moved from sessionStorage.
|
||||
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 = "";
|
||||
orgId.value = "";
|
||||
user.value = null;
|
||||
}
|
||||
|
||||
return {
|
||||
token,
|
||||
orgId,
|
||||
user,
|
||||
isAuthenticated,
|
||||
setSession,
|
||||
clearSession,
|
||||
};
|
||||
},
|
||||
{
|
||||
persist: {
|
||||
key: AUTH_STORAGE_KEY,
|
||||
storage: localStorage,
|
||||
pick: ["token", "orgId", "user"],
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,164 @@
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
import { loadDataApi } from "@/services/api";
|
||||
import { useAuthStore } from "./auth";
|
||||
|
||||
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((module) => [String(module.b_code || ""), module])),
|
||||
);
|
||||
const moduleById = computed(
|
||||
() => new Map(modules.value.map((module) => [String(module.b_id || ""), module])),
|
||||
);
|
||||
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 = "";
|
||||
|
||||
// g3soft 也需要加载模块列表用于菜单渲染,权限检查直接返回 true
|
||||
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 moduleById = new Map(activeModules.map((module) => [String(module.b_id), module]));
|
||||
const powerById = new Map(powers.data.map((power) => [String(power.b_id), power]));
|
||||
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) => moduleById.get(String(item.b_module_id))?.b_code)
|
||||
.filter(Boolean);
|
||||
powerCodes.value = userPowers.data
|
||||
.map((item) => powerById.get(String(item.b_power_id)))
|
||||
.filter(Boolean)
|
||||
.map((power) => `${String(power.b_module_id)}.${power.b_code}`)
|
||||
.filter(Boolean);
|
||||
loadedPrincipalKey.value = principalKey;
|
||||
})
|
||||
.finally(() => {
|
||||
if (requestVersion !== loadVersion) return;
|
||||
loadingPromise = null;
|
||||
loadingPrincipalKey = "";
|
||||
});
|
||||
loadingPromise = request;
|
||||
return request;
|
||||
}
|
||||
|
||||
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) : null;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
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