UNPKG

@pwc-ra/components

Version:

PwC RA shared components library

1,103 lines (1,078 loc) 48.7 kB
'use strict'; var React = require('react'); var antd = require('antd'); var icons = require('@ant-design/icons'); var reactI18next = require('react-i18next'); var Keycloak = require('keycloak-js'); var axios = require('axios'); var reactRouterDom = require('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 Option = antd.Select.Option; var LanguageSwitcher = function () { var i18n = reactI18next.useTranslation().i18n; var handleChange = function (value) { i18n.changeLanguage(value); }; return (React.createElement(antd.Select, { defaultValue: i18n.language, style: { width: 100 }, onChange: handleChange, suffixIcon: React.createElement(icons.GlobalOutlined, null) }, React.createElement(Option, { value: "zh" }, "\u4E2D\u6587"), React.createElement(Option, { value: "en" }, "English"))); }; var Text$2 = antd.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; var userMenu = [ { key: 'userInfo', label: (React.createElement("div", { style: { padding: '4px 0' } }, React.createElement(antd.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(icons.UserOutlined, null), onClick: function () { } }, { key: 'logout', label: '退出登录', icon: React.createElement(icons.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" }, logo), React.createElement("div", { className: "navbar-right" }, React.createElement(antd.Space, { size: 24, align: "center" }, extra, React.createElement(antd.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(antd.Dropdown, { menu: { items: userMenu }, placement: "bottomRight" }, React.createElement(antd.Space, { style: { cursor: 'pointer' }, size: 8, align: "center" }, React.createElement(antd.Avatar, { size: 32, icon: React.createElement(icons.UserOutlined, null) }), loading ? (React.createElement(antd.Spin, { size: "small" })) : (React.createElement(Text$2, { strong: true, style: { fontSize: '14px' } }, currentUser === null || currentUser === void 0 ? void 0 : currentUser.username)))))))); }; var Text$1 = antd.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 = React.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(icons.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(antd.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 { var token = ensureKeycloak().token; if (!token) { console.warn('Keycloak token 为空'); } return token; } catch (error) { console.error('获取token失败:', 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 () { return !!(keycloak === null || keycloak === void 0 ? void 0 : keycloak.token); }; 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) || []; }; // 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'; // token最小有效期(秒) var MIN_TOKEN_VALIDITY = 60; // 设置全局默认值,是否自动转换键名 var globalTransformKeysEnabled = false; // 导出全局配置setter var setAutoTransformKeys = function (enabled) { globalTransformKeysEnabled = enabled; }; // 请求拦截器 instance.interceptors.request.use(function (config) { return __awaiter(void 0, void 0, void 0, function () { var token, error_1, token, configSetting, transform, originalFormData, newFormData, entries_2, _i, entries_1, _a, key, value, transformedKey; return __generator(this, function (_b) { switch (_b.label) { case 0: if (!isLoggedIn()) return [3 /*break*/, 4]; _b.label = 1; case 1: _b.trys.push([1, 3, , 4]); // 检查token是否即将过期,如果是则更新 return [4 /*yield*/, updateToken(MIN_TOKEN_VALIDITY)]; case 2: // 检查token是否即将过期,如果是则更新 _b.sent(); token = getToken(); if (token) { config.headers.Authorization = "Bearer ".concat(token); } return [3 /*break*/, 4]; case 3: error_1 = _b.sent(); console.error('更新token失败:', error_1); token = getToken(); if (token) { config.headers.Authorization = "Bearer ".concat(token); } return [3 /*break*/, 4]; case 4: 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, axiosConfig = __rest(config, ["transformKeys"]); // 将我们的自定义配置存储为普通属性,使用特殊的键名 if (transformKeys !== undefined) { axiosConfig[TRANSFORM_KEYS_PROP] = transformKeys; } 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 = React.createContext(undefined); var PageHeaderProvider = function (_a) { var children = _a.children; var _b = React.useState(null), currentUser = _b[0], setCurrentUser = _b[1]; var _c = React.useState(true), loading = _c[0], setLoading = _c[1]; var _d = React.useState(null), error = _d[0], setError = _d[1]; var _e = React.useState(null), selectedTenant = _e[0], setSelectedTenant = _e[1]; var initAttemptRef = React.useRef(0); var maxInitAttempts = 3; var isInitializing = React.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 }); }; var updateSelectedTenant = function (tenant) { setSelectedTenant(tenant); updateGlobalState({ selectedTenant: tenant }); }; React.useEffect(function () { var initAuth = function () { return __awaiter(void 0, void 0, void 0, function () { var authenticated, data, 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); // 默认选择第一个租户 if (data.tenants && data.tenants.length > 0) { updateSelectedTenant(data.tenants[0]); } 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 = React.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 = antd.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, logo = _a.logo; var _b = usePageHeader(), currentUser = _b.currentUser, loading = _b.loading, selectedTenant = _b.selectedTenant, setSelectedTenant = _b.setSelectedTenant, logout = _b.logout; var _c = React.useState(false), productDrawerVisible = _c[0], setProductDrawerVisible = _c[1]; var _d = React.useState([]), products = _d[0], setProducts = _d[1]; React.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*/]; } }); }); }; fetchProductMenu(); }, []); // 移除 products 依赖,避免无限循环 var toggleProductDrawer = function () { 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' } }, React.createElement("span", { className: "trigger", onClick: toggleProductDrawer, style: { cursor: 'pointer', fontSize: '18px' } }, productDrawerVisible ? React.createElement(icons.MenuUnfoldOutlined, null) : React.createElement(icons.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 })), React.createElement(antd.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(PageHeaderProvider, null, React.createElement(PageHeaderContent, __assign({}, props)))); }; var Sider = antd.Layout.Sider; var Text = antd.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 _a; var currentPath = pathname.split('/')[1]; for (var _i = 0, menuItems_1 = menuItems; _i < menuItems_1.length; _i++) { var item = menuItems_1[_i]; if ((_a = item.children) === null || _a === void 0 ? void 0 : _a.some(function (child) { return child.key === currentPath; })) { 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(icons.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; var navigate = reactRouterDom.useNavigate(); var location = reactRouterDom.useLocation(); var _h = React.useState([]), openKeys = _h[0], setOpenKeys = _h[1]; var _j = React.useState([]), selectedKeys = _j[0], setSelectedKeys = _j[1]; React.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) }, 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(antd.Space, { style: { width: '100%', gap: '8px' } }, React.createElement("span", { style: styles$1.icon }, productIcon), React.createElement(Text, { style: styles$1.text }, productName)))), React.createElement(antd.Menu, { mode: "inline", selectedKeys: selectedKeys, openKeys: collapsed ? [] : openKeys, onOpenChange: handleOpenChange, items: menuItems, onClick: handleMenuClick, style: __assign(__assign({}, styles$1.menu), { height: "calc(100% - ".concat(headerHeight, "px)") }) }))); }; var Breadcrumb = function (_a) { var routes = _a.routes, _b = _a.pathNameMap, pathNameMap = _b === void 0 ? {} : _b, onItemClick = _a.onItemClick, className = _a.className, style = _a.style, _c = _a.disableLastItemClick, disableLastItemClick = _c === void 0 ? true : _c, _d = _a.lastItemStyle, lastItemStyle = _d === void 0 ? { color: 'rgba(0, 0, 0, 0.45)' } : _d; var breadcrumbItems = routes.map(function (route, index) { var isLast = index === routes.length - 1; var hasMultipleRoutes = routes.length > 1; // 获取显示标题,优先使用路由配置中的标题,其次使用路径映射 var title = route.title || pathNameMap[route.path] || route.path; // 处理点击事件 var handleClick = function () { if (isLast && disableLastItemClick) { return; } if (route.onClick) { route.onClick(); } else if (onItemClick) { onItemClick(route.path, index); } }; return { key: route.path, title: (React.createElement("span", { style: __assign({ cursor: (isLast && disableLastItemClick) ? 'default' : 'pointer' }, (isLast && hasMultipleRoutes ? lastItemStyle : {})), onClick: handleClick }, title)) }; }); return (React.createElement(antd.Breadcrumb, { items: breadcrumbItems, className: className, style: style })); }; var Content = antd.Layout.Content; // 布局相关的常量 var HEADER_HEIGHT = 50; var SIDEBAR_WIDTH = 200; var COLLAPSED_SIDEBAR_WIDTH = 80; // 内联样式 var styles = { mainLayout: { minHeight: '100vh' }, mainLayoutBody: { minHeight: '100vh', paddingTop: "".concat(HEADER_HEIGHT, "px") }, contentContainer: { transition: 'margin-left 0.2s' }, content: { padding: '0px 24px 24px', minHeight: "calc(100vh - ".concat(HEADER_HEIGHT, "px - 8px)"), background: '#f0f2f5' }, breadcrumb: { fontSize: '16px', lineHeight: '36px', height: '50px', padding: '6px 0 0 0', display: 'flex', alignItems: 'center' }, contentWrapper: { background: '#fff', padding: '24px', minHeight: '280px', borderRadius: '2px' }}; var MainLayout = function (_a) { var menuItems = _a.menuItems, productName = _a.productName, productIcon = _a.productIcon, children = _a.children, breadcrumb = _a.breadcrumb, _b = _a.defaultCollapsed, defaultCollapsed = _b === void 0 ? false : _b, contentStyle = _a.contentStyle, contentWrapperStyle = _a.contentWrapperStyle, className = _a.className, headerExtra = _a.headerExtra, logo = _a.logo; var _c = React.useState(defaultCollapsed), collapsed = _c[0], setCollapsed = _c[1]; var handleCollapse = function () { setCollapsed(!collapsed); }; return (React.createElement(antd.Layout, { style: styles.mainLayout, className: className }, React.createElement(PageHeader, { collapsed: collapsed, onCollapse: handleCollapse, extra: headerExtra, logo: logo }), React.createElement(antd.Layout, { style: styles.mainLayoutBody }, React.createElement(Sidebar, { collapsed: collapsed, menuItems: menuItems, productName: productName, productIcon: productIcon }), React.createElement(antd.Layout, { style: __assign(__assign({}, styles.contentContainer), { marginLeft: collapsed ? "".concat(COLLAPSED_SIDEBAR_WIDTH, "px") : "".concat(SIDEBAR_WIDTH, "px") }) }, React.createElement(Content, { style: __assign(__assign({}, styles.content), contentStyle) }, breadcrumb && React.createElement("div", { style: styles.breadcrumb }, breadcrumb), React.createElement("div", { style: __assign(__assign({}, styles.contentWrapper), contentWrapperStyle) }, children)))))); }; exports.Breadcrumb = Breadcrumb; exports.MainLayout = MainLayout; exports.PageHeader = PageHeader; exports.PageHeaderProvider = PageHeaderProvider; exports.PageHeaderState = PageHeaderState; exports.Sidebar = Sidebar; exports.getCurrentUser = getCurrentUser; exports.getProductMenu = getProductMenu; exports.getRoles = getRoles; exports.getToken = getToken; exports.getUsername = getUsername; exports.http = http; exports.initKeycloak = initKeycloak; exports.isLoggedIn = isLoggedIn; exports.login = login; exports.logout = logout; exports.setAutoTransformKeys = setAutoTransformKeys; exports.updateToken = updateToken; exports.usePageHeader = usePageHeader; //# sourceMappingURL=index.js.map