UNPKG

@pwc-ra/components

Version:

PwC RA shared components library

1,206 lines (1,186 loc) 57.9 kB
import React, { useMemo, createContext, useState, useRef, useEffect, useContext } from 'react'; import { Dropdown, Typography, Space, Select, Avatar, Spin, Menu, Layout, Drawer, Breadcrumb as Breadcrumb$1 } from 'antd'; import { GlobalOutlined, UserOutlined, LogoutOutlined, AppstoreOutlined, MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons'; import { useTranslation } from 'react-i18next'; import Keycloak from 'keycloak-js'; import axios from 'axios'; import { useNavigate, useLocation } from 'react-router-dom'; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __rest(s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; } function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } function __spreadArray(to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var LanguageSwitcher = function () { var i18n = useTranslation().i18n; var handleMenuClick = function (_a) { var key = _a.key; i18n.changeLanguage(key); }; var items = [ { key: 'zh', label: '中文' }, { key: 'en', label: 'English' } ]; return (React.createElement(Dropdown, { menu: { items: items, onClick: handleMenuClick }, placement: "bottomRight" }, React.createElement(GlobalOutlined, { style: { color: '#303133', fontSize: '16px', cursor: 'pointer' } }))); }; var Text$2 = Typography.Text; var Navbar = function (_a) { var _b; var logo = _a.logo, currentUser = _a.currentUser, _c = _a.loading, loading = _c === void 0 ? false : _c, selectedTenant = _a.selectedTenant, onTenantChange = _a.onTenantChange, onLogout = _a.onLogout, extra = _a.extra, leftExtra = _a.leftExtra, _d = _a.showTenantSelector, showTenantSelector = _d === void 0 ? true : _d; var userMenu = [ { key: 'userInfo', label: (React.createElement("div", { style: { padding: '4px 0' } }, React.createElement(Space, { direction: "vertical", size: 2, style: { width: '100%' } }, React.createElement(Text$2, { strong: true, style: { fontSize: '12px' } }, currentUser === null || currentUser === void 0 ? void 0 : currentUser.username), React.createElement(Text$2, { type: "secondary", style: { fontSize: '12px' } }, "\u90AE\u7BB1: ", currentUser === null || currentUser === void 0 ? void 0 : currentUser.email)))), disabled: true }, { type: 'divider' }, { key: 'profile', label: '个人信息', icon: React.createElement(UserOutlined, null), onClick: function () { } }, { key: 'logout', label: '退出登录', icon: React.createElement(LogoutOutlined, null), onClick: onLogout } ]; var handleTenantChange = function (value) { onTenantChange === null || onTenantChange === void 0 ? void 0 : onTenantChange(value); }; return (React.createElement("div", { className: "navbar", style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%', height: '100%' } }, React.createElement("div", { className: "navbar-left" }, React.createElement(Space, { size: 16, align: "center" }, logo, leftExtra)), React.createElement("div", { className: "navbar-right" }, React.createElement(Space, { size: 24, align: "center" }, extra, showTenantSelector && (React.createElement(Select, { style: { width: 200 }, placeholder: "\u8BF7\u9009\u62E9\u79DF\u6237", value: selectedTenant === null || selectedTenant === void 0 ? void 0 : selectedTenant.id, onChange: handleTenantChange, loading: loading, options: (_b = currentUser === null || currentUser === void 0 ? void 0 : currentUser.tenants) === null || _b === void 0 ? void 0 : _b.map(function (tenant) { return ({ label: "".concat(tenant.code, " - ").concat(tenant.name), value: tenant.id }); }) })), React.createElement(LanguageSwitcher, null), React.createElement(Dropdown, { menu: { items: userMenu }, placement: "bottomRight" }, React.createElement(Space, { style: { cursor: 'pointer' }, size: 8, align: "center" }, React.createElement(Avatar, { size: 32, icon: React.createElement(UserOutlined, null) }), loading ? (React.createElement(Spin, { size: "small" })) : (React.createElement(Text$2, { strong: true, style: { fontSize: '14px' } }, currentUser === null || currentUser === void 0 ? void 0 : currentUser.username)))))))); }; var Text$1 = Typography.Text; // 自定义样式 var styles$2 = { groupLabel: { fontSize: '12px', fontWeight: 'bold', textTransform: 'uppercase', letterSpacing: '1px', paddingLeft: '8px', color: 'rgba(0, 0, 0, 0.45)', } }; var ProductSidebar = function (_a) { var products = _a.products, onProductSelect = _a.onProductSelect; // 按分类对产品进行分组并转换为菜单项 var menuItems = useMemo(function () { var groups = {}; // 默认分类 var defaultCategory = '其他'; // 对产品进行分组 products.forEach(function (product) { var category = product.category || defaultCategory; if (!groups[category]) { groups[category] = []; } groups[category].push(product); }); // 转换为菜单项 return Object.entries(groups).map(function (_a) { var category = _a[0], categoryProducts = _a[1]; // 子菜单项(产品) var children = categoryProducts.map(function (product) { return ({ key: product.id, label: product.name, icon: React.createElement(AppstoreOutlined, null), }); }); // 分类菜单组 return { type: 'group', label: React.createElement(Text$1, { style: styles$2.groupLabel }, category), children: children }; }); }, [products]); var handleMenuClick = function (info) { onProductSelect(info.key.toString()); }; return (React.createElement(Menu, { mode: "inline", // 不显示当前选中项 selectedKeys: [], items: menuItems, onClick: handleMenuClick })); }; var keycloak = null; var isInitializing = false; var initKeycloak = function () { var args_1 = []; for (var _i = 0; _i < arguments.length; _i++) { args_1[_i] = arguments[_i]; } return __awaiter(void 0, __spreadArray([], args_1, true), void 0, function (configUrl) { var response, config, options, authenticated, error_1; if (configUrl === void 0) { configUrl = '/api/public/keycloak-config'; } return __generator(this, function (_a) { switch (_a.label) { case 0: // 防止并发初始化 if (isInitializing) { console.warn('Keycloak 已经在初始化中'); return [2 /*return*/, false]; } // 如果已经初始化并且有效,直接返回 if (keycloak === null || keycloak === void 0 ? void 0 : keycloak.authenticated) { return [2 /*return*/, true]; } isInitializing = true; _a.label = 1; case 1: _a.trys.push([1, 4, 5, 6]); return [4 /*yield*/, axios.get(configUrl)]; case 2: response = _a.sent(); config = response.data; if (!config.keycloakUrl || !config.realm || !config.clientId) { throw new Error("Invalid Keycloak configuration: ".concat(JSON.stringify(config))); } keycloak = new Keycloak({ url: config.keycloakUrl, realm: config.realm, clientId: config.clientId }); options = { onLoad: 'login-required', checkLoginIframe: false, enableLogging: true, pkceMethod: 'S256', silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html', useNonce: false }; return [4 /*yield*/, keycloak.init(options)]; case 3: authenticated = _a.sent(); return [2 /*return*/, authenticated]; case 4: error_1 = _a.sent(); console.error('Keycloak setup failed:', error_1); throw error_1; case 5: isInitializing = false; return [7 /*endfinally*/]; case 6: return [2 /*return*/]; } }); }); }; var ensureKeycloak = function () { if (!keycloak) { throw new Error('Keycloak not initialized'); } return keycloak; }; var login = function () { try { return ensureKeycloak().login(); } catch (error) { console.error('登录失败:', error); // 不抛出异常,避免循环 return false; } }; var logout = function () { try { return ensureKeycloak().logout(); } catch (error) { console.error('登出失败:', error); // 不抛出异常,避免循环 return false; } }; var getToken = function () { try { // 1. 检查keycloak实例是否存在 if (!keycloak) { console.warn('[Keycloak] getToken: 实例未初始化'); return null; } // 2. 检查是否已认证 if (!keycloak.authenticated) { console.warn('[Keycloak] getToken: 未认证'); return null; } // 3. 获取token var token = keycloak.token; if (!token) { console.warn('[Keycloak] getToken: token为空'); return null; } return token; } catch (error) { console.error('[Keycloak] getToken: 获取失败', error); return null; } }; var updateToken = function (minValidity) { return __awaiter(void 0, void 0, void 0, function () { var error_2; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); return [4 /*yield*/, ensureKeycloak().updateToken(minValidity)]; case 1: return [2 /*return*/, _a.sent()]; case 2: error_2 = _a.sent(); console.error('更新token失败:', error_2); // 不抛出异常,避免循环 return [2 /*return*/, false]; case 3: return [2 /*return*/]; } }); }); }; var isLoggedIn = function () { try { // 1. 首先检查keycloak实例是否存在 if (!keycloak) { console.warn('[Keycloak] 未初始化'); return false; } // 2. 检查authenticated属性 if (!keycloak.authenticated) { console.warn('[Keycloak] 未认证'); return false; } // 3. 检查token是否存在 var hasToken = !!keycloak.token; if (!hasToken) { console.warn('[Keycloak] 已认证但token不存在'); return false; } // 4. 所有检查通过 return true; } catch (error) { console.error('[Keycloak] 检查登录状态失败:', error); return false; } }; var getUsername = function () { var _a; return (_a = keycloak === null || keycloak === void 0 ? void 0 : keycloak.tokenParsed) === null || _a === void 0 ? void 0 : _a.preferred_username; }; var getRoles = function () { var _a, _b; return ((_b = (_a = keycloak === null || keycloak === void 0 ? void 0 : keycloak.tokenParsed) === null || _a === void 0 ? void 0 : _a.realm_access) === null || _b === void 0 ? void 0 : _b.roles) || []; }; // 检查Keycloak是否已初始化 var isKeycloakInitialized = function () { return !!keycloak && !!keycloak.authenticated; }; // 确保Keycloak初始化完成,如果未初始化则等待 var ensureKeycloakInitialized = function () { var args_1 = []; for (var _i = 0; _i < arguments.length; _i++) { args_1[_i] = arguments[_i]; } return __awaiter(void 0, __spreadArray([], args_1, true), void 0, function (timeout) { var error_3; if (timeout === void 0) { timeout = 5000; } return __generator(this, function (_a) { switch (_a.label) { case 0: // 如果已初始化,直接返回 if (isKeycloakInitialized()) { return [2 /*return*/, true]; } if (!(!isInitializing && !keycloak)) return [3 /*break*/, 4]; _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, initKeycloak()]; case 2: return [2 /*return*/, _a.sent()]; case 3: error_3 = _a.sent(); console.error('[Keycloak] 初始化失败:', error_3); return [2 /*return*/, false]; case 4: // 如果正在初始化中,等待完成 if (isInitializing) { return [2 /*return*/, new Promise(function (resolve) { // 创建一个定时检查的函数 var checkInterval = 100; // 每100ms检查一次 var totalWait = 0; var check = function () { // 如果初始化完成 if (isKeycloakInitialized()) { resolve(true); return; } // 如果超时 if (totalWait >= timeout) { console.warn('[Keycloak] 等待初始化超时'); resolve(false); return; } // 继续等待 totalWait += checkInterval; setTimeout(check, checkInterval); }; // 开始检查 check(); })]; } return [2 /*return*/, false]; } }); }); }; // snake_case转camelCase的工具函数 var toCamelCase = function (str) { return str.replace(/_([a-z])/g, function (_, letter) { return letter.toUpperCase(); }); }; // camelCase转snake_case的工具函数 var toSnakeCase = function (str) { return str.replace(/([A-Z])/g, function (_, letter) { return "_".concat(letter.toLowerCase()); }); }; // 检查字符串是否为snake_case格式 var isSnakeCase = function (str) { return /_[a-z]/.test(str); }; // 检查字符串是否为camelCase格式 var isCamelCase = function (str) { return /[a-z][A-Z]/.test(str); }; // 检查对象的键格式并返回结果 var detectKeyFormat = function (obj) { if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) { return 'unknown'; } var keys = Object.keys(obj); if (keys.length === 0) return 'unknown'; var snakeCount = 0; var camelCount = 0; keys.forEach(function (key) { if (isSnakeCase(key)) snakeCount++; else if (isCamelCase(key)) camelCount++; }); if (snakeCount > 0 && camelCount === 0) return 'snake_case'; if (camelCount > 0 && snakeCount === 0) return 'camelCase'; if (snakeCount > 0 || camelCount > 0) return 'mixed'; return 'unknown'; }; // 递归转换对象的键为camelCase var keysToCamelCase = function (obj) { if (obj === null || typeof obj !== 'object') { return obj; } if (Array.isArray(obj)) { return obj.map(keysToCamelCase); } var camelCaseObj = {}; Object.keys(obj).forEach(function (key) { var camelKey = toCamelCase(key); camelCaseObj[camelKey] = keysToCamelCase(obj[key]); }); return camelCaseObj; }; // 递归转换对象的键为snake_case var keysToSnakeCase = function (obj) { if (obj === null || typeof obj !== 'object') { return obj; } if (Array.isArray(obj)) { return obj.map(keysToSnakeCase); } var snakeCaseObj = {}; Object.keys(obj).forEach(function (key) { var snakeKey = toSnakeCase(key); snakeCaseObj[snakeKey] = keysToSnakeCase(obj[key]); }); return snakeCaseObj; }; // 智能转换对象键,保持原始格式或转换为所需格式 var intelligentKeyTransform = function (obj, targetFormat) { if (obj === null || typeof obj !== 'object') { return obj; } if (Array.isArray(obj)) { return obj.map(function (item) { return intelligentKeyTransform(item, targetFormat); }); } var format = detectKeyFormat(obj); // 如果已经是目标格式,则保持不变 if (format === targetFormat) { return obj; } // 如果是混合格式 或 未知格式,尝试转换所有键 if (format === 'mixed' || format === 'unknown') { return targetFormat === 'snake_case' ? keysToSnakeCase(obj) : keysToCamelCase(obj); } // 转换为目标格式 return targetFormat === 'snake_case' ? keysToSnakeCase(obj) : keysToCamelCase(obj); }; // 创建axios实例 var instance = axios.create({ baseURL: '/api', timeout: 60000, headers: { 'Content-Type': 'application/json' } }); // 定义一个特殊的键名,避免与标准配置冲突 var TRANSFORM_KEYS_PROP = '_transformKeys'; var WAIT_FOR_AUTH_PROP = '_waitForAuth'; // token最小有效期(秒) var MIN_TOKEN_VALIDITY = 60; // 设置全局默认值,是否自动转换键名 var globalTransformKeysEnabled = false; // 设置全局默认值,是否等待认证 var globalWaitForAuthEnabled = true; // 导出全局配置setter var setAutoTransformKeys = function (enabled) { globalTransformKeysEnabled = enabled; }; // 导出全局配置setter var setWaitForAuth = function (enabled) { globalWaitForAuthEnabled = enabled; }; // 请求拦截器 instance.interceptors.request.use(function (config) { return __awaiter(void 0, void 0, void 0, function () { var configWaitAuth, shouldWaitAuth, error_1, token, error_2, token, configSetting, transform, originalFormData, newFormData, entries_2, _i, entries_1, _a, key, value, transformedKey; return __generator(this, function (_b) { switch (_b.label) { case 0: configWaitAuth = config[WAIT_FOR_AUTH_PROP]; shouldWaitAuth = configWaitAuth !== undefined ? configWaitAuth !== false : globalWaitForAuthEnabled; if (!shouldWaitAuth) return [3 /*break*/, 4]; _b.label = 1; case 1: _b.trys.push([1, 3, , 4]); console.log("[HTTP] \u8BF7\u6C42 ".concat(config.url, " \u5C06\u7B49\u5F85Keycloak\u521D\u59CB\u5316\u5B8C\u6210")); return [4 /*yield*/, ensureKeycloakInitialized()]; case 2: _b.sent(); return [3 /*break*/, 4]; case 3: error_1 = _b.sent(); console.error("[HTTP] \u7B49\u5F85Keycloak\u521D\u59CB\u5316\u5931\u8D25:", error_1); return [3 /*break*/, 4]; case 4: if (!isLoggedIn()) return [3 /*break*/, 9]; _b.label = 5; case 5: _b.trys.push([5, 7, , 8]); // 检查token是否即将过期,如果是则更新 return [4 /*yield*/, updateToken(MIN_TOKEN_VALIDITY)]; case 6: // 检查token是否即将过期,如果是则更新 _b.sent(); token = getToken(); if (token) { config.headers.Authorization = "Bearer ".concat(token); } return [3 /*break*/, 8]; case 7: error_2 = _b.sent(); console.error('更新token失败:', error_2); token = getToken(); if (token) { config.headers.Authorization = "Bearer ".concat(token); } return [3 /*break*/, 8]; case 8: return [3 /*break*/, 10]; case 9: // 记录未登录的请求 console.warn("[HTTP] \u7528\u6237\u672A\u767B\u5F55\uFF0C\u8BF7\u6C42\u5C06\u4E0D\u5305\u542BAuthorization\u5934: ".concat(config.url)); _b.label = 10; case 10: configSetting = config[TRANSFORM_KEYS_PROP]; transform = configSetting !== undefined ? configSetting !== false : globalTransformKeysEnabled; // 智能转换请求数据为snake_case(如果启用) if (transform && config.data) { // 检查是否为 FormData if (config.data instanceof FormData) { originalFormData = config.data; newFormData = new FormData(); entries_2 = []; originalFormData.forEach(function (value, key) { entries_2.push([key, value]); }); for (_i = 0, entries_1 = entries_2; _i < entries_1.length; _i++) { _a = entries_1[_i], key = _a[0], value = _a[1]; transformedKey = intelligentKeyTransform(key, 'snake_case'); // 处理 value 可能为 null 的情况 if (value !== null) { newFormData.append(transformedKey, value); } else { // 对于 null 值,添加一个空字符串或其他适当的默认值 newFormData.append(transformedKey, ''); } } config.data = newFormData; } else { // 其他数据类型按原来的方式处理 config.data = intelligentKeyTransform(config.data, 'snake_case'); } } // 智能转换URL参数为snake_case(如果启用) if (transform && config.params) { config.params = intelligentKeyTransform(config.params, 'snake_case'); } return [2 /*return*/, config]; } }); }); }, function (error) { return Promise.reject(error); }); // 响应拦截器 instance.interceptors.response.use(function (response) { // 检查是否应该转换键(如果配置中未指定,则使用全局设置) var configSetting = response.config[TRANSFORM_KEYS_PROP]; var transform = configSetting !== undefined ? configSetting !== false : globalTransformKeysEnabled; // 智能转换响应数据为前端所需格式(camelCase)(如果启用) if (transform && response.data) { response.data = intelligentKeyTransform(response.data, 'camelCase'); } return response; }, function (error) { return __awaiter(void 0, void 0, void 0, function () { var configSetting, transform; var _a, _b; return __generator(this, function (_c) { // 如果是401错误,触发登录 if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 401) { console.warn('认证失败,需要重新登录'); // 触发登录流程 login(); return [2 /*return*/, Promise.reject(new Error('认证失败,正在重新登录...'))]; } configSetting = error.config ? error.config[TRANSFORM_KEYS_PROP] : undefined; transform = configSetting !== undefined ? configSetting !== false : globalTransformKeysEnabled; // 对于其他错误,如果有响应数据并且启用了转换,尝试转换格式 if (transform && ((_b = error.response) === null || _b === void 0 ? void 0 : _b.data)) { error.response.data = intelligentKeyTransform(error.response.data, 'camelCase'); } // 对于其他错误,直接返回 return [2 /*return*/, Promise.reject(error)]; }); }); }); // 处理自定义配置 var processConfig = function (config) { if (!config) return {}; // 提取自定义配置 var transformKeys = config.transformKeys, waitForAuth = config.waitForAuth, axiosConfig = __rest(config, ["transformKeys", "waitForAuth"]); // 将我们的自定义配置存储为普通属性,使用特殊的键名 if (transformKeys !== undefined) { axiosConfig[TRANSFORM_KEYS_PROP] = transformKeys; } // 保存是否等待认证的设置 if (waitForAuth !== undefined) { axiosConfig[WAIT_FOR_AUTH_PROP] = waitForAuth; } return axiosConfig; }; var http = { get: function (url, config) { var axiosConfig = processConfig(config); return instance.get(url, axiosConfig); }, post: function (url, data, config) { var axiosConfig = processConfig(config); return instance.post(url, data, axiosConfig); }, put: function (url, data, config) { var axiosConfig = processConfig(config); return instance.put(url, data, axiosConfig); }, delete: function (url, config) { var axiosConfig = processConfig(config); return instance.delete(url, axiosConfig); } }; var getCurrentUser = function () { return http.get('/auth/users/me'); }; // 初始化全局状态 var globalState = { currentUser: null, selectedTenant: null, loading: true, error: null }; // 状态变化订阅者 var subscribers = []; // 更新全局状态并通知订阅者 var updateGlobalState = function (newState) { Object.assign(globalState, newState); // 通知所有订阅者 subscribers.forEach(function (callback) { return callback(__assign({}, globalState)); }); }; // 提供全局访问方法 var PageHeaderState = { /** * 获取当前用户信息 */ getCurrentUser: function () { return globalState.currentUser; }, /** * 获取当前选中的租户 */ getSelectedTenant: function () { return globalState.selectedTenant; }, /** * 获取加载状态 */ isLoading: function () { return globalState.loading; }, /** * 获取错误信息 */ getError: function () { return globalState.error; }, /** * 监听状态变化 * @param callback 状态变化时的回调函数 * @returns 取消监听的函数 */ subscribe: function (callback) { subscribers.push(callback); // 立即执行一次回调,传入当前状态 callback(__assign({}, globalState)); // 返回取消订阅的函数 return function () { var index = subscribers.indexOf(callback); if (index > -1) { subscribers.splice(index, 1); } }; } }; var PageHeaderContext = createContext(undefined); var PageHeaderProvider = function (_a) { var children = _a.children, tenantStorageKey = _a.tenantStorageKey; console.log('PageHeaderProvider 初始化,tenantStorageKey:', tenantStorageKey); var _b = useState(null), currentUser = _b[0], setCurrentUser = _b[1]; var _c = useState(true), loading = _c[0], setLoading = _c[1]; var _d = useState(null), error = _d[0], setError = _d[1]; var _e = useState(null), selectedTenant = _e[0], setSelectedTenant = _e[1]; var initAttemptRef = useRef(0); var maxInitAttempts = 3; var isInitializing = useRef(false); // 更新内部状态的同时更新全局状态 var updateCurrentUser = function (user) { setCurrentUser(user); updateGlobalState({ currentUser: user }); }; var updateLoading = function (isLoading) { setLoading(isLoading); updateGlobalState({ loading: isLoading }); }; var updateError = function (errorMsg) { setError(errorMsg); updateGlobalState({ error: errorMsg }); }; // 从 localStorage 读取租户信息 var getStoredTenant = function () { if (!tenantStorageKey) return null; try { var stored = localStorage.getItem(tenantStorageKey); return stored ? JSON.parse(stored) : null; } catch (error) { console.warn('读取存储的租户信息失败:', error); return null; } }; // 保存租户信息到 localStorage var storeTenant = function (tenant) { if (!tenantStorageKey) return; try { if (tenant) { localStorage.setItem(tenantStorageKey, JSON.stringify(tenant)); console.log('租户信息已保存到 localStorage:', tenant); } else { localStorage.removeItem(tenantStorageKey); console.log('租户信息已从 localStorage 移除'); } } catch (error) { console.warn('保存租户信息失败:', error); } }; var updateSelectedTenant = function (tenant) { setSelectedTenant(tenant); updateGlobalState({ selectedTenant: tenant }); // 保存到 localStorage storeTenant(tenant); }; useEffect(function () { var initAuth = function () { return __awaiter(void 0, void 0, void 0, function () { var authenticated, data, tenantToSelect, storedTenant_1, isValidTenant, userError_1, err_1; return __generator(this, function (_a) { switch (_a.label) { case 0: // 防止并发初始化 if (isInitializing.current) return [2 /*return*/]; isInitializing.current = true; _a.label = 1; case 1: _a.trys.push([1, 7, 8, 9]); updateLoading(true); // 检查初始化尝试次数,避免无限循环 if (initAttemptRef.current >= maxInitAttempts) { console.error('超过最大初始化尝试次数'); updateError('认证服务暂时不可用,请稍后再试'); return [2 /*return*/]; } initAttemptRef.current += 1; return [4 /*yield*/, initKeycloak()]; case 2: authenticated = _a.sent(); if (!authenticated) { console.warn('Keycloak 认证失败,但不重定向'); updateError('认证失败,请刷新页面重试'); return [2 /*return*/]; } _a.label = 3; case 3: _a.trys.push([3, 5, , 6]); return [4 /*yield*/, getCurrentUser()]; case 4: data = (_a.sent()).data; updateCurrentUser(data); tenantToSelect = null; if (data.tenants && data.tenants.length > 0) { storedTenant_1 = getStoredTenant(); if (storedTenant_1) { isValidTenant = data.tenants.some(function (tenant) { return tenant.id === storedTenant_1.id; }); if (isValidTenant) { tenantToSelect = storedTenant_1; console.log('恢复之前选择的租户:', storedTenant_1.name); } else { console.log('存储的租户无效,选择第一个租户'); tenantToSelect = data.tenants[0]; } } else { console.log('没有存储的租户,选择第一个租户'); tenantToSelect = data.tenants[0]; } updateSelectedTenant(tenantToSelect); } updateError(null); // 重置尝试计数 initAttemptRef.current = 0; return [3 /*break*/, 6]; case 5: userError_1 = _a.sent(); console.error('获取用户信息失败:', userError_1); updateError('获取用户信息失败,请刷新页面重试'); return [3 /*break*/, 6]; case 6: return [3 /*break*/, 9]; case 7: err_1 = _a.sent(); console.error('初始化失败:', err_1); updateError('初始化失败,请刷新页面重试'); return [3 /*break*/, 9]; case 8: updateLoading(false); isInitializing.current = false; return [7 /*endfinally*/]; case 9: return [2 /*return*/]; } }); }); }; initAuth(); }, []); var value = { currentUser: currentUser, loading: loading, error: error, selectedTenant: selectedTenant, setSelectedTenant: updateSelectedTenant, logout: logout }; return (React.createElement(PageHeaderContext.Provider, { value: value }, children)); }; var usePageHeader = function () { var context = useContext(PageHeaderContext); if (context === undefined) { throw new Error('usePageHeader must be used within a PageHeaderProvider'); } return context; }; /** * 获取产品菜单数据 * @returns 处理后的产品列表 */ var getProductMenu = function () { return __awaiter(void 0, void 0, void 0, function () { var response, _a, categories, products, categoryMap_1, processedProducts, error_1; return __generator(this, function (_b) { switch (_b.label) { case 0: _b.trys.push([0, 2, , 3]); return [4 /*yield*/, axios.get('/api/public/product-menu')]; case 1: response = _b.sent(); _a = response.data, categories = _a.categories, products = _a.products; categoryMap_1 = new Map(categories.map(function (category) { return [category.id, { name: category.name, order: category.order }]; })); processedProducts = products.map(function (product) { var _a; return ({ id: product.id, name: product.name, url: product.url, category: ((_a = categoryMap_1.get(product.category)) === null || _a === void 0 ? void 0 : _a.name) || product.category, order: product.order }); }); // 先按分类排序,再按产品排序 processedProducts.sort(function (a, b) { var _a, _b; // 获取分类的排序值 var categoryOrderA = ((_a = categoryMap_1.get(a.category)) === null || _a === void 0 ? void 0 : _a.order) || 0; var categoryOrderB = ((_b = categoryMap_1.get(b.category)) === null || _b === void 0 ? void 0 : _b.order) || 0; // 先按分类排序 if (categoryOrderA !== categoryOrderB) { return categoryOrderA - categoryOrderB; } // 分类相同时按产品排序 return (a.order || 0) - (b.order || 0); }); return [2 /*return*/, processedProducts]; case 2: error_1 = _b.sent(); console.error('获取产品菜单失败:', error_1); // 返回空数组 return [2 /*return*/, []]; case 3: return [2 /*return*/]; } }); }); }; var Header = Layout.Header; // 布局常量 var HEADER_HEIGHT$1 = 50; var PRODUCT_MENU_WIDTH = 200; var PageHeaderContent = function (_a) { _a.collapsed; _a.onCollapse; var onProductChange = _a.onProductChange, extra = _a.extra, leftExtra = _a.leftExtra, logo = _a.logo, _b = _a.showTenantSelector, showTenantSelector = _b === void 0 ? true : _b, _c = _a.showProductSelector, showProductSelector = _c === void 0 ? true : _c; var _d = usePageHeader(), currentUser = _d.currentUser, loading = _d.loading, selectedTenant = _d.selectedTenant, setSelectedTenant = _d.setSelectedTenant, logout = _d.logout; var _e = useState(false), productDrawerVisible = _e[0], setProductDrawerVisible = _e[1]; var _f = useState([]), products = _f[0], setProducts = _f[1]; useEffect(function () { var fetchProductMenu = function () { return __awaiter(void 0, void 0, void 0, function () { var productData, error_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); return [4 /*yield*/, getProductMenu()]; case 1: productData = _a.sent(); // 如果API返回了数据,则使用API数据 if (productData && productData.length > 0) { setProducts(productData); } return [3 /*break*/, 3]; case 2: error_1 = _a.sent(); console.error('获取产品菜单失败:', error_1); return [3 /*break*/, 3]; case 3: return [2 /*return*/]; } }); }); }; if (showProductSelector) { fetchProductMenu(); } }, [showProductSelector]); // 依赖 showProductSelector,避免不必要的请求 var toggleProductDrawer = function () { if (showProductSelector) { setProductDrawerVisible(!productDrawerVisible); } }; var closeProductDrawer = function () { setProductDrawerVisible(false); }; var handleProductSelect = function (productId) { if (onProductChange) { onProductChange(productId); } else { // 如果没有提供回调函数,直接跳转 var product = products.find(function (p) { return p.id === productId; }); if (product) { window.location.href = product.url; } } closeProductDrawer(); }; var renderLogo = function () { // 默认 logo 标题 var defaultTitle = "PwC Privacy Ready"; // 如果提供了 logo 属性 if (logo !== undefined) { // 如果 logo 是字符串,则渲染为标题文本 if (typeof logo === 'string') { return React.createElement("h1", { style: { fontSize: '18px', margin: 0 } }, logo); } // 如果 logo 是 ReactNode,则直接渲染 return logo; } // 没有提供 logo 时使用默认标题 return React.createElement("h1", { style: { fontSize: '18px', margin: 0 } }, defaultTitle); }; var logoElement = (React.createElement("span", { style: { display: 'flex', alignItems: 'center', gap: '12px' } }, showProductSelector && (React.createElement("span", { className: "trigger", onClick: toggleProductDrawer, style: { cursor: 'pointer', fontSize: '18px' } }, productDrawerVisible ? React.createElement(MenuUnfoldOutlined, null) : React.createElement(MenuFoldOutlined, null))), renderLogo())); return (React.createElement(React.Fragment, null, React.createElement(Header, { className: "page-header", style: { position: 'fixed', top: 0, zIndex: 1002, width: '100%', height: HEADER_HEIGHT$1, lineHeight: "".concat(HEADER_HEIGHT$1, "px"), padding: '0 24px', background: '#fff', boxShadow: '0 1px 4px rgba(0,21,41,.08)' } }, React.createElement(Navbar, { logo: logoElement, currentUser: currentUser, loading: loading, selectedTenant: selectedTenant, onTenantChange: function (tenantId) { var tenant = currentUser === null || currentUser === void 0 ? void 0 : currentUser.tenants.find(function (t) { return t.id === tenantId; }); setSelectedTenant(tenant || null); }, onLogout: logout, extra: extra, leftExtra: leftExtra, showTenantSelector: showTenantSelector })), showProductSelector && (React.createElement(Drawer, { placement: "left", width: PRODUCT_MENU_WIDTH, open: productDrawerVisible, onClose: closeProductDrawer, closable: false, maskClosable: true, mask: true, style: { marginTop: HEADER_HEIGHT$1 }, rootClassName: "product-drawer", title: null, bodyStyle: { padding: 0 }, contentWrapperStyle: { boxShadow: '0 4px 12px rgba(0,0,0,0.15)' } }, React.createElement(ProductSidebar, { products: products, onProductSelect: handleProductSelect }))))); }; var PageHeader = function (props) { return React.createElement(PageHeaderContent, __assign({}, props)); }; var Sider = Layout.Sider; var Text = Typography.Text; // 默认配置 var DEFAULT_SIDEBAR_WIDTH = 200; var DEFAULT_COLLAPSED_SIDEBAR_WIDTH = 80; var DEFAULT_SIDEBAR_HEADER_HEIGHT = 50; // 内联样式 var styles$1 = { sider: { position: 'fixed', top: '50px', // 默认顶部导航高度 left: 0, bottom: 0, zIndex: 99, boxShadow: '2px 0 8px 0 rgba(29,35,41,.05)', transition: 'all 0.2s' }, siderCollapsed: { width: "".concat(DEFAULT_COLLAPSED_SIDEBAR_WIDTH, "px !important"), minWidth: "".concat(DEFAULT_COLLAPSED_SIDEBAR_WIDTH, "px !important"), maxWidth: "".concat(DEFAULT_COLLAPSED_SIDEBAR_WIDTH, "px !important"), flex: "0 0 ".concat(DEFAULT_COLLAPSED_SIDEBAR_WIDTH, "px !important") }, siderHeader: { height: "".concat(DEFAULT_SIDEBAR_HEADER_HEIGHT, "px"), lineHeight: '36px', padding: '6px 24px 0', borderBottom: '1px solid #f0f0f0', overflow: 'hidden', whiteSpace: 'nowrap', display: 'flex', alignItems: 'center' }, siderHeaderCollapsed: { padding: '6px 0 0', justifyContent: 'center' }, icon: { fontSize: '16px', color: 'rgba(0, 0, 0, 0.88)' }, text: { fontSize: '16px', fontWeight: 600, color: 'rgba(0, 0, 0, 0.88)', lineHeight: 1 }, menu: { height: '100%', fontSize: '14px', borderRight: 'none' }}; /** * 查找父级菜单键 */ var findParentKey = function (pathname, menuItems) { var pathParts = pathname.split('/').filter(function (part) { return part; }); var currentPath = pathParts.length > 0 ? pathParts[0] : ''; // 先检查顶级菜单项是否匹配 for (var _i = 0, menuItems_1 = menuItems; _i < menuItems_1.length; _i++) { var item = menuItems_1[_i]; if (item && 'key' in item && item.key === currentPath) { return item.key; } } // 再检查子菜单项是否匹配 for (var _a = 0, menuItems_2 = menuItems; _a < menuItems_2.length; _a++) { var item = menuItems_2[_a]; if (item && 'items' in item && Array.isArray(item.items)) { var matchedChild = item.items.find(function (child) { return child && 'key' in child && child.key === currentPath; }); if (matchedChild) { return item.key; } } } return undefined; }; /** * 侧边栏组件 */ var Sidebar = function (_a) { var collapsed = _a.collapsed, menuItems = _a.menuItems, productName = _a.productName, _b = _a.productIcon, productIcon = _b === void 0 ? React.createElement(AppstoreOutlined, null) : _b, _c = _a.width, width = _c === void 0 ? DEFAULT_SIDEBAR_WIDTH : _c, _d = _a.collapsedWidth, collapsedWidth = _d === void 0 ? DEFAULT_COLLAPSED_SIDEBAR_WIDTH : _d, _e = _a.headerHeight, headerHeight = _e === void 0 ? DEFAULT_SIDEBAR_HEADER_HEIGHT : _e, onMenuClick = _a.onMenuClick, _f = _a.className, className = _f === void 0 ? '' : _f, _g = _a.style, style = _g === void 0 ? {} : _g, _h = _a.showProductName, showProductName = _h === void 0 ? true : _h; var navigate = useNavigate(); var location = useLocation(); var _j = useState([]), openKeys = _j[0], setOpenKeys = _j[1]; var _k = useState([]), selectedKeys = _k[0], setSelectedKeys = _k[1]; useEffect(function () { var pathname = location.pathname; var currentPath = pathname.split('/')[1] || ''; var parentKey = findParentKey(pathname, menuItems); if (currentPath) { setSelectedKeys([currentPath]); } if (parentKey && !collapsed) { setOpenKeys(function (prev) { if (!prev.includes(parentKey)) { return __spreadArray(__spreadArray([], prev, true), [parentKey], false); } return prev; }); } }, [location.pathname, collapsed, menuItems]); var handleMenuClick = function (_a) { var key = _a.key; if (onMenuClick) { onMenuClick(key); } else { navigate("/".concat(key)); } }; var handleOpenChange = function (keys) { setOpenKeys(keys); }; return (React.createElement(Sider, { width: width, collapsedWidth: collapsedWidth, theme: "light", className: className, collapsed: collapsed, style: __assign(__assign(__assign({}, styles$1.sider), (collapsed ? styles$1.siderCollapsed : {})), style) }, showProductName && (React.createElement("div", { style: __assign(__assign({}, (collapsed ? __assign(__assign({}, styles$1.siderHeader), styles$1.siderHeaderCollapsed) : styles$1.siderHeader)), { height: "".concat(headerHeight, "px") }) }, collapsed ? (React.createElement("span", { style: styles$1.icon }, productIcon)) : (React.createElement(Space, { style: { width: '100%', gap: '8px' } }, React.createElement("span", { style: styles$1.icon