import { mockApiPlugin as gatewayFixtures } from './gatewayBrowserFixtures.mjs' const time = '2026-10-26T10:00:00+08:00' const configs = [1, 2, 3].map((id) => ({ id, config_name: `模拟配置 ${id}`, priority: id, description: '本地模拟,不代表真实配置', status: id === 3 ? 0 : 1, carrier_id: 8, carrier_name: '模拟运营商账户', card_category: 'normal', card_condition: 'activated', asset_status: 2, package_state: 'active', realname_check_interval: null, carddata_check_interval: 300, package_check_interval: 600, card_status_check_interval: 0, pre_reset_window: 3600, pre_reset_carddata_interval: 60, created_at: time, updated_at: time })) const tasks = new Map() const page = (items, url) => { const current = Number(url.searchParams.get('page') || 1) const size = Number(url.searchParams.get('page_size') || 20) return { items: items.slice((current - 1) * size, current * size), page: current, size, page_size: size, total: items.length, total_pages: Math.ceil(items.length / size) } } // 本地测试专用:所有 /api 都被当前或 gateway fixture 消费,无真实后端代理。 export const mockApiPlugin = { name: 'polling-config-local-mock-api', configureServer(server) { server.middlewares.use(async (req, res, next) => { const url = new URL(req.url, 'http://127.0.0.1') const path = url.pathname const auth = ['/api/auth/me', '/api/auth/login', '/api/auth/logout'].includes(path) if ( !auth && !path.startsWith('/api/admin/polling-configs') && !['/api/admin/polling-stats/pre-reset', '/api/admin/polling-stats/tiers'].includes(path) ) return next() let body = '' for await (const chunk of req) body += chunk const input = body ? JSON.parse(body) : {} const viewer = req.headers.authorization?.includes('local-polling-viewer') const denied = req.headers.authorization?.includes('local-polling-denied') const admin = !viewer && !denied let data = {} let code = 0 let msg = 'local mock only' if (auth) { const menus = [ { url: '/polling-management/config', name: '轮询配置' }, { url: '/polling-management/config-stats', name: '轮询配置统计', perm_code: 'polling_config_stats:page' } ] data = { access_token: viewer ? 'local-polling-viewer' : denied ? 'local-polling-denied' : 'local-polling-admin', refresh_token: '', expires_in: 3600, user: { id: admin ? 1 : 2, user_type: admin ? 1 : 2, username: admin ? '本地模拟管理员' : '本地普通账号', roles: [admin ? 'R_SUPER' : 'R_ADMIN'] }, permissions: denied ? [] : ['polling_config_stats:page'], buttons: denied ? [] : [ 'polling_config:match_preview', 'polling_config:recalculate_progress', 'polling_config_stats:pre_reset', 'polling_config_stats:tiers' ], menus: [{ url: '/polling-management', name: '轮询管理', children: menus }] } } else if (path === '/api/admin/polling-configs/match-preview') { const iccid = url.searchParams.get('iccid') if (!iccid || (req.method === 'POST' && input.iccid !== iccid)) { code = 1001 msg = 'Query 和 Body 的 ICCID 必须一致' } else data = { card: { iccid, card_id: 42, carrier_id: 8, enable_polling: true, asset_status: 2, card_category: 'normal', card_condition: 'activated', package_state: 'active', package_state_name: '有生效套餐' }, matched: req.method === 'POST' && input.overrides?.length ? input.overrides .filter((item) => !item.disabled) .map((item) => ({ id: item.id, config_name: item.config_name, priority: item.priority, draft: true })) : [{ id: 1, config_name: configs[0].config_name, priority: 1, draft: false }], intervals: [ { config_id: 1, config_name: configs[0].config_name, inherited: false, interval: 300, mode: 'interval', task_type: 'polling:carddata', task_type_name: '流量' }, { config_id: 1, config_name: configs[0].config_name, inherited: true, interval: 0, mode: 'disabled', task_type: 'polling:card_status', task_type_name: '卡状态' }, { config_id: 0, config_name: '', inherited: false, interval: null, mode: 'unmatched', task_type: 'polling:realname', task_type_name: '实名' } ] } } else if (path === '/api/admin/polling-configs/priorities') { if (!admin) { code = 403 msg = '仅管理员可操作' } else { input.ordered_ids.forEach((id, index) => { const row = configs.find((item) => item.id === id) if (row) row.priority = index + 1 }) data = null } } else if (path === '/api/admin/polling-configs/recalculate-due' && req.method === 'POST') { if (!admin) { code = 403 msg = '仅管理员可操作' } else { const id = `local-task-${tasks.size + 1}` data = { task_id: id, status: 'pending', carrier_id: input.carrier_id == null ? '' : String(input.carrier_id), total: 100, processed: 0, advanced: 0, error: '', created_at: time, started_at: '', finished_at: '' } tasks.set(id, { data, polls: 0 }) } } else if (path.startsWith('/api/admin/polling-configs/recalculate-due/')) { const id = decodeURIComponent( path.slice('/api/admin/polling-configs/recalculate-due/'.length) ) if (!tasks.has(id)) tasks.set(id, { data: { task_id: id, carrier_id: '8', total: 100, processed: 0, advanced: 0, error: '', created_at: time, started_at: '', finished_at: '' }, polls: 0 }) const entry = tasks.get(id) entry.polls++ data = { ...entry.data, status: entry.polls > 2 ? 'completed' : 'running', processed: Math.min(100, entry.polls * 40), advanced: entry.polls * 10, started_at: time, finished_at: entry.polls > 2 ? time : '' } } else if (path === '/api/admin/polling-stats/pre-reset') { data = { carriers: [ { carrier_id: 8, carrier_name: '模拟运营商账户', previous: { active_package_cards: 100, checked_in_window: 80, not_checked_in_window: 20, reset_at: time, last_offset_buckets: { le_5: 0, le_15: 20, le_30: 15, le_60: 25, gt_60: 10 } }, upcoming: null } ] } } else if (path === '/api/admin/polling-stats/tiers') { data = { by_config: [ { config_id: 1, config_name: configs[0].config_name, cards: 95 }, { config_id: 0, config_name: '', cards: 5 } ], by_package_state: { active: 80, depleted: 10, pending: 5, none: 3, unknown: 2 }, computed_at: time, total_cards: 100, unmatched_in_stock: 2, unmatched_sold_no_package: 3 } } else if (path === '/api/admin/polling-configs' && req.method === 'GET') { const query = url.searchParams.get('config_name') || '' data = page( configs.filter((item) => item.config_name.includes(query)), url ) } else if (path === '/api/admin/polling-configs' && req.method === 'POST') { data = { ...input, id: configs.length + 1, status: 1, created_at: time, updated_at: time, warnings: ['本地模拟:保存提示'] } configs.push(data) } else if (/^\/api\/admin\/polling-configs\/\d+$/.test(path) && req.method === 'PUT') { const row = configs.find((item) => item.id === Number(path.split('/').at(-1))) Object.assign(row, input) for (const key of input.clear_fields || []) row[key] = null delete row.clear_fields data = { ...row, warnings: ['本地模拟:保存提示'] } } else if (/^\/api\/admin\/polling-configs\/\d+$/.test(path) && req.method === 'GET') data = configs.find((item) => item.id === Number(path.split('/').at(-1))) else { code = 400 msg = '此本地验收接口未定义,已拦截写请求' } res.setHeader('Content-Type', 'application/json; charset=utf-8') res.end(JSON.stringify({ code, msg, data })) }) gatewayFixtures.configureServer(server) } }