Files
SMANAGE/TempLate/GERP/assets/gerp/gerp.js
T

1254 lines
48 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* GERP.js - GERP 业务逻辑层
* 仅负责业务逻辑,所有 UI 操作通过 GERPUI 框架完成
*/
class GERP {
constructor(config = {}) {
this.Config = {
DefaultPage: config.DefaultPage || 'pages/dashboard.html',
DefaultTitle: config.DefaultTitle || '仪表盘',
Title: config.Title || 'GERP 管理系统',
...config
};
this.State = {
CurrentUser: null,
Notifications: [],
Permissions: [],
RoleScope: null, // {Type, TypeLabel, IsAdmin, IsDeptManager, IsWorkgroupLead, IsMember, DeptUUID, WorkgroupUUID}
IsAdmin: false
};
this.API = {
BaseURL: 'https://ugm.gvsds.com',
Request: this._ApiRequest.bind(this),
UserInfo: this._UserInfo.bind(this)
};
this.UI = null;
this.PopupManager = null;
this.Init();
}
async _ApiRequest(url, options = {}) {
const { method = 'GET', body = null, headers = {} } = options;
try {
const response = await fetch(url, {
method,
credentials: 'include',
headers: {
'Content-Type': 'application/json',
...headers
},
body: body ? JSON.stringify(body) : null
});
const result = await response.json();
if (result.code === 0) {
return { success: true, data: result };
} else {
return { success: false, message: result.msg || '请求失败' };
}
} catch (error) {
return { success: false, message: error.message || '网络错误' };
}
}
_SetupTimezoneHeader() {
// 显示用户时区
const tzText = document.getElementById('GERPTZText');
if (tzText) {
const userTZ = Intl.DateTimeFormat().resolvedOptions().timeZone;
const offset = -new Date().getTimezoneOffset() / 60;
const offsetStr = offset >= 0 ? `+${offset}` : `${offset}`;
tzText.textContent = `UTC${offsetStr}`;
}
const originalFetch = window.fetch;
const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const currentHost = window.location.hostname;
let _eltExpiredShown = false;
let _refreshing = null;
function _tryRefreshToken() {
if (_refreshing) return _refreshing;
_refreshing = originalFetch.call(window, 'https://ugm.gvsds.com/Auth/RefreshToken', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' }
}).then(res => res.json()).then(result => {
_refreshing = null;
return result.code === 0;
}).catch(() => {
_refreshing = null;
return false;
});
return _refreshing;
}
window.fetch = function(url, options = {}) {
if (typeof url === 'string') {
let isSameOrigin = url.startsWith('/API/') || url.startsWith('/');
if (!isSameOrigin) {
try {
const u = new URL(url);
isSameOrigin = u.hostname === currentHost;
} catch(e) {}
}
if (isSameOrigin) {
options.headers = options.headers || {};
if (options.headers instanceof Headers) {
if (!options.headers.has('X-User-Timezone')) {
options.headers.set('X-User-Timezone', userTimezone);
}
} else {
options.headers['X-User-Timezone'] = options.headers['X-User-Timezone'] || userTimezone;
}
}
}
return originalFetch.call(this, url, options).then(response => {
if (!_eltExpiredShown && response.status === 401) {
return _tryRefreshToken().then(ok => {
if (ok) {
return originalFetch.call(this, url, options);
}
_eltExpiredShown = true;
GERP.ShowELTExpiredDialog();
return response;
});
}
if (!_eltExpiredShown && response.redirected) {
try {
const redirectUrl = new URL(response.url);
if (redirectUrl.pathname.includes('login') || redirectUrl.pathname.includes('Login')) {
_eltExpiredShown = true;
GERP.ShowELTExpiredDialog();
}
} catch(e) {}
}
return response;
});
};
}
static ShowELTExpiredDialog() {
const existing = document.getElementById('GERPELTExpiredOverlay');
if (existing) return;
const overlay = document.createElement('div');
overlay.id = 'GERPELTExpiredOverlay';
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:999999;display:flex;align-items:center;justify-content:center;';
const dialog = document.createElement('div');
dialog.style.cssText = 'background:var(--CardBackground,#fff);border-radius:12px;padding:32px;max-width:420px;width:90%;box-shadow:0 20px 60px rgba(0,0,0,0.3);text-align:center;';
dialog.innerHTML = `
<div style="width:64px;height:64px;border-radius:50%;background:rgba(239,68,68,0.1);display:flex;align-items:center;justify-content:center;margin:0 auto 20px;">
<i class="fas fa-lock" style="font-size:28px;color:#ef4444;"></i>
</div>
<h3 style="margin:0 0 12px;font-size:18px;color:var(--TextPrimary,#1e293b);">登录已失效</h3>
<p style="margin:0 0 24px;font-size:14px;color:var(--TextSecondary,#64748b);line-height:1.6;">您的登录凭证已过期或无效,请重新登录以继续操作。</p>
<button id="GERPELTExpiredReloginBtn" style="width:100%;padding:12px;border:none;border-radius:8px;background:var(--PrimaryColor,#2563eb);color:#fff;font-size:15px;font-weight:500;cursor:pointer;transition:opacity 0.2s;">重新登录</button>
`;
overlay.appendChild(dialog);
document.body.appendChild(overlay);
document.getElementById('GERPELTExpiredReloginBtn').addEventListener('click', () => {
const currentUrl = window.location.href;
window.location.href = `https://www.gvsds.com/login.html?redirect=${encodeURIComponent(currentUrl)}`;
});
document.getElementById('GERPELTExpiredReloginBtn').addEventListener('mouseover', function() {
this.style.opacity = '0.9';
});
document.getElementById('GERPELTExpiredReloginBtn').addEventListener('mouseout', function() {
this.style.opacity = '1';
});
}
async _UserInfo() {
return await this.API.Request(`${this.API.BaseURL}/Query/GetELTUserInfo`);
}
Init() {
this._SetupTimezoneHeader();
// 等待 GERPUI 初始化完成
if (window.GERPUI) {
this.SetupUI();
} else {
document.addEventListener('GERPUIReady', () => this.SetupUI());
}
}
// 模型 i18n 已由后端 FormAPI 通过 verbose_name 直接提供(column.title),
// 不再需要单独加载 i18n/models/ 接口
SetupUI() {
this.UI = window.GERPUI;
// 应用用户设置
this.ApplySettings();
// 初始化弹出层管理器
this.InitPopupManager();
// 绑定 UI 事件到业务逻辑
this.BindUIEvents();
// 绑定导航按钮事件
this.BindNavEvents();
// 初始化菜单数据
this.InitMenuData();
// 打开默认页面
this.OpenDefaultTab();
// 加载用户数据
this.LoadUserData();
// 加载通知
this.LoadNotifications();
}
// 从服务器加载并应用用户设置
async ApplySettings() {
const defaultSettings = {
themeMode: 'default',
sidebarExpanded: true, maxTabs: 20,
allowDuplicate: false, confirmClose: false,
desktopNotify: true, notifySound: true,
animations: true, preload: true
};
let settings = defaultSettings;
// 优先从服务器加载
try {
const resp = await fetch('/API/Settings');
const result = await resp.json();
if (result.code === 0 && result.data && Object.keys(result.data).length > 0) {
settings = { ...defaultSettings, ...result.data };
}
} catch (e) {
// 网络异常时回退到本地存储
try {
const saved = localStorage.getItem('GERPSettings');
if (saved) settings = { ...defaultSettings, ...JSON.parse(saved) };
} catch (e2) {}
}
this._ApplySettingsData(settings);
}
// 应用设置数据到 DOM 和 UI 配置
_ApplySettingsData(settings) {
// 应用动画
if (!settings.animations) {
document.documentElement.style.setProperty('--TransitionSpeed', '0s');
document.documentElement.style.setProperty('--AnimationDuration', '0s');
document.body.classList.add('GERPNoAnimations');
}
// 应用到 UI 配置
if (this.UI) {
this.UI.Config.MaxTabs = settings.maxTabs;
}
// 应用侧边栏状态
if (this.UI && this.UI.SetSidebarCollapsed) {
this.UI.SetSidebarCollapsed(!settings.sidebarExpanded);
}
// 保存引用供子页面查询
this._Settings = settings;
}
// 初始化弹出层管理器
InitPopupManager() {
this.PopupManager = {
popups: new Set(),
rafId: null,
lastScrollY: window.scrollY,
lastScrollX: window.scrollX,
lastInnerHeight: window.innerHeight,
lastInnerWidth: window.innerWidth,
// 注册弹出层
register(popup) {
this.popups.add(popup);
this.startMonitoring();
},
// 注销弹出层
unregister(popup) {
this.popups.delete(popup);
if (this.popups.size === 0) {
this.stopMonitoring();
}
},
// 开始监控
startMonitoring() {
if (this.rafId) return;
const monitor = () => {
// 检测视口变化
const scrollY = window.scrollY;
const scrollX = window.scrollX;
const innerHeight = window.innerHeight;
const innerWidth = window.innerWidth;
const hasChange = (
scrollY !== this.lastScrollY ||
scrollX !== this.lastScrollX ||
innerHeight !== this.lastInnerHeight ||
innerWidth !== this.lastInnerWidth
);
if (hasChange) {
this.lastScrollY = scrollY;
this.lastScrollX = scrollX;
this.lastInnerHeight = innerHeight;
this.lastInnerWidth = innerWidth;
// 更新所有弹出层位置
this.updateAllPositions();
}
this.rafId = requestAnimationFrame(monitor);
};
this.rafId = requestAnimationFrame(monitor);
},
// 停止监控
stopMonitoring() {
if (this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
},
// 更新所有弹出层位置
updateAllPositions() {
this.popups.forEach(popup => {
if (popup && typeof popup.updatePosition === 'function') {
try {
popup.updatePosition();
} catch (e) {
// 忽略错误
}
}
});
}
};
// 监听 iframe 内容滚动(通过 postMessage)
window.addEventListener('message', (e) => {
if (e.data && e.data.type === 'IFRAME_SCROLL') {
this.PopupManager.updateAllPositions();
}
});
// 初始化主题管理器
this.InitThemeManager();
// 初始化全局主题同步(供子页面使用)
this.InitGlobalThemeSync();
}
// 初始化主题管理器
InitThemeManager() {
this.ThemeManager = {
themes: {
default: {
name: '默认主题',
colors: {
PrimaryColor: '#2563eb',
PrimaryHover: '#1d4ed8',
SecondaryColor: '#64748b',
SuccessColor: '#10b981',
WarningColor: '#f59e0b',
DangerColor: '#ef4444',
BackgroundColor: '#f1f5f9',
CardBackground: '#ffffff',
TextPrimary: '#1e293b',
TextSecondary: '#64748b',
TextMuted: '#94a3b8',
BorderColor: '#e2e8f0'
}
},
dark: {
name: '深色主题',
colors: {
PrimaryColor: '#3b82f6',
PrimaryHover: '#2563eb',
SecondaryColor: '#94a3b8',
SuccessColor: '#22c55e',
WarningColor: '#eab308',
DangerColor: '#ef4444',
BackgroundColor: '#0f172a',
CardBackground: '#1e293b',
TextPrimary: '#f1f5f9',
TextSecondary: '#94a3b8',
TextMuted: '#64748b',
BorderColor: '#334155'
}
},
light: {
name: '浅色主题',
colors: {
PrimaryColor: '#0ea5e9',
PrimaryHover: '#0284c7',
SecondaryColor: '#71717a',
SuccessColor: '#22c55e',
WarningColor: '#f59e0b',
DangerColor: '#ef4444',
BackgroundColor: '#fafafa',
CardBackground: '#ffffff',
TextPrimary: '#18181b',
TextSecondary: '#71717a',
TextMuted: '#a1a1aa',
BorderColor: '#e4e4e7'
}
},
green: {
name: '绿色主题',
colors: {
PrimaryColor: '#10b981',
PrimaryHover: '#059669',
SecondaryColor: '#6b7280',
SuccessColor: '#22c55e',
WarningColor: '#f59e0b',
DangerColor: '#ef4444',
BackgroundColor: '#f0fdf4',
CardBackground: '#ffffff',
TextPrimary: '#14532d',
TextSecondary: '#4b5563',
TextMuted: '#9ca3af',
BorderColor: '#d1fae5'
}
},
purple: {
name: '紫色主题',
colors: {
PrimaryColor: '#8b5cf6',
PrimaryHover: '#7c3aed',
SecondaryColor: '#6b7280',
SuccessColor: '#10b981',
WarningColor: '#f59e0b',
DangerColor: '#ef4444',
BackgroundColor: '#faf5ff',
CardBackground: '#ffffff',
TextPrimary: '#3b0764',
TextSecondary: '#4b5563',
TextMuted: '#9ca3af',
BorderColor: '#ede9fe'
}
},
red: {
name: '红色主题',
colors: {
PrimaryColor: '#ef4444',
PrimaryHover: '#dc2626',
SecondaryColor: '#6b7280',
SuccessColor: '#10b981',
WarningColor: '#f59e0b',
DangerColor: '#b91c1c',
BackgroundColor: '#fef2f2',
CardBackground: '#ffffff',
TextPrimary: '#450a0a',
TextSecondary: '#4b5563',
TextMuted: '#9ca3af',
BorderColor: '#fee2e2'
}
}
},
currentTheme: 'default',
// 切换主题
setTheme(themeName) {
if (!this.themes[themeName]) {
console.warn(`主题 ${themeName} 不存在,使用默认主题`);
themeName = 'default';
}
const theme = this.themes[themeName];
const root = document.documentElement;
// 应用主题颜色到 CSS 变量
Object.entries(theme.colors).forEach(([key, value]) => {
root.style.setProperty(`--${key}`, value);
});
// 保存到 localStorage
localStorage.setItem('gerp-theme', themeName);
localStorage.setItem('gerp-theme-colors', JSON.stringify(theme.colors));
// 更新当前主题
this.currentTheme = themeName;
// 触发主题切换事件
window.dispatchEvent(new CustomEvent('themeChange', {
detail: { theme: themeName, colors: theme.colors }
}));
// 通知所有子页面(iframe)更新主题
this.NotifyChildFrames(themeName, theme.colors);
// 通知父页面(如果存在)
if (window.parent && window.parent !== window) {
window.parent.postMessage({
type: 'THEME_CHANGE_FROM_CHILD',
theme: themeName,
colors: theme.colors
}, '*');
}
return theme;
},
// 通知所有子页面(iframe)更新主题
NotifyChildFrames(themeName, colors) {
const iframes = document.querySelectorAll('iframe');
iframes.forEach(iframe => {
try {
iframe.contentWindow.postMessage({
type: 'THEME_CHANGE',
theme: themeName,
colors: colors
}, '*');
} catch (e) {
console.warn('无法通知子页面:', e);
}
});
},
// 获取当前主题
getTheme() {
return this.themes[this.currentTheme];
},
// 获取所有主题
getAllThemes() {
return this.themes;
},
// 加载保存的主题
loadSavedTheme() {
const savedTheme = localStorage.getItem('gerp-theme');
if (savedTheme && this.themes[savedTheme]) {
this.setTheme(savedTheme);
} else {
this.setTheme('default');
}
}
};
// 页面加载时自动应用保存的主题
this.ThemeManager.loadSavedTheme();
// 监听主题变化并同步到子页面
window.addEventListener('message', (e) => {
if (e.data && e.data.type === 'THEME_CHANGE' && e.data.colors) {
const root = document.documentElement;
Object.entries(e.data.colors).forEach(([key, value]) => {
root.style.setProperty(`--${key}`, value);
});
}
});
}
// 初始化全局主题同步(供子页面使用)
InitGlobalThemeSync() {
// 提供给子页面使用的全局主题同步函数
window.GERPThemeSync = {
// 初始化主题同步
init() {
const root = document.documentElement;
// 从localStorage读取并应用保存的主题
this.applySavedTheme();
// 监听来自父页面的主题变化消息
window.addEventListener('message', (e) => {
const { type, data } = e.data || {};
if (type === 'THEME_CHANGE' && data && data.colors) {
Object.entries(data.colors).forEach(([key, value]) => {
root.style.setProperty(`--${key}`, value);
});
// 保存到localStorage
localStorage.setItem('gerp-theme', data.theme);
localStorage.setItem('gerp-theme-colors', JSON.stringify(data.colors));
}
if (type === 'THEME_RESPONSE' && data && data.colors) {
Object.entries(data.colors).forEach(([key, value]) => {
root.style.setProperty(`--${key}`, value);
});
// 保存到localStorage
localStorage.setItem('gerp-theme', data.theme);
localStorage.setItem('gerp-theme-colors', JSON.stringify(data.colors));
}
});
// 页面加载时向父页面请求当前主题
if (window.parent && window.parent !== window) {
window.parent.postMessage({ type: 'GET_THEME' }, '*');
}
},
// 应用保存的主题
applySavedTheme() {
const root = document.documentElement;
const themeColors = localStorage.getItem('gerp-theme-colors');
if (themeColors) {
try {
const colors = JSON.parse(themeColors);
Object.entries(colors).forEach(([key, value]) => {
root.style.setProperty(`--${key}`, value);
});
} catch (e) {
console.warn('无法解析主题颜色:', e);
}
}
}
};
}
// 绑定 UI 框架事件
BindUIEvents() {
// 菜单点击 - 打开 Tab
this.UI.On('menuClick', (data) => {
const icon = this.UI.ExtractIconClass(
document.querySelector(`[data-id="${data.id}"] .GERPMenuIcon`)?.innerHTML
);
this.UI.OpenTab({
page: data.page,
title: data.title,
icon: icon
}, this.UI.State.ActivePanelId);
});
// Tab 激活 - 更新标题
this.UI.On('tabActivate', (data) => {
this.UI.SetTitle(data.tab.Title);
});
// Tab 关闭
this.UI.On('tabClose', (tab) => {
});
// Tab 为空
this.UI.On('tabEmpty', () => {
this.UI.LoadFrame('');
this.UI.SetTitle('');
});
// 侧边栏切换
this.UI.On('sidebarToggle', (state) => {
});
// 用户操作
this.UI.On('userAction', (action) => {
this.HandleUserAction(action);
});
}
// 绑定导航按钮事件
BindNavEvents() {
// 搜索按钮
this.UI.Elements.SearchBtn?.addEventListener('click', () => {
this.OpenSearch();
});
// 通知按钮 - hover 触发
let notifyTimeout;
this.UI.Elements.NotifyBtn?.addEventListener('mouseenter', () => {
notifyTimeout = setTimeout(() => this.OpenNotifyPanel(), 150);
});
this.UI.Elements.NotifyBtn?.addEventListener('mouseleave', () => {
clearTimeout(notifyTimeout);
});
// 设置按钮 - 统一打开设置页面
this.UI.Elements.SettingsBtn?.addEventListener('click', () => {
this.OpenSettingsPage();
});
// 侧边栏设置按钮 - 统一打开设置页面
this.UI.Elements.SidebarSettingsBtn?.addEventListener('click', () => {
this.OpenSettingsPage();
});
// 用户信息 - hover 触发
let userTimeout;
this.UI.Elements.UserInfo?.addEventListener('mouseenter', () => {
userTimeout = setTimeout(() => this.OpenUserDropdown(), 150);
});
this.UI.Elements.UserInfo?.addEventListener('mouseleave', () => {
clearTimeout(userTimeout);
});
// 侧边栏切换按钮
this.UI.Elements.ToggleBtn?.addEventListener('click', () => {
this.UI.ToggleSidebar();
});
// 遮罩层点击
this.UI.Elements.Overlay?.addEventListener('click', () => {
this.UI.CloseSidebar();
});
}
// 初始化菜单数据
async InitMenuData() {
// 先加载角色作用域(用于默认菜单差异化)
if (!this.State.RoleScope) {
await this.LoadRoleScope();
}
fetch('/API/MenuList', { credentials: 'include' })
.then(res => res.json())
.then(result => {
if (result.code === 0 && result.data && result.data.length > 0) {
this.UI.SetMenuData(result.data);
} else {
this.UI.SetMenuData(this._getDefaultMenuData());
}
})
.catch(() => {
this.UI.SetMenuData(this._getDefaultMenuData());
});
}
// 加载角色作用域(用于前端差异化显示)
async LoadRoleScope() {
try {
const res = await fetch('/API/FormAPI/CheckAdmin', { credentials: 'include' });
const result = await res.json();
if (result.code === 0 && result.data) {
this.State.RoleScope = result.data.role_scope || null;
this.State.IsAdmin = !!(result.data.is_admin || result.data.is_superuser);
}
} catch(e) {
this.State.RoleScope = null;
this.State.IsAdmin = false;
}
}
_getDefaultMenuData() {
const scope = this.State.RoleScope;
const scopeType = scope?.Type || 'member';
const isAdmin = scopeType === 'admin';
const isDeptManager = scopeType === 'dept_manager';
const isWorkgroupLead = scopeType === 'workgroup_lead';
const isOrgManager = isAdmin || isDeptManager || isWorkgroupLead;
// 仪表盘:所有角色都可见
const menus = [
{
Id: 'dashboard',
Title: '仪表盘',
Icon: '<i class="fas fa-chart-line"></i>',
Page: 'pages/dashboard.html'
}
];
// 公司资产:所有角色都可见(原"系统设置"改名,资产管理挪入此)
// admin 还包含租户/审计/支付/系统配置等系统层面设置
const companyAssetsChildren = [
{ Id: 'system-assets', Title: '资产管理', Icon: '<i class="fas fa-images"></i>', Page: 'pages/assets/manager.html' }
];
if (isAdmin) {
companyAssetsChildren.push(
{ Id: 'system-tenant', Title: '租户管理', Icon: '<i class="fas fa-layer-group"></i>', Page: 'pages/Company/tenant.html' },
{ Id: 'system-tenant-apply', Title: '租户入驻申请', Icon: '<i class="fas fa-key"></i>', Page: 'pages/system/tenant-create.html' },
{ Id: 'system-tenant-review', Title: '租户审核工作台', Icon: '<i class="fas fa-clipboard-check"></i>', Page: 'pages/system/tenant-review.html' },
{ Id: 'system-audit-log', Title: '操作审计日志', Icon: '<i class="fas fa-history"></i>', Page: 'pages/system/audit-log.html' },
{ Id: 'system-payment-config', Title: '支付网关配置', Icon: '<i class="fas fa-credit-card"></i>', Page: 'pages/system/payment-config.html' },
{ Id: 'system-config', Title: '系统配置', Icon: '<i class="fas fa-tools"></i>', Page: 'pages/system/settings.html' }
);
} else if (isDeptManager || isWorkgroupLead) {
companyAssetsChildren.push(
{ Id: 'system-tenant-apply', Title: '租户入驻申请', Icon: '<i class="fas fa-key"></i>', Page: 'pages/system/tenant-create.html' }
);
}
menus.push({
Id: 'company-assets',
Title: '公司资产',
Icon: '<i class="fas fa-boxes"></i>',
Expanded: false,
Children: companyAssetsChildren
});
// 业务管理:所有角色都可见(销售订单 + 产品管理)
menus.push({
Id: 'business',
Title: '业务管理',
Icon: '<i class="fas fa-briefcase"></i>',
Expanded: false,
Children: [
{ Id: 'sales-orders', Title: '销售订单', Icon: '<i class="fas fa-shopping-cart"></i>', Page: 'pages/sales/orders.html' },
{ Id: 'inventory-products', Title: '产品管理', Icon: '<i class="fas fa-box"></i>', Page: 'pages/inventory/products.html' }
]
});
// 组织管理:admin/dept_manager/workgroup_lead 可见
// 归集公司/部门/工作组/角色/成员/职务等组织相关功能
if (isOrgManager) {
const orgChildren = [];
if (isAdmin) {
orgChildren.push({ Id: 'org-company', Title: '公司管理', Icon: '<i class="fas fa-building"></i>', Page: 'pages/Company/company.html' });
}
orgChildren.push(
{ Id: 'org-department', Title: '部门管理', Icon: '<i class="fas fa-sitemap"></i>', Page: 'pages/Company/department.html' },
{ Id: 'org-workgroup', Title: '工作组管理', Icon: '<i class="fas fa-users-cog"></i>', Page: 'pages/Company/workgroup.html' },
{ Id: 'org-role', Title: '角色管理', Icon: '<i class="fas fa-shield-alt"></i>', Page: 'pages/Company/role.html' }
);
if (isAdmin) {
orgChildren.push({ Id: 'org-role-permissions', Title: '角色权限分配', Icon: '<i class="fas fa-sitemap"></i>', Page: 'pages/system/role-permissions.html' });
}
// 成员管理(pages/system/users.html 内置 role_scope 差异化:admin=用户管理/dept_manager=部门成员/workgroup_lead=工作组成员)
const memberMenuTitle = isAdmin ? '成员管理' :
(isDeptManager ? '部门成员' : '工作组成员');
orgChildren.push(
{ Id: 'org-employee', Title: memberMenuTitle, Icon: '<i class="fas fa-user"></i>', Page: 'pages/system/users.html' },
{ Id: 'org-position', Title: '职务管理', Icon: '<i class="fas fa-id-badge"></i>', Page: 'pages/system/dbtable.html?table=Position&title=职务管理' }
);
menus.push({
Id: 'org',
Title: '组织管理',
Icon: '<i class="fas fa-project-diagram"></i>',
Expanded: false,
Children: orgChildren
});
}
// 服务器管理:仅 admin 可见(含菜单编辑器)
if (isAdmin) {
menus.push({
Id: 'server',
Title: '服务器管理',
Icon: '<i class="fas fa-server"></i>',
Expanded: false,
Children: [
{ Id: 'server-database', Title: '数据库管理', Icon: '<i class="fas fa-database"></i>', Page: 'pages/system/database.html' },
{ Id: 'server-msync', Title: 'MSYNC 服务器', Icon: '<i class="fas fa-network-wired"></i>', Page: 'pages/system/msync-servers.html' },
{ Id: 'server-menu', Title: '菜单编辑器', Icon: '<i class="fas fa-bars"></i>', Page: 'pages/system/menu.html' }
]
});
}
// 普通组员:仅看到 仪表盘 + 公司资产(个人资料通过顶部头像菜单访问)
return menus;
}
// 打开默认 Tab(仅在主页时)
OpenDefaultTab() {
const currentPath = window.location.pathname;
const isHomePage = currentPath === '/' ||
currentPath === '/index.html' ||
currentPath === '/index';
if (!isHomePage) {
return;
}
this.UI.OpenTab({
page: this.Config.DefaultPage,
title: this.Config.DefaultTitle,
icon: 'fa-chart-line'
});
}
// 打开搜索
OpenSearch() {
const allMenuItems = this.FlattenMenu(this.UI.State.MenuData);
this.UI.OpenSearch({
placeholder: '搜索菜单、页面、功能...',
onSearch: (keyword) => {
if (!keyword.trim()) return [];
return allMenuItems
.filter(item => item.title.toLowerCase().includes(keyword.toLowerCase()))
.map(item => ({
id: item.id,
title: item.title,
path: item.path,
icon: this.UI.ExtractIconClass(item.icon)
}));
},
onSelect: (item) => {
if (item) {
this.UI.SetActiveMenu(item.id);
this.UI.OpenTab({
page: item.page,
title: item.title,
icon: this.UI.ExtractIconClass(item.icon)
});
}
}
});
}
// 打开通知面板
OpenNotifyPanel() {
this.UI.OpenNotifyPanel({
notifications: this.State.Notifications,
onReadAll: () => {
fetch('/API/Message', {
method: 'PATCH',
credentials: 'include',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'action=read_all'
}).then(r => r.json()).then(result => {
if (result.code === 0) {
this.State.UnreadCount = 0;
this.State.Notifications = [];
this.UpdateNotifyBadge();
}
});
},
onViewAll: () => {
this.UI.OpenTab({
page: 'pages/system/message.html',
title: '站内信',
icon: 'fa-envelope'
});
},
onItemClick: (msgId) => {
this.UI.OpenTab({
page: 'pages/system/message.html?id=' + msgId,
title: '站内信',
icon: 'fa-envelope'
});
}
});
}
// 打开设置页面
OpenSettingsPage() {
this.UI.OpenTab({
page: 'pages/system/settings.html',
title: '系统设置',
icon: 'fa-cog'
});
}
// 打开用户下拉菜单
OpenUserDropdown() {
this.UI.OpenUserDropdown({
user: this.State.CurrentUser,
items: [
{ action: 'profile', label: '个人资料', icon: 'fa-user' },
{ action: 'password', label: '修改密码', icon: 'fa-key' },
{ action: 'logout', label: '退出登录', icon: 'fa-sign-out-alt', danger: true }
]
});
}
// 处理用户操作
HandleUserAction(action) {
switch (action) {
case 'profile':
this.UI.OpenTab({
page: 'pages/profile.html',
title: '个人资料',
icon: 'fa-user'
});
break;
case 'password':
this.ShowPasswordModal();
break;
case 'logout':
this.Logout();
break;
}
}
// 显示修改密码弹窗
ShowPasswordModal() {
this.UI.OpenModal({
title: '修改密码',
content: `
<div style="padding: 20px;">
<div style="margin-bottom: 16px;">
<label style="display: block; margin-bottom: 8px; font-weight: 500;">当前密码</label>
<input type="password" style="width: 100%; padding: 10px; border: 1px solid var(--BorderColor); border-radius: 8px;">
</div>
<div style="margin-bottom: 16px;">
<label style="display: block; margin-bottom: 8px; font-weight: 500;">新密码</label>
<input type="password" style="width: 100%; padding: 10px; border: 1px solid var(--BorderColor); border-radius: 8px;">
</div>
<div style="margin-bottom: 16px;">
<label style="display: block; margin-bottom: 8px; font-weight: 500;">确认新密码</label>
<input type="password" style="width: 100%; padding: 10px; border: 1px solid var(--BorderColor); border-radius: 8px;">
</div>
<button class="GERPBtn Primary" style="width: 100%;">确认修改</button>
</div>
`
});
}
// 退出登录
Logout() {
window.location.href = 'https://ugm.gvsds.com/Auth/DeleteAllCookies';
}
// 加载用户数据
async LoadUserData() {
const result = await this.API.UserInfo();
if (result.success) {
const user = result.data;
this.State.CurrentUser = {
uuid: user.UserUUID,
name: user.UserName,
gender: user.UserGender,
signature: user.UserPersonalSignature,
email: user.UserEmail,
tele: user.UserTele,
positionStatus: user.UserPositionStatus,
avatar: 'https://ugm.gvsds.com/Query/GetELTUserAvatar'
};
const userNameEl = document.querySelector('.GERPUserName');
if (userNameEl) {
userNameEl.textContent = user.UserName || '用户';
}
// 获取当前角色对应的公司名称,更新 Logo
this._UpdateLogoCompanyName();
} else {
console.error('加载用户信息失败:', result.message);
}
}
// 更新 Logo 显示当前角色所属公司名称
async _UpdateLogoCompanyName() {
try {
const res = await fetch('https://ugm.gvsds.com/Auth/GetUserRoles', {
method: 'GET',
credentials: 'include'
});
const result = await res.json();
if (result.code === 0 && result.data?.roles?.length) {
const activeRole = result.data.roles.find(r => r.active) || result.data.roles[0];
const logoText = document.querySelector('.GERPLogoText');
if (logoText && activeRole) {
const fullName = activeRole.CompanyName || '';
const shortName = activeRole.CompanyShort || '';
if (fullName && shortName) {
logoText.textContent = `${fullName}(${shortName})`;
} else {
logoText.textContent = fullName || shortName || activeRole.RoleName || '后台管理';
}
}
}
} catch (e) {
// 获取失败时保持默认文字
}
}
// 加载通知
LoadNotifications() {
fetch('/API/Message?action=unread_count', { credentials: 'include' })
.then(r => r.json())
.then(result => {
if (result.code === 0) {
this.State.UnreadCount = result.UnreadCount || 0;
this.State.Notifications = (result.RecentMessages || []).map(msg => ({
id: msg.MessageUUID,
icon: msg.MsgType === 0 ? 'fa-info-circle' : msg.MsgType === 1 ? 'fa-clipboard-check' : msg.MsgType === 2 ? 'fa-tools' : 'fa-envelope',
title: msg.Title,
text: msg.Content,
time: msg.CreateTime,
unread: true,
msgType: msg.MsgType,
sender: msg.SenderName
}));
this.UpdateNotifyBadge();
} else {
this.State.UnreadCount = 0;
this.State.Notifications = [];
this.UpdateNotifyBadge();
}
})
.catch(() => {
this.State.UnreadCount = 0;
this.State.Notifications = [];
this.UpdateNotifyBadge();
});
}
// 更新通知角标
UpdateNotifyBadge() {
const unreadCount = this.State.UnreadCount || 0;
const badge = this.UI.Elements.NotifyBtn?.querySelector('.GERPBadge');
if (badge) {
badge.textContent = unreadCount > 99 ? '99+' : unreadCount;
badge.style.display = unreadCount > 0 ? 'flex' : 'none';
}
}
// 扁平化菜单数据
FlattenMenu(items, path = '') {
let result = [];
items.forEach(item => {
const currentPath = path ? `${path} / ${item.Title}` : item.Title;
if (item.Page) {
result.push({
id: item.Id,
title: item.Title,
page: item.Page,
icon: item.Icon,
path: path
});
}
if (item.Children) {
result = result.concat(this.FlattenMenu(item.Children, currentPath));
}
});
return result;
}
// 静态方法:供子页面调用
static Navigate(page, title) {
if (window.parent !== window && window.parent.GERPApp && window.parent.GERPApp.UI) {
// 在 GERP 父框架内:通过 postMessage 通知父框架打开新标签页
window.parent.postMessage({ type: 'NAVIGATE', data: { page, title } }, '*');
} else {
// 非 GERP 父情况:在新标签页打开
window.open(page, '_blank');
}
}
static SetTitle(title) {
if (window.parent !== window && window.parent.GERPApp && window.parent.GERPApp.UI) {
window.parent.postMessage({ type: 'SET_TITLE', data: { title } }, '*');
} else {
document.title = title;
}
}
static Refresh() {
if (window.parent !== window && window.parent.GERPApp && window.parent.GERPApp.UI) {
window.parent.postMessage({ type: 'REFRESH' }, '*');
} else {
window.location.reload();
}
}
}
// 自动初始化
document.addEventListener('DOMContentLoaded', () => {
window.GERPApp = new GERP();
});
// ==================== 全局主题同步功能 ===================
// 注意:GERPThemeSync.js 已提取为独立文件,供所有子页面使用
// 这里只处理父页面接收子页面主题变化的逻辑
// 全局主题监听器
window.addEventListener('message', (e) => {
const { type, data } = e.data || {};
// 处理来自子页面的主题请求
if (type === 'GET_THEME') {
const gerp = window.GERPApp;
if (gerp && gerp.ThemeManager) {
const theme = gerp.ThemeManager.getTheme();
const iframe = e.source;
if (iframe && theme) {
iframe.postMessage({
type: 'THEME_RESPONSE',
theme: gerp.ThemeManager.currentTheme,
colors: theme.colors
}, '*');
}
}
return;
}
// 处理来自子页面的主题变化
if (type === 'THEME_CHANGE_FROM_CHILD') {
const gerp = window.GERPApp;
if (gerp && gerp.ThemeManager && data && data.colors) {
// 更新父页面的CSS变量
const root = document.documentElement;
Object.entries(data.colors).forEach(([key, value]) => {
root.style.setProperty(`--${key}`, value);
});
// 保存到localStorage
localStorage.setItem('gerp-theme', data.theme);
localStorage.setItem('gerp-theme-colors', JSON.stringify(data.colors));
// 通知其他子页面
gerp.ThemeManager.NotifyChildFrames(data.theme, data.colors);
}
return;
}
// 处理来自设置页面的设置更新
if (type === 'SETTINGS_UPDATE') {
const gerp = window.GERPApp;
if (gerp && data) {
gerp._ApplySettingsData(data);
}
return;
}
});
// ==================== 监听 iframe 消息 ====================
window.addEventListener('message', (e) => {
const { type, data } = e.data || {};
const gerp = window.GERPApp;
if (!gerp || !gerp.UI) return;
switch (type) {
case 'NAVIGATE':
gerp.UI.OpenTab({
page: data.page,
title: data.title || '新页面'
});
break;
case 'SET_TITLE':
gerp.UI.SetTitle(data.title);
break;
case 'REFRESH':
gerp.UI.Elements.ContentFrame?.contentWindow.location.reload();
break;
}
});
/*
const styles = {
sysName: 'background: linear-gradient(90deg, #1c92d2, #f2fcfe); color: #fff; padding: 8px 15px; border-radius: 4px 0 0 4px; font-weight: bold; font-size: 14px; text-shadow: 1px 1px 2px #000;',
version: 'background: #00c6ff; background: -webkit-linear-gradient(to right, #0072ff, #00c6ff); background: linear-gradient(to right, #0072ff, #00c6ff); color: #fff; padding: 8px 15px; border-radius: 0 4px 4px 0; font-weight: bold; font-size: 14px;',
info: 'background: #f6ffed; color: #52c41a; padding: 5px 10px; border-left: 4px solid #52c41a; font-size: 12px; font-family: monospace;',
copyright: 'background: #333; color: #999; padding: 4px 10px; border-radius: 4px; font-size: 11px;',
warning: 'background: #fff2f0; color: #f5222d; padding: 5px 10px; border-left: 4px solid #f5222d; font-size: 12px; font-weight: bold;'
};
console.log("\n");
console.log(`%c GERP-GERP %c CORE V${this.version} `, styles.sysName, styles.version);
console.log(`%c GVSDS-GERP V${this.version} %c © 2026 GVSDS Inc.`, 'background:#35495e ; padding: 4px; border-radius: 3px 0 0 3px; color: #fff', 'background:#41b883 ; padding: 4px; border-radius: 0 3px 3px 0; color: #fff' );
console.log(`%c 银河锋锐ERP后台管理系统核心逻辑 | GVSDS TermiNexus Team `, styles.copyright);
console.log(`%c CORE INIT: LAYUI | JQUERY | MD5 | LAYER `, styles.info);
console.log(`%c ⚠️ LICENSE: INTERNAL USE ONLY. UNAUTHORIZED MODIFICATION OR DISTRIBUTION IS PROHIBITED. ⚠️ `, styles.warning);
console.log(
`%c 🚀 我们在招人,欢迎你来! ` +
`%c Quick Access %c https://invite.gvsds.com/`,
'background: linear-gradient(90deg, #FF512F 0%, #DD2476 100%); color: #fff; padding: 5px 10px; border-radius: 4px 0 0 4px; font-weight: bold.',
'background: #333; color: #fff; padding: 5px 10px; border-radius: 0 4px 4px 0;',
'color: #4CAF50; font-weight: bold; text-decoration: underline;'
);
console.log(`%c QQ 群: 1037453621`, styles.warning);
console.info( '%c System Check %c UserAgent: ' + navigator.userAgent, 'background: #00bcd4; color: white; padding: 4px; font-weight: bold;', 'color: #00bcd4; font-weight: bold;' );
console.log("\n");
window.GERPThemeSync.init();
*/