import {
clearAuthSession,
getAuthContext,
startAuthSync,
switchAccount,
} from '/static/portal/common/auth.js?v=20260913';
import { escapeHtml } from '/static/portal/common/formatters.js';
import { mountCustomerServiceWidget } from '/static/portal/common/customer-service-widget/widget.js';
const PUBLIC_LINKS = [
['home', '首页', '/portal/guest/home/'],
['products', '基金产品', '/portal/guest/products/'],
['ranking', '基金排行', '/portal/guest/products/?view=ranking'],
];
const CUSTOMER_LINKS = [
['dashboard', '资产总览', '/portal/customer/dashboard/'],
['holdings', '我的持仓', '/portal/customer/holdings/'],
['profit-loss', '收益明细', '/portal/customer/profit-loss/'],
['orders', '交易记录', '/portal/customer/orders/'],
['transactions', '成交明细', '/portal/customer/transactions/'],
['risk-questionnaire', '风险测评', '/portal/customer/risk-questionnaire/'],
];
const RISK_LINKS = [
['risk-dashboard', '风险工作台', '/portal/employee-risk/dashboard/'],
];
const ADVISOR_LINKS = [
['advisor-dashboard', '投顾工作台', '/portal/employee-advisor/dashboard/'],
['public-products', '公开产品', '/portal/guest/products/'],
];
const OPERATOR_LINKS = [
['operator-dashboard', '运营工作台', '/portal/employee-operations/dashboard/'],
['public-products', '公开产品', '/portal/guest/products/'],
];
const ADMIN_LINKS = [
['admin-workspace', '平台治理', '/portal/employee-console/workspace/'],
['risk-dashboard', '风控中心', '/portal/employee-risk/dashboard/'],
];
const ROLE_LABELS = Object.freeze({
customer: '客户账户',
risk_operator: '风控人员',
operator: '运营人员',
advisor: '投顾人员',
admin: '管理员',
super_admin: '超级管理员',
});
function accountActions(context) {
const username = escapeHtml(context.username || '已登录用户');
const role = context.roles.map((item) => ROLE_LABELS[item]).find(Boolean) || '已认证账户';
return `
`;
}
function headerMarkup(links, active, context, mode) {
const nav = links.map(([key, label, href]) => ``).join('');
const actions = context
? accountActions(context)
: '员工入口登录';
const menuButton = mode === 'customer' ? '' : '';
return `跳到主要内容`;
}
function footerMarkup() {
return '';
}
export function mountShell({ active, mode = 'public' }) {
startAuthSync();
document.body.classList.add(`portal-${mode}`);
const context = getAuthContext();
const links = mode === 'customer'
? CUSTOMER_LINKS
: mode === 'risk'
? RISK_LINKS
: mode === 'advisor'
? ADVISOR_LINKS
: mode === 'operator'
? OPERATOR_LINKS
: mode === 'admin'
? ADMIN_LINKS
: PUBLIC_LINKS;
document.body.insertAdjacentHTML('afterbegin', headerMarkup(links, active, context, mode));
document.body.insertAdjacentHTML('beforeend', footerMarkup());
const menu = document.querySelector('[data-menu]');
const nav = document.querySelector('[data-nav]');
menu?.addEventListener('click', () => {
const open = nav.classList.toggle('site-header__nav--open');
menu.setAttribute('aria-expanded', String(open));
});
const accountMenu = document.querySelector('[data-account-menu]');
const accountToggle = document.querySelector('[data-account-toggle]');
const accountPopover = document.querySelector('[data-account-popover]');
const closeAccountMenu = () => {
accountMenu?.classList.remove('account-menu--open');
accountToggle?.setAttribute('aria-expanded', 'false');
};
accountToggle?.addEventListener('click', () => {
const open = accountMenu.classList.toggle('account-menu--open');
accountToggle.setAttribute('aria-expanded', String(open));
if (open) accountPopover?.querySelector('[role="menuitem"]')?.focus();
});
document.addEventListener('click', (event) => {
if (accountMenu && !accountMenu.contains(event.target)) closeAccountMenu();
});
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
closeAccountMenu();
accountToggle?.focus();
}
});
document.querySelector('[data-switch-account]')?.addEventListener('click', () => {
switchAccount(mode === 'customer' ? 'customer' : 'staff');
});
document.querySelector('[data-logout]')?.addEventListener('click', () => {
clearAuthSession();
window.location.assign('/portal/guest/home/?reason=signed-out');
});
const networkStatus = document.querySelector('[data-network-status]');
window.addEventListener('offline', () => {
networkStatus.textContent = '网络已断开';
networkStatus.classList.add('network-status--visible');
});
window.addEventListener('online', () => {
networkStatus.textContent = '网络已恢复,请点击刷新';
networkStatus.classList.add('network-status--visible');
window.setTimeout(() => networkStatus.classList.remove('network-status--visible'), 3000);
});
window.addEventListener('portal:error', (event) => {
const detail = event.detail || {};
networkStatus.textContent = detail.message || '请求未完成';
networkStatus.classList.add('network-status--visible');
});
window.addEventListener('portal:auth-expired', () => {
networkStatus.textContent = '登录已过期,请重新登录';
networkStatus.classList.add('network-status--visible');
});
if (new URLSearchParams(window.location.search).get('reason') === 'signed-out') {
networkStatus.textContent = '您已安全退出登录';
networkStatus.classList.add('network-status--visible');
window.setTimeout(() => networkStatus.classList.remove('network-status--visible'), 3200);
window.history.replaceState({}, '', window.location.pathname);
}
const observer = new MutationObserver(() => {
const traceId = document.documentElement.dataset.traceId || '';
const watermark = document.querySelector('[data-trace-watermark]');
if (watermark) watermark.textContent = traceId ? `追踪标识 ${traceId}` : '';
});
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-trace-id'] });
const isCustomerContext = context?.roles?.includes('customer');
const canUseCustomerService = mode === 'customer' || mode === 'public' && (!context || isCustomerContext);
if (canUseCustomerService) {
const style = document.createElement('link');
style.rel = 'stylesheet'; style.href = '/static/portal/common/customer-service-widget/widget.css?v=20260913';
document.head.appendChild(style);
mountCustomerServiceWidget(isCustomerContext ? 'customer' : mode);
}
}