(() => { const defaults = { mode: 'mock', apiBaseUrl: '', fallbackToMock: true, timeoutMs: 10000, getAccessToken: null, }; const runtime = { ...defaults, ...(window.GONGXUE_PLATFORM_RUNTIME_CONFIG || {}) }; const operationCatalog = Object.fromEntries( (window.GONGXUE_PLATFORM_SPEC?.operations || []).map(operation => [operation.operationId, operation]), ); const trace = []; class PlatformApiError extends Error { constructor(message, options = {}) { super(message); this.name = 'PlatformApiError'; this.status = Number(options.status || 0); this.code = options.code || 'PLATFORM_API_ERROR'; this.requestId = options.requestId || ''; this.operationId = options.operationId || ''; this.details = options.details; } } function normalizeBaseUrl(value) { return String(value || '').trim().replace(/\/+$/, ''); } function isEnabled() { return runtime.mode === 'api' || (runtime.mode === 'auto' && Boolean(normalizeBaseUrl(runtime.apiBaseUrl))); } function publicConfig() { return { mode: runtime.mode, apiBaseUrl: normalizeBaseUrl(runtime.apiBaseUrl), fallbackToMock: runtime.fallbackToMock !== false, timeoutMs: Number(runtime.timeoutMs) || defaults.timeoutMs, configured: isEnabled(), }; } async function accessToken() { const provider = typeof runtime.getAccessToken === 'function' ? runtime.getAccessToken : typeof window.GONGXUE_PLATFORM_ACCESS_TOKEN_PROVIDER === 'function' ? window.GONGXUE_PLATFORM_ACCESS_TOKEN_PROVIDER : null; const token = provider ? await provider() : ''; return String(token || '').trim(); } function buildUrl(path, query = {}, pathParams = {}) { const baseUrl = normalizeBaseUrl(runtime.apiBaseUrl); const resolvedPath = Object.entries(pathParams).reduce((value, [key, replacement]) => value.replace(`{${key}}`, encodeURIComponent(String(replacement))), path); const url = new URL(`${baseUrl}${resolvedPath.startsWith('/') ? resolvedPath : `/${resolvedPath}`}`, window.location.href); Object.entries(query).forEach(([key, value]) => { if (value === undefined || value === null || value === '') return; url.searchParams.set(key, Array.isArray(value) ? value.join(',') : String(value)); }); return url.toString(); } function responseRequestId(payload, response) { return String( payload?.meta?.requestId || payload?.requestId || payload?.traceId || payload?.extensions?.traceId || response?.headers?.get?.('x-request-id') || '', ); } function responseErrorCode(payload) { return String(payload?.code || payload?.extensions?.code || payload?.errorCode || 'PLATFORM_API_REQUEST_FAILED'); } function responseErrorMessage(payload, status) { return String(payload?.message || payload?.title || payload?.error || `请求失败(HTTP ${status})`); } async function request(operationId, options = {}) { const operation = operationCatalog[operationId]; if (!operation) { throw new PlatformApiError(`未知平台接口:${operationId}`, { code: 'PLATFORM_OPERATION_UNKNOWN', operationId, }); } if (!isEnabled()) { throw new PlatformApiError('平台 API 未启用', { code: 'PLATFORM_API_DISABLED', operationId, }); } const token = await accessToken(); if (!token) { throw new PlatformApiError('缺少平台管理员 access token', { status: 401, code: 'PLATFORM_ACCESS_TOKEN_MISSING', operationId, }); } const extraHeaders = Object.fromEntries(Object.entries(options.headers || {}).map(([key, value]) => [key, String(value)])); const forbiddenHeader = Object.keys(extraHeaders).find((key) => { const normalized = key.toLowerCase(); return ['authorization', 'x-user-id', 'x-platform-admin-key'].includes(normalized) || normalized.startsWith('x-tenant-'); }); if (forbiddenHeader) { throw new PlatformApiError(`禁止页面覆盖身份头:${forbiddenHeader}`, { code: 'PLATFORM_IDENTITY_HEADER_FORBIDDEN', operationId, }); } const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), Number(runtime.timeoutMs) || defaults.timeoutMs); const startedAt = Date.now(); if (options.signal) { if (options.signal.aborted) controller.abort(); else options.signal.addEventListener('abort', () => controller.abort(), { once: true }); } try { const response = await fetch(buildUrl(operation.path, options.query, options.pathParams), { method: operation.method, headers: { ...extraHeaders, Accept: 'application/json', Authorization: `Bearer ${token}`, ...(operation.method === 'GET' ? {} : { 'Content-Type': 'application/json' }), }, body: operation.method === 'GET' || options.body === undefined ? undefined : JSON.stringify(options.body), credentials: 'omit', signal: controller.signal, }); const text = await response.text(); let payload = {}; if (text) { try { payload = JSON.parse(text); } catch { payload = { error: text.slice(0, 500) }; } } const requestId = responseRequestId(payload, response); trace.push({ operationId, method: operation.method, path: operation.path, status: response.status, requestId, durationMs: Date.now() - startedAt, at: new Date().toISOString(), }); if (trace.length > 120) trace.splice(0, trace.length - 120); if (!response.ok) { throw new PlatformApiError( responseErrorMessage(payload, response.status), { status: response.status, code: responseErrorCode(payload), requestId, operationId, details: payload, }, ); } return payload; } catch (error) { if (error instanceof PlatformApiError) throw error; const aborted = error?.name === 'AbortError'; throw new PlatformApiError(aborted ? '平台接口请求超时' : (error?.message || '平台接口请求失败'), { code: aborted ? 'PLATFORM_API_TIMEOUT' : 'PLATFORM_API_NETWORK_ERROR', operationId, details: error, }); } finally { clearTimeout(timeout); } } const dictionaries = { tenantStatusToApi: { 待配置: 'draft', 试用: 'active', 正常: 'active', 暂停: 'suspended', 已归档: 'archived' }, tenantStatusFromApi: { draft: '待配置', active: '正常', suspended: '暂停', archived: '已归档' }, billingStatusToApi: { 试用: 'trial', 正常: 'active', 待付款: 'active', 逾期: 'past_due', 暂停: 'suspended', 已取消: 'cancelled' }, billingStatusFromApi: { trial: '试用', active: '正常', past_due: '逾期', suspended: '暂停', cancelled: '已取消' }, subscriptionStatusToApi: { 试用中: 'trial', 活跃: 'active', 暂停: 'suspended', 已取消: 'cancelled', 已到期: 'expired' }, subscriptionStatusFromApi: { trial: '试用中', active: '活跃', suspended: '暂停', cancelled: '已取消', expired: '已到期' }, invoiceStatusFromApi: { draft: '草稿', issued: '待付款', paid: '已支付', void: '已作废', overdue: '已逾期' }, invoiceTypeToApi: { 订阅服务费: 'subscription', 用量超额费: 'usage', 人工调整: 'adjustment', 首期服务费: 'manual' }, invoiceTypeFromApi: { subscription: '订阅服务费', usage: '用量超额费', usage_overage: '用量超额费', manual: '人工账单', adjustment: '人工调整' }, severityToApi: { 低: 'low', 中: 'medium', 高: 'high', 严重: 'critical' }, severityFromApi: { low: '低', medium: '中', high: '高', critical: '严重' }, providerToApi: { Webhook: 'generic', SMTP: 'generic', 邮件: 'generic', 短信: 'generic', 钉钉: 'dingtalk', 飞书: 'feishu', 企微: 'wecom' }, providerFromApi: { generic: 'Webhook', dingtalk: '钉钉', feishu: '飞书', wecom: '企微' }, eventStatusFromApi: { pending: '待处理', processing: '发送中', sent: '成功', retrying: '重试中', failed: '失败', discarded: '已丢弃' }, crmStatusFromApi: { pending: '待推送', processing: '推送中', retrying: '重试中', sent: '已推送', failed: '推送失败', discarded: '已忽略' }, providerStatusFromApi: { active: '正常', disabled: '未启用', testing: '测试中' }, providerStatusToApi: { 正常: 'active', 未启用: 'disabled', 测试中: 'testing' }, smsTemplateStatusFromApi: { active: '正常', disabled: '未启用' }, smsAuditStatusFromApi: { draft: '草稿', pending_review: '审核中', approved: '已通过', rejected: '已拒绝' }, paymentStatusFromApi: { active: '正常', disabled: '未启用', testing: '测试中' }, paymentStatusToApi: { 正常: 'active', 未启用: 'disabled', 测试中: 'testing' }, }; const platformPermissionLabels = { 'platform:dashboard:view': '查看平台经营概览', 'platform:tenant:manage': '管理平台租户', 'platform:staff:manage': '管理平台员工', 'platform:role:manage': '管理平台角色与权限', 'platform:question-bank:manage': '运营平台公共题库', 'platform:audit:view': '查看平台审计与告警', 'platform:billing:notification': '管理账务催缴通知', 'platform:saas-catalog:manage': '管理 SaaS 商品与版本', 'platform:saas-billing:manage': '管理 SaaS 订单与账务', 'platform:crm:read': '查看租户 CRM 配置', 'platform:crm:write': '管理租户 CRM 配置', 'platform:sms:read': '查看租户短信服务', 'platform:sms:write': '管理租户短信服务', 'platform:payment:read': '查看平台支付设置', 'platform:payment:write': '管理平台支付设置', }; const platformRoleLabels = { platform_super_admin: { name: '超级管理员', description: '拥有全部平台管理权限' }, }; function mapValue(map, value, fallback = value) { return Object.prototype.hasOwnProperty.call(map, value) ? map[value] : fallback; } function centsToYuan(value) { const number = Number(value || 0); return Number.isFinite(number) ? number / 100 : 0; } function yuanToCents(value) { const number = Number(value || 0); return Number.isFinite(number) ? Math.round(number * 100) : 0; } function dateOnly(value) { return value ? String(value).slice(0, 10) : ''; } function monthOnly(value) { return value ? String(value).slice(0, 7) : ''; } function dateTime(value) { if (!value) return '-'; const parsed = new Date(value); if (Number.isNaN(parsed.getTime())) return String(value); return new Intl.DateTimeFormat('zh-CN', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, }).format(parsed).replaceAll('/', '-'); } function isoStartOfDay(value) { if (!value) return undefined; return `${String(value).slice(0, 10)}T00:00:00+08:00`; } function splitList(value) { if (Array.isArray(value)) return value.map(String).map(item => item.trim()).filter(Boolean); return String(value || '').split(/[、,,]/).map(item => item.trim()).filter(Boolean); } function periodBounds(period) { const match = String(period || '').match(/^(\d{4})-(\d{2})$/); if (!match) return { periodStart: '', periodEnd: '' }; const year = Number(match[1]); const month = Number(match[2]); const lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate(); return { periodStart: `${match[1]}-${match[2]}-01`, periodEnd: `${match[1]}-${match[2]}-${String(lastDay).padStart(2, '0')}`, }; } function invoicePeriodBounds(label) { const match = String(label || '').match(/^(\d{4})年(\d{1,2})月$/); return match ? periodBounds(`${match[1]}-${String(match[2]).padStart(2, '0')}`) : {}; } const bodyBuilders = { PlatformSaasController_upsertFeature(input) { return { id: input.id || undefined, code: input.code, name: input.name, category: input.category, description: input.description || undefined, referencePriceCents: yuanToCents(input.referencePrice), currency: 'CNY', status: input.status || 'active', sortOrder: Number(input.sortOrder || 100) }; }, PlatformSaasController_upsertOffering(input) { return { id: input.id || undefined, code: input.code, name: input.name, type: input.type || 'base_plan', status: input.status || 'active', description: input.description || undefined, sortOrder: Number(input.sortOrder || 100) }; }, PlatformSaasController_upsertFeatureLimit(input) { return { id: input.id || undefined, metricCode: input.metricCode, featureCode: input.featureCode, name: input.name, unit: input.unit || 'count', kind: input.kind || 'current', warningPercent: Number(input.warningPercent || 80), isHardLimit: input.isHardLimit !== false }; }, PlatformSaasController_upsertVersion(input) { return { id: input.id || undefined, offeringId: input.offeringId, billingCycle: input.billingCycle || 'yearly', originalAmountCents: yuanToCents(input.originalAmount), amountCents: yuanToCents(input.amount), currency: 'CNY', effectiveAt: input.effectiveAt ? new Date(input.effectiveAt).toISOString() : undefined, featureCodes: splitList(input.featureCodes), limits: {}, metadata: {} }; }, PlatformBackofficeController_upsertRole(input) { return { id: input.id || undefined, code: input.code, name: input.name, status: input.status || 'active', description: input.description || undefined }; }, PlatformBackofficeController_replaceRoleBindings(input) { return { permissionCodes: input.permissionCodes || [], menuCodes: input.menuCodes || [] }; }, PlatformSaasController_upsertTenantFeatureOverride(input) { return { tenantId: input.tenantId, featureCode: input.featureCode, mode: input.mode, expiresAt: input.expiresAt ? new Date(input.expiresAt).toISOString() : undefined, reason: input.reason }; }, PlatformAdminTenantsController_create(input) { return { slug: input.slug, name: input.name, legalName: input.legalName || undefined, status: mapValue(dictionaries.tenantStatusToApi, input.status, input.status), billingStatus: mapValue(dictionaries.billingStatusToApi, input.billingStatus, input.billingStatus), metadata: input.metadata || {}, ownerEmail: input.ownerEmail || undefined, ownerPhone: input.ownerPhone || undefined, ownerName: input.ownerName, temporaryPassword: input.temporaryPassword, }; }, PlatformAdminTenantsController_status(input) { return { tenantId: input.tenantId, status: mapValue(dictionaries.tenantStatusToApi, input.status, input.status), billingStatus: mapValue(dictionaries.billingStatusToApi, input.billingStatus, input.billingStatus), reason: input.reason, }; }, PlatformAdminTenantsController_billingProfile(input) { return { tenantId: input.tenantId, billingName: input.billingName || undefined, taxId: input.taxId || undefined, contactName: input.contactName || undefined, contactPhone: input.phone || input.contactPhone || undefined, contactEmail: input.email || input.contactEmail || undefined, billingAddress: input.billingAddress || undefined, invoiceTitle: input.invoiceTitle || undefined, invoiceType: ({ 增值税专用发票: 'special_vat', 增值税普通发票: 'normal_vat', 不开票: 'none' })[input.type || input.invoiceType] || input.invoiceType || 'none', bankName: input.bankName || undefined, bankAccountMasked: input.bankAccountMasked || undefined, }; }, PlatformAdminBillingController_createSubscription(input) { return { tenantId: input.tenantId, planCode: input.planCode, status: mapValue(dictionaries.subscriptionStatusToApi, input.status, input.status || 'active'), billingCycle: ({ 年度: 'yearly', 月度: 'monthly', 试用: 'trial' })[input.cycle] || input.billingCycle, amountCents: yuanToCents(input.amount), startsAt: isoStartOfDay(input.start || input.startsAt), expiresAt: isoStartOfDay(input.expires || input.expiresAt), metadata: { autoRenew: Boolean(input.autoRenew), source: 'static-platform-console' }, }; }, PlatformAdminBillingController_createInvoice(input) { const bounds = invoicePeriodBounds(input.period); return { tenantId: input.tenantId, invoiceType: mapValue(dictionaries.invoiceTypeToApi, input.type, input.invoiceType || 'manual'), status: input.status || 'draft', dueDate: input.due || input.dueDate, billingPeriodStart: input.billingPeriodStart || bounds.periodStart, billingPeriodEnd: input.billingPeriodEnd || bounds.periodEnd, items: input.items || [{ itemType: mapValue(dictionaries.invoiceTypeToApi, input.type, 'manual'), description: input.note || input.type || '平台手工账单', quantity: 1, unitAmountCents: yuanToCents(input.amount), metadata: { periodLabel: input.period || '' }, }], note: input.note || undefined, }; }, PlatformAdminBillingController_fromSubscription(input) { return { tenantId: input.tenantId, subscriptionId: input.subscriptionId || undefined, status: input.status || 'issued', dueDate: input.dueDate || undefined, note: input.note || undefined, }; }, PlatformAdminBillingController_fromSubscriptionsBatch(input) { return { tenantIds: input.tenantIds || undefined, subscriptionIds: input.subscriptionIds || undefined, daysAhead: input.daysAhead ?? 45, status: input.status || 'issued', dueDate: input.dueDate || undefined, note: input.note || undefined, dryRun: Boolean(input.dryRun), }; }, PlatformAdminBillingController_fromUsage(input) { const bounds = input.periodStart && input.periodEnd ? input : { ...input, ...periodBounds(input.period) }; return { tenantIds: bounds.tenantIds || undefined, periodStart: bounds.periodStart, periodEnd: bounds.periodEnd, dueDate: bounds.dueDate || undefined, note: bounds.note || undefined, status: bounds.status || 'issued', dryRun: Boolean(bounds.dryRun), }; }, PlatformAdminBillingController_processOverdue(input) { return { dryRun: Boolean(input.dryRun), channel: input.channel || 'internal', limit: Number(input.limit || 100) }; }, PlatformAdminBillingController_confirmPayment(input) { return { paymentId: input.paymentId || input.backendPaymentId || input.invoiceId, providerTradeNo: input.tradeNo || input.providerTradeNo || undefined, paidAt: input.paidAt ? new Date(input.paidAt).toISOString() : undefined, reason: input.reason || input.paymentNo || 'platform_manual_payment_confirm', }; }, PlatformAdminBillingController_recordUsage(input) { const bounds = input.periodStart && input.periodEnd ? input : { ...input, ...periodBounds(input.period) }; return { tenantId: bounds.tenantId, metricKey: bounds.metricKey, metricValue: Number(bounds.metricValue || 0), periodStart: bounds.periodStart, periodEnd: bounds.periodEnd, metadata: { source: 'platform_console_manual' }, }; }, PlatformAdminOverviewController_upsertStaff(input) { return { userId: input.userId || undefined, email: input.email || undefined, phone: input.phone || undefined, name: input.name, status: input.status === '已停用' ? 'disabled' : 'active', roleIds: input.roleIds || [], }; }, PlatformAdminOverviewController_updateStaffStatus(input) { return { userId: input.userId || input.staffId, status: input.status === '正常' || input.status === 'active' ? 'active' : 'disabled', reason: input.reason || undefined, }; }, PlatformAdminAuditController_updateAlert(input) { return { alertId: input.alertId, status: input.status, resolutionNote: input.resolutionNote || input.note || undefined, }; }, PlatformAdminCrmController_upsertConfig(input) { return { id: input.backendId || input.id || undefined, tenantId: input.tenantId, enabled: input.enabled !== false && input.status !== '未启用', url: input.endpoint || input.url || undefined, secretRef: input.secretRef || undefined, formName: input.formName || input.pool || undefined, examType: input.examType || input.subject || undefined, timeoutSeconds: Number(input.timeout || input.timeoutSeconds || 10), delaySeconds: Number(input.delaySeconds || 0), assignmentMode: input.assignmentMode || 'round_robin', assignmentPool: splitList(input.pool), assignmentConfig: { retry: Number(input.retry || 3), source: 'platform_console' }, }; }, PlatformAdminCrmController_retryLead(input) { return { queueId: input.backendId || input.queueId || input.id, note: input.note || 'platform_console_retry' }; }, PlatformAdminSmsController_upsertChannel(input) { return { id: input.backendId || input.id || undefined, tenantId: input.tenantId, provider: input.provider || 'generic', name: input.name || `${input.provider || 'generic'} 短信渠道`, signature: input.signature || '', scene: input.scene || 'login', status: mapValue(dictionaries.providerStatusToApi, input.status, input.status || 'active'), secretRef: input.secretRef || undefined, priority: Number(input.priority || 100), monthlyQuota: Number(input.quota || input.monthlyQuota || 0), configPublic: input.configPublic || { redacted: true }, metadata: { source: 'platform_console' }, }; }, PlatformAdminSmsController_upsertTemplate(input) { return { id: input.backendId || input.id || undefined, tenantId: input.tenantId, channelId: input.channelId, code: input.code, name: input.name, type: input.type || 'notification', auditStatus: input.auditStatus || 'draft', status: input.status || 'active', providerTemplateCode: input.providerTemplateCode || undefined, content: input.content || '', remark: input.remark || undefined, metadata: { source: 'platform_console' }, }; }, PlatformAdminPaymentSettingsController_upsertApp(input) { return { id: input.backendId || input.id || undefined, appCode: input.appCode || input.code || String(input.appName || '').toLowerCase().replaceAll(' ', '_'), appName: input.appName, status: mapValue(dictionaries.paymentStatusToApi, input.status, input.status || 'active'), settlementMode: input.settlement || input.settlementMode || 'PlatformCollect', description: input.description || undefined, metadata: { source: 'platform_console' }, }; }, PlatformAdminPaymentSettingsController_upsertChannel(input) { return { id: input.backendId || input.id || undefined, appId: input.appId, provider: input.provider || 'manual', mode: input.mode || 'PlatformCollect', status: mapValue(dictionaries.paymentStatusToApi, input.status, input.status || 'active'), displayName: input.displayName || input.appName || input.provider || '支付渠道', secretRef: input.secretRef || undefined, callbackPath: input.callbackPath || undefined, priority: Number(input.priority || 100), configPublic: input.configPublic || { redacted: true }, metadata: { source: 'platform_console' }, }; }, PlatformAdminTenantPaymentSettingsController_upsertApp(input) { return { tenantId: input.tenantId, provider: input.provider || 'manual', status: mapValue(dictionaries.providerStatusToApi, input.status, input.status || 'active'), displayName: input.appName || input.displayName || undefined, secretRef: input.secretRef || undefined, priority: Number(input.priority || 100), configPublic: input.configPublic || { mode: input.mode || 'TenantCollect', redacted: true }, metadata: { source: 'platform_console' }, }; }, PlatformQuestionBanksController_upsertBank(input) { return { id: input.id || undefined, name: input.name, metadata: input.metadata || {}, }; }, PlatformQuestionBanksController_upsertNode(input) { return { id: input.id || undefined, questionBankId: input.questionBankId, parentId: input.parentId || undefined, nodeKey: input.nodeKey || undefined, name: input.name, nodeType: input.nodeType, sortOrder: Number(input.sortOrder || 0), isSelectable: input.isSelectable !== false, metadata: input.metadata || {}, }; }, PlatformQuestionBanksController_batchCreateNodes(input) { return { questionBankId: input.questionBankId, parentId: input.parentId || undefined, nodeType: input.nodeType, names: Array.isArray(input.names) ? input.names : splitList(input.names), }; }, PlatformQuestionBanksController_upsertQuestion(input) { return { id: input.id || undefined, questionBankId: input.questionBankId, contentNodeId: input.contentNodeId, legacyId: input.legacyId || undefined, type: input.type, typeLabel: input.typeLabel || undefined, difficulty: input.difficulty == null ? undefined : Number(input.difficulty), tags: input.tags || [], content: input.content, options: input.options || [], correctOptionIndex: input.correctOptionIndex ?? undefined, correctOptionIndices: input.correctOptionIndices || [], answerText: input.answerText || undefined, explanation: input.explanation || undefined, subQuestions: input.subQuestions || [], codeLang: input.codeLang || undefined, codeTemplate: input.codeTemplate || undefined, mediaUrl: input.mediaUrl || undefined, status: input.status || 'published', examMarkers: input.examMarkers || {}, sourceHash: input.sourceHash || undefined, }; }, PlatformQuestionBanksController_archiveQuestions(input) { return { questionIds: input.questionIds || [] }; }, PlatformQuestionBanksController_previewImport(input) { return { questionBankId: input.questionBankId, contentNodeId: input.contentNodeId || undefined, format: input.format, sourceName: input.sourceName || undefined, payload: input.payload, }; }, PlatformQuestionBanksController_executeImport(input) { return bodyBuilders.PlatformQuestionBanksController_previewImport(input); }, }; function notificationBody(input) { const maxAttempts = Number(input.maxAttempts || input.retry || 3); return { id: input.id || undefined, channelCode: input.code || input.channelCode, name: input.name, enabled: input.enabled !== false, provider: mapValue(dictionaries.providerToApi, input.provider, input.provider || 'generic'), webhookUrl: input.endpoint || input.webhookUrl, secret: input.secret || undefined, secretRef: input.secretRef || undefined, minSeverity: mapValue(dictionaries.severityToApi, input.minSeverity, input.minSeverity || 'medium'), statusFilter: splitList(input.statusFilter || 'open,acknowledged'), timeoutSec: Number(input.timeout || input.timeoutSec || 10), maxAttempts, metadata: { ...(input.metadata || {}), maxAttempts }, }; } bodyBuilders.PlatformAdminAuditController_upsertChannel = notificationBody; bodyBuilders.PlatformAdminDunningChannelsController_upsertChannel = input => ({ channelId: input.id || input.channelId || undefined, channelCode: input.code || input.channelCode, name: input.name, description: input.description || undefined, enabled: input.enabled !== false, provider: mapValue(dictionaries.providerToApi, input.provider, input.provider || 'generic'), webhookUrl: input.endpoint || input.webhookUrl, secretRef: input.secretRef || undefined, minReminderLevel: Number(input.minReminderLevel || 1), reminderTypes: input.reminderTypes || ['overdue', 'final_notice'], reminderChannels: input.reminderChannels || ['internal'], tenantIds: input.tenantIds || [], timeoutSeconds: Number(input.timeout || input.timeoutSeconds || 10), metadata: { ...(input.metadata || {}), secretRedacted: Boolean(input.secretRef) }, }); function buildBody(operationId, input = {}) { const builder = bodyBuilders[operationId]; return builder ? builder(input) : input; } function jsonSummary(value) { if (!value) return ''; if (typeof value === 'string') return value; try { return JSON.stringify(value); } catch { return String(value); } } function safeEndpoint(item) { if (!item?.webhook) return ''; const protocol = item.webhook.protocol ? `${item.webhook.protocol}://` : ''; return `${protocol}${item.webhook.host || ''}${item.webhook.pathname || ''}`; } function currentById(items, id) { return (items || []).find(item => item.id === id) || {}; } function itemOf(payload) { return payload?.item || payload || {}; } function itemsOf(payload, key = '') { if (Array.isArray(payload)) return payload; if (Array.isArray(payload?.items)) return payload.items; if (key && Array.isArray(payload?.[key])) return payload[key]; return []; } function lowerEnum(value) { return String(value || '').replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase(); } function catalogPlans(payload) { const offerings = itemsOf(payload, 'offerings'); const offeringsById = new Map(offerings.map(item => [item.id, item])); return itemsOf(payload, 'versions') .map(version => { const offering = offeringsById.get(version.offeringId) || {}; return { ...version, code: offering.code || version.offeringCode, name: offering.name || version.offeringName, description: offering.description || version.description, status: lowerEnum(version.status), billingCycle: lowerEnum(version.billingCycle), baseAmountCents: version.amountCents ?? version.baseAmountCents, includedQuotas: version.limits, offeringType: lowerEnum(offering.type || version.offeringType), }; }) .filter(item => item.offeringType !== 'add_on' && item.status !== 'retired'); } const adapters = { plan(item, current = {}) { const quotas = item.includedQuotas && typeof item.includedQuotas === 'object' ? Object.entries(item.includedQuotas).map(([key, value]) => `${key}: ${value}`) : current.quotas || []; return { ...current, code: item.code, name: item.name, description: item.description || '', cycle: ({ yearly: '年度', monthly: '月度' })[item.billingCycle] || item.billingCycle || '-', amount: centsToYuan(item.baseAmountCents), quotas, tenantCount: Number(current.tenantCount || 0), status: item.status, }; }, tenant(item, current = {}, planMap = {}) { const plan = planMap[item.planCode]; return { students: 0, questions: 0, storageGB: 0, trafficTB: 0, balance: 0, host: '-', owner: '待配置', ownerPhone: '-', ...current, id: item.id, slug: item.slug, name: item.name, legal: item.legalName || current.legal || item.name, brand: item.brandName || current.brand || item.name, planCode: item.planCode || '', plan: plan?.name || current.plan || item.planCode || '未订阅', status: mapValue(dictionaries.tenantStatusFromApi, item.status, item.status || '-'), billing: mapValue(dictionaries.billingStatusFromApi, item.billingStatus, item.billingStatus || '-'), mode: item.mode === 'saas' ? '正式' : item.mode || current.mode || '-', expires: dateOnly(item.subscriptionExpiresAt), balance: centsToYuan(item.openBalanceCents), createdAt: dateTime(item.createdAt), }; }, subscription(item) { return { id: item.subscriptionId || item.id, tenantId: item.tenantId, planCode: item.planCode || item.baseOfferingVersionId || '', status: mapValue(dictionaries.subscriptionStatusFromApi, lowerEnum(item.status), item.status || '-'), cycle: ({ yearly: '年度', monthly: '月度', quarterly: '季度', trial: '试用' })[lowerEnum(item.billingCycle)] || item.billingCycle || '-', amount: centsToYuan(item.amountCents ?? item.totalAmountCents), start: dateOnly(item.startsAt), expires: dateOnly(item.expiresAt || item.currentPeriodEnd), autoRenew: Boolean(item.metadata?.autoRenew), }; }, invoice(item) { const status = lowerEnum(item.status); const totalCents = item.totalCents ?? item.totalAmountCents; const paidCents = item.paidCents ?? (status === 'paid' ? totalCents : 0); return { id: item.invoiceNo || item.id, backendId: item.id, orderId: item.orderId || '', tenantId: item.tenantId, type: mapValue(dictionaries.invoiceTypeFromApi, item.invoiceType, item.invoiceType || '-'), period: item.billingPeriodStart ? monthOnly(item.billingPeriodStart) : monthOnly(item.issuedAt || item.dueDate), total: centsToYuan(totalCents), paid: centsToYuan(paidCents), balance: centsToYuan(item.balanceCents ?? Math.max(0, Number(totalCents || 0) - Number(paidCents || 0))), due: dateOnly(item.dueDate), status: mapValue(dictionaries.invoiceStatusFromApi, status, item.status || '-'), note: item.note || '', }; }, billingPayment(item) { return { id: item.id, orderId: item.orderId, tenantId: item.tenantId, paymentNo: item.paymentNo || '', status: lowerEnum(item.status), amount: centsToYuan(item.amountCents), providerTradeNo: item.providerTradeNo || '', paidAt: item.paidAt || '', }; }, reminder(item) { return { id: item.id, invoiceId: item.invoiceNo || item.invoiceId, backendInvoiceId: item.invoiceId, tenantId: item.tenantId, level: Number(item.reminderLevel || 1), channel: item.channel || '-', status: mapValue(dictionaries.eventStatusFromApi, item.status, item.status || '-'), date: dateTime(item.reminderDate || item.createdAt), message: item.message || '', }; }, bank(item, current = {}) { return { ...current, id: item.id, name: item.name, contentEntryId: item.contentEntryId || '', catalog: '平台公共题库', source: '平台自建', nodes: Number(item.nodeCount || 0), questions: Number(item.questionCount || 0), version: item.metadata?.version || item.metadata?.contentVersion || '-', status: lowerEnum(item.status) === 'active' ? '正常' : lowerEnum(item.status) === 'archived' ? '已归档' : item.status || '-', }; }, staff(item) { const roleCodes = item.roleCodes || []; const permissions = roleCodes.includes('platform_super_admin') ? ['*'] : []; return { id: item.userId || item.id, userId: item.userId || item.id, name: item.name || item.email || '平台员工', email: item.email || '', phone: item.phoneMasked || item.phone || '-', roleCodes, role: permissions.includes('*') ? '超级管理员' : roleCodes.join('、') || '未分配角色', permissions, status: item.status === 'active' ? '正常' : '已停用', lastActive: dateTime(item.updatedAt), }; }, audit(item) { const details = jsonSummary(item.details); return { id: item.id, time: `${dateTime(item.createdAt)} CST`, actor: item.actorName || item.actorUsername || item.actorUserId || 'system', role: item.actorUserId ? '平台管理员' : '系统任务', action: item.action || '-', targetType: item.targetType || '-', targetId: item.targetId || '', target: item.tenantName || item.targetId || item.targetType || '-', severity: mapValue(dictionaries.severityFromApi, item.details?.severity, item.details?.severity || '中'), result: item.details?.result === 'failed' ? '失败' : '成功', ip: item.ipAddress || '-', details, }; }, alert(item) { const evidence = Array.isArray(item.details?.evidence) ? item.details.evidence.map(String) : Object.entries(item.details || {}).slice(0, 8).map(([key, value]) => `${key}: ${jsonSummary(value)}`); return { id: item.id, title: item.title || item.ruleName || '平台审计告警', summary: item.summary || item.action || '', severity: mapValue(dictionaries.severityFromApi, item.severity, item.severity || '-'), status: item.status || 'open', owner: item.acknowledgedByName || item.resolvedByName || item.tenantName || item.ruleName || '待认领', targetType: item.targetType || '-', targetId: item.targetId || '', evidence, updatedAt: dateTime(item.updatedAt || item.createdAt), note: item.resolutionNote || '', }; }, rule(item) { return { id: item.id, code: item.code, name: item.name, severity: mapValue(dictionaries.severityFromApi, item.severity, item.severity || '-'), enabled: item.enabled !== false, window: item.conditions?.window || item.metadata?.window || '-', threshold: item.conditions?.threshold || item.metadata?.threshold || '-', target: item.description || (item.targetTypes || []).join('、') || '-', }; }, channel(item) { return { id: item.id, code: item.channelCode, name: item.name, provider: mapValue(dictionaries.providerFromApi, item.provider, item.provider || 'Webhook'), endpoint: safeEndpoint(item), enabled: item.enabled !== false, retry: Number(item.maxAttempts || item.metadata?.maxAttempts || 3), minSeverity: mapValue(dictionaries.severityFromApi, item.minSeverity, item.minSeverity || '中'), statusFilter: (item.statusFilter || []).join(','), timeout: Number(item.timeoutSec || 10), maxAttempts: Number(item.maxAttempts || item.metadata?.maxAttempts || 3), lastStatus: '正常', secretRef: item.secretRef || '', endpointRedacted: true, reminderTypes: item.reminderTypes || [], reminderChannels: item.reminderChannels || [], minReminderLevel: Number(item.minReminderLevel || 1), }; }, event(item, type = 'audit') { return { id: item.id, channelId: item.channelId, subject: type === 'dunning' ? item.reminderMessage || item.invoiceNo || '催缴通知' : item.alertTitle || item.alertAction || '审计告警通知', status: mapValue(dictionaries.eventStatusFromApi, item.status, item.status || '-'), attempts: Number(item.attempts || 0), updatedAt: dateTime(item.updatedAt || item.sentAt || item.createdAt), error: item.lastError || '', }; }, crmConfig(item) { const assignmentPool = Array.isArray(item.assignmentPool) ? item.assignmentPool.join('、') : jsonSummary(item.assignmentPool); return { id: item.id, backendId: item.id, tenantId: item.tenantId, provider: item.url ? 'Webhook' : 'generic', endpoint: item.url || '', assignmentMode: ({ round_robin: '按地区轮转', direct: '手工分配', referrer: '来源归属', none: '不分配' })[lowerEnum(item.assignmentMode)] || item.assignmentMode || '-', pool: assignmentPool || item.formName || '-', timeout: Number(item.timeoutSeconds || 0), retry: Number(item.assignmentConfig?.retry || 0), enabled: item.enabled !== false, status: item.enabled === false ? '未启用' : '正常', secretRef: item.secretRef || '', formName: item.formName || '', examType: item.examType || '', }; }, crmLead(item) { return { id: item.id, backendId: item.id, tenantId: item.tenantId, student: item.payload?.student || item.leadId || item.recordId || 'CRM 线索', phone: item.payload?.phoneMasked || '-', source: item.source || '-', subject: item.payload?.subject || item.provider || '-', owner: item.payload?.owner || '-', attempts: Number(item.attempts || 0), status: mapValue(dictionaries.crmStatusFromApi, lowerEnum(item.status), item.status || '-'), lastError: item.lastError || '', }; }, smsChannel(item) { const status = mapValue(dictionaries.providerStatusFromApi, lowerEnum(item.status), item.status || '-'); return { id: item.id, backendId: item.id, tenantId: item.tenantId, provider: item.provider || '-', name: item.name || item.displayName || '', signature: item.signature || item.name || '-', scene: item.scene || '-', secretRef: item.secretRef || '', quota: Number(item.monthlyQuota || 0), used: Number(item.sentThisMonth || 0), status, enabled: status === '正常', priority: Number(item.priority || 100), }; }, smsTemplate(item) { const sent = Number(item.sentCount || 0); const failed = Number(item.failedCount || 0); return { id: item.id, backendId: item.id, tenantId: item.tenantId, channelId: item.channelId, name: item.name || '-', code: item.code || '-', type: lowerEnum(item.type), status: mapValue(dictionaries.smsAuditStatusFromApi, lowerEnum(item.auditStatus), item.auditStatus || '-'), templateStatus: mapValue(dictionaries.smsTemplateStatusFromApi, lowerEnum(item.status), item.status || '-'), lastSend: dateTime(item.updatedAt), successRate: sent + failed ? `${Math.round(sent / (sent + failed) * 100)}%` : '-', content: item.content || '', }; }, smsLog(item) { return { id: item.id, tenantId: item.tenantId, templateId: item.templateId, channelId: item.channelId, phone: item.phoneMasked || '-', provider: item.provider || '-', updatedAt: dateTime(item.sentAt || item.createdAt), status: lowerEnum(item.status) === 'sent' ? '发送成功' : lowerEnum(item.status) === 'failed' ? '发送失败' : '待发送', }; }, platformPaymentApp(item) { const status = mapValue(dictionaries.paymentStatusFromApi, lowerEnum(item.status), item.status || '-'); return { id: item.id, backendId: item.id, tenantId: '', appCode: item.appCode || '', appName: item.appName || '-', provider: 'platform', mode: '平台收款', settlement: item.settlementMode || 'PlatformCollect', secretRef: '', status, appScope: 'platform', }; }, tenantPaymentApp(item) { const status = mapValue(dictionaries.providerStatusFromApi, lowerEnum(item.status), item.status || '-'); return { id: item.id, backendId: item.id, tenantId: item.tenantId || '', appName: item.displayName || item.provider || '-', provider: item.provider || '-', mode: item.configPublic?.mode || 'TenantCollect', settlement: '租户收款', secretRef: item.secretRef || '', status, appScope: 'tenant', }; }, platformPaymentEvent(item) { return { id: item.id, appId: item.appId || '', orderNo: item.providerEventId || item.eventId || item.paymentId || '-', amount: centsToYuan(item.payload?.amountCents || item.amountCents || 0), channel: item.provider || '-', notified: item.eventType || '-', updatedAt: dateTime(item.createdAt || item.processedAt), status: item.error ? '回调失败' : '成功', }; }, }; function aggregateUsage(items) { const records = new Map(); items.forEach(item => { const period = monthOnly(item.periodStart || item.periodEnd); const key = `${item.tenantId}:${period}`; const record = records.get(key) || { tenantId: item.tenantId, period, source: item.metadata?.source === 'platform_usage_worker' ? 'Worker 自动采集' : '人工补录', students: 0, questions: 0, storageGB: 0, trafficTB: 0, overage: [], estimated: 0, }; const metricMap = { students: 'students', questions: 'questions', storage_gb: 'storageGB', traffic_tb: 'trafficTB', }; const field = metricMap[item.metricKey || item.metricCode]; if (field) record[field] = Number(item.metricValue ?? item.usedValue ?? 0); records.set(key, record); }); return Array.from(records.values()); } function currentPeriod() { const parts = new Intl.DateTimeFormat('en-US', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', }).formatToParts(new Date()); const values = Object.fromEntries(parts.map(part => [part.type, part.value])); return `${values.year}-${values.month}`; } const bootstrapDescriptors = [ ['backofficeBootstrap', 'PlatformBackofficeController_bootstrap', {}], ['overview', 'PlatformAdminOverviewController_overview', {}], ['domains', 'PlatformAdminTenantsController_domains', { query: { limit: 200 } }], ['plans', 'PlatformAdminOverviewController_plans', {}], ['tenants', 'PlatformAdminTenantsController_list', { query: { limit: 200 } }], ['subscriptions', 'PlatformAdminBillingController_subscriptions', { query: { limit: 200 } }], ['saasOrders', 'PlatformAdminBillingController_orders', { query: { limit: 200 } }], ['saasRefunds', 'PlatformAdminBillingController_refunds', { query: { limit: 200 } }], ['invoices', 'PlatformAdminBillingController_invoices', { query: { limit: 200 } }], ['billingPayments', 'PlatformAdminBillingController_payments', { query: { limit: 200 } }], ['usage', 'PlatformAdminBillingController_usage', { query: { limit: 200 } }], ['reminders', 'PlatformAdminBillingController_reminders', { query: { limit: 100 } }], ['banks', 'PlatformQuestionBanksController_getBanks', { query: { status: 'all' } }], ['staff', 'PlatformAdminOverviewController_staff', { query: { limit: 200 } }], ['dunningChannels', 'PlatformAdminDunningChannelsController_channels', { query: { limit: 100 } }], ['dunningEvents', 'PlatformAdminDunningEventsController_events', { query: { limit: 100 } }], ['auditLogs', 'PlatformAdminAuditController_logs', { query: { limit: 200 } }], ['alerts', 'PlatformAdminAuditController_alerts', { query: { limit: 100 } }], ['crmConfigs', 'PlatformAdminCrmController_configs', { query: { limit: 200 } }], ['crmLeads', 'PlatformAdminCrmController_leads', { query: { limit: 200 } }], ['crmLogs', 'PlatformAdminCrmController_logs', { query: { limit: 200 } }], ['smsChannels', 'PlatformAdminSmsController_channels', { query: { limit: 200 } }], ['smsTemplates', 'PlatformAdminSmsController_templates', { query: { limit: 200 } }], ['smsLogs', 'PlatformAdminSmsController_logs', { query: { limit: 200 } }], ['platformPaymentApps', 'PlatformAdminPaymentSettingsController_apps', { query: { limit: 200 } }], ['paymentChannels', 'PlatformAdminPaymentSettingsController_channels', { query: { limit: 200 } }], ['rebateSummary', 'PlatformAdminPaymentSettingsController_rebateSummary', {}], ['tenantPaymentApps', 'PlatformAdminTenantPaymentSettingsController_apps', { query: { limit: 200 } }], ['platformPaymentEvents', 'PlatformAdminPaymentSettingsController_events', { query: { limit: 100 } }], ['tenantPaymentEvents', 'PlatformAdminTenantPaymentSettingsController_events', { query: { limit: 100 } }], ]; async function bootstrap(options = {}) { const descriptors = bootstrapDescriptors.map(([key, operationId, requestOptions]) => { const nextOptions = { ...requestOptions, query: { ...(requestOptions.query || {}) } }; return [key, operationId, nextOptions]; }); const results = await Promise.allSettled( descriptors.map(([, operationId, requestOptions]) => request(operationId, requestOptions)), ); const payloads = {}; const errors = {}; results.forEach((result, index) => { const [key] = descriptors[index]; if (result.status === 'fulfilled') payloads[key] = result.value; else errors[key] = result.reason; }); return { payloads, errors, requested: descriptors.length }; } function adaptBootstrap(payloads, currentData = {}) { const patch = {}; const backoffice = payloads.backofficeBootstrap || {}; const permissionItems = Array.isArray(backoffice.permissions) ? backoffice.permissions : []; const roleItems = Array.isArray(backoffice.roles) ? backoffice.roles : []; if (permissionItems.length) { patch.platformPermissions = Object.fromEntries(permissionItems.map(item => [ item.code, platformPermissionLabels[item.code] || item.name || item.code, ])); } if (roleItems.length) { patch.platformRoles = roleItems .filter(item => String(item.status || '').toLowerCase() === 'active') .map(item => { const localized = platformRoleLabels[item.code] || {}; return { id: item.id, code: item.code, name: localized.name || item.name || item.code, description: localized.description || item.description || '', permissionCodes: item.permissionCodes || [], menuCodes: item.menuCodes || [] }; }); } if (payloads.overview) patch.apiOverview = itemOf(payloads.overview); if (payloads.domains) patch.domains = itemsOf(payloads.domains); if (payloads.plans) patch.saasCatalog = payloads.plans; if (payloads.permissions) patch.apiPermissions = itemOf(payloads.permissions); const planPayload = itemsOf(payloads.plans, 'versions').length ? catalogPlans(payloads.plans) : itemsOf(payloads.plans); if (planPayload.length) { patch.plans = planPayload.map(item => adapters.plan(item, currentById(currentData.plans, item.code))); } const planItems = patch.plans || currentData.plans || []; const planMap = Object.fromEntries(planItems.map(item => [item.code, item])); const tenants = itemsOf(payloads.tenants); if (tenants.length) { patch.tenants = tenants.map(item => adapters.tenant(item, currentById(currentData.tenants, item.id), planMap)); } const staff = itemsOf(payloads.staff); if (staff.length) { const roles = patch.platformRoles || currentData.platformRoles || []; patch.staff = staff.map(item => { const adapted = adapters.staff(item); const assignedRoles = roles.filter(role => adapted.roleCodes.includes(role.code)); adapted.roleIds = assignedRoles.map(role => role.id); adapted.role = assignedRoles.map(role => role.name).join('、') || adapted.role; adapted.permissions = adapted.permissions.includes('*') ? adapted.permissions : [...new Set(assignedRoles.flatMap(role => role.permissionCodes))]; return adapted; }); } const invoices = itemsOf(payloads.invoices); if (invoices.length) patch.invoices = invoices.map(adapters.invoice); const billingPayments = itemsOf(payloads.billingPayments); if (billingPayments.length) patch.billingPayments = billingPayments.map(adapters.billingPayment); const subscriptions = itemsOf(payloads.subscriptions); if (subscriptions.length) patch.subscriptions = subscriptions.map(adapters.subscription); if (payloads.saasOrders) patch.saasOrders = itemsOf(payloads.saasOrders); if (payloads.saasRefunds) patch.saasRefunds = itemsOf(payloads.saasRefunds); const usage = itemsOf(payloads.usage); if (usage.length) patch.usage = aggregateUsage(usage); if (Array.isArray(payloads.usageCandidates?.items) && patch.usage) { const usageByKey = new Map(patch.usage.map(item => [`${item.tenantId}:${item.period}`, item])); payloads.usageCandidates.items.forEach(candidate => { const record = usageByKey.get(`${candidate.tenantId}:${monthOnly(candidate.periodStart)}`); if (!record) return; record.estimated = centsToYuan(candidate.totalCents); record.overage = (candidate.items || []).map(item => item.description || item.itemType).filter(Boolean); }); } const reminders = itemsOf(payloads.reminders); if (reminders.length) patch.reminders = reminders.map(adapters.reminder); const dunningChannels = itemsOf(payloads.dunningChannels); if (dunningChannels.length) patch.dunningChannels = dunningChannels.map(adapters.channel); const dunningEvents = itemsOf(payloads.dunningEvents); if (dunningEvents.length) patch.dunningEvents = dunningEvents.map(item => adapters.event(item, 'dunning')); const banks = itemsOf(payloads.banks); if (banks.length) { patch.banks = banks.map(item => adapters.bank(item, currentById(currentData.banks, item.id))); } const auditLogs = itemsOf(payloads.auditLogs); if (auditLogs.length) patch.auditLogs = auditLogs.map(adapters.audit); const alertRules = itemsOf(payloads.alertRules); if (alertRules.length) patch.alertRules = alertRules.map(adapters.rule); const alerts = itemsOf(payloads.alerts); if (alerts.length) patch.alerts = alerts.map(adapters.alert); const crmConfigs = itemsOf(payloads.crmConfigs); if (crmConfigs.length) patch.crmConfigs = crmConfigs.map(adapters.crmConfig); const crmLeads = itemsOf(payloads.crmLeads); if (crmLeads.length) patch.crmLeads = crmLeads.map(adapters.crmLead); if (payloads.crmLogs) patch.crmLogs = itemsOf(payloads.crmLogs); const smsChannels = itemsOf(payloads.smsChannels); if (smsChannels.length) patch.smsChannels = smsChannels.map(adapters.smsChannel); const smsTemplates = itemsOf(payloads.smsTemplates); if (smsTemplates.length) patch.smsTemplates = smsTemplates.map(adapters.smsTemplate); const smsLogs = itemsOf(payloads.smsLogs); if (smsLogs.length) patch.smsLogs = smsLogs.map(adapters.smsLog); const platformPaymentApps = itemsOf(payloads.platformPaymentApps).map(adapters.platformPaymentApp); const tenantPaymentApps = itemsOf(payloads.tenantPaymentApps).map(adapters.tenantPaymentApp); if (platformPaymentApps.length || tenantPaymentApps.length) patch.paymentApps = [...platformPaymentApps, ...tenantPaymentApps]; if (payloads.paymentChannels) patch.paymentChannels = itemsOf(payloads.paymentChannels); if (payloads.rebateSummary) patch.rebateSummary = itemOf(payloads.rebateSummary); const paymentEvents = [ ...itemsOf(payloads.platformPaymentEvents).map(adapters.platformPaymentEvent), ...itemsOf(payloads.tenantPaymentEvents).map(adapters.platformPaymentEvent), ]; if (paymentEvents.length) patch.paymentEvents = paymentEvents; const auditChannels = itemsOf(payloads.auditChannels); if (auditChannels.length) patch.auditChannels = auditChannels.map(adapters.channel); const notificationEvents = itemsOf(payloads.notificationEvents); if (notificationEvents.length) { patch.notificationEvents = notificationEvents.map(item => adapters.event(item, 'audit')); } return patch; } async function tenantDetail(tenantId) { const payload = await request('PlatformAdminTenantsController_detail', { query: { tenantId } }); const item = payload.item || {}; return { tenant: item.tenant || null, domains: item.domains || [], subscriptions: (item.subscriptions || []).map(adapters.subscription), invoices: (item.invoices || []).map(adapters.invoice), usage: aggregateUsage(item.usage || []), billingProfile: item.tenant ? { billingName: item.tenant.billingName || '', taxId: item.tenant.taxId || '', contactName: item.tenant.contactName || '', phone: item.tenant.contactPhone || '', email: item.tenant.contactEmail || '', billingAddress: item.tenant.billingAddress || '', invoiceTitle: item.tenant.invoiceTitle || '', type: ({ special_vat: '增值税专用发票', normal_vat: '增值税普通发票', none: '不开票' })[item.tenant.invoiceType] || item.tenant.invoiceType || '', bankName: item.tenant.bankName || '', bankAccountMasked: item.tenant.bankAccountMasked || '', } : null, }; } function downloadExport(payload) { const item = payload?.item; if (!item?.contentBase64) { throw new PlatformApiError('审计导出响应缺少文件内容', { code: 'AUDIT_EXPORT_INVALID' }); } const bytes = Uint8Array.from(atob(item.contentBase64), character => character.charCodeAt(0)); const blob = new Blob([bytes], { type: item.mimeType || 'application/octet-stream' }); const url = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = url; anchor.download = item.filename || `platform-audit.${item.format || 'csv'}`; anchor.click(); setTimeout(() => URL.revokeObjectURL(url), 1000); return item; } window.GongxuePlatformApi = Object.freeze({ PlatformApiError, adapters, adaptBootstrap, bootstrap, buildBody, dictionaries, downloadExport, isEnabled, operationCatalog, periodBounds, publicConfig, request, tenantDetail, trace, }); })();