import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; const root = new URL('..', import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1'); const read = (path) => readFileSync(join(root, path), 'utf8'); const app = read('admin/src/App.vue'); for (const pattern of [ 'OperationsOverviewPanel', "activeModule = 'overview'", "activeModule = 'cleaning'", "activeModule = 'stores'", "activeModule = 'orders'", "activeModule = 'payments'", "activeModule = 'thirdParty'", "activeModule = 'people'", "activeModule = 'devices'", "activeModule = 'platformApps'", '平台运营总览', 'StoresRoomsPanel', 'OrdersPanel', 'PaymentsPanel', 'ThirdPartyPanel', 'MembersStaffPanel', 'DevicesPanel', 'PlatformAppsPanel', '运营总览', 'savedToken', 'loadCleaningWorkspace' ]) { assert.match(app, new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))); } const overview = read('admin/src/components/OperationsOverviewPanel.vue'); for (const pattern of [ 'listManagedStores', 'getBusinessStatistics', '近 7 日', '近 30 日', '近 90 日', '成功实收', '预订金额', '下单会员', '验券成功', '当前房态', '支付渠道', '每日收入趋势', '核心业务导航', 'open-cleaning', '后续增量', 'collectedAmountCents', 'availableRoomTotal', 'paymentChannels', 'dailyRevenue', 'errorMessage' ]) { assert.match(overview, new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))); } const api = read('admin/src/api.ts'); assert.match(api, /request\(session, '\/stores'\)/); assert.match(api, /request\(session, `\/statistics\?\$\{params\}`\)/); for (const pattern of [ 'createManagedStore', 'updateManagedStore', 'archiveManagedStore', 'listManagedRooms', 'createManagedRoom', 'updateManagedRoom', 'updateManagedRoomStatus', 'addManagedRoomDisabledPeriod' ]) { assert.match(api, new RegExp(pattern)); } const stores = read('admin/src/components/StoresRoomsPanel.vue'); for (const pattern of [ '门店与房间管理', '搜索门店、城市或地址', '每周营业时间', 'wifiPassword', 'listManagedStores', 'listManagedRooms', 'updateManagedRoomStatus', '新增房间', '停用时段', 'configurationStatus', 'operationalStatus', 'archiveManagedRoom' ]) { assert.match(stores, new RegExp(pattern)); } const orders = read('admin/src/components/OrdersPanel.vue'); for (const pattern of [ '订单筛选与处置', 'listManagedOrders', 'getManagedOrder', 'listManagedOrderHistory', 'executeManagedOrderAction', 'addManagedOrderNote', 'adjustManagedOrderTime', 'renewManagedOrder', 'changeManagedOrderRoom', '导出当前页', '状态流水', 'allowedActions' ]) { assert.match(orders, new RegExp(pattern)); } const payments = read('admin/src/components/PaymentsPanel.vue'); for (const pattern of [ '支付、退款与分账', 'getProfitSharingSnapshot', 'saveCollectionAccount', 'saveProfitShareReceiver', 'saveProfitSharePolicy', 'createWechatRefund', 'requestWechatReconciliation', 'executeProfitSharing', '凭据只保存环境引用', 'clientRequestId', '生产 API', '非生产 Mock' ]) { assert.match(payments, new RegExp(pattern)); } const thirdParty = read('admin/src/components/ThirdPartyPanel.vue'); for (const pattern of [ 'listThirdPartyRecords', 'getThirdPartySetup', 'redeemGroupVoucher', 'redeemGroupVoucherManually', 'saveThirdPartyConfig', 'saveThirdPartyMapping', 'voucherCode', 'PENDING_MAPPING' ]) { assert.match(thirdParty, new RegExp(pattern)); } const thirdPartyRoutes = read('backend/src/routes/third-party.ts'); assert.match(thirdPartyRoutes, /'\/admin-api\/third-party\/setup'/); assert.match(thirdPartyRoutes, /'\/admin-api\/group-vouchers\/redeem'/); const people = read('admin/src/components/MembersStaffPanel.vue'); for (const pattern of [ 'listMembers', 'getMember', 'listStaffUsers', 'createStaffUser', 'updateStaffUser', 'resetStaffSessions', 'memberDetail', 'staffForm' ]) { assert.match(people, new RegExp(pattern)); } const userRoutes = read('backend/src/routes/user-management.ts'); assert.match(userRoutes, /userType: z\.enum\(\['STAFF'\]\)\.optional\(\)/); const devices = read('admin/src/components/DevicesPanel.vue'); for (const pattern of [ 'getDeviceTopology', 'createDeviceAsset', 'bindDeviceChannel', 'bindSubDevice', 'addDeviceMaintenance', 'controlDevice', 'openAlerts', 'Sub‑1G' ]) { assert.match(devices, new RegExp(pattern)); } const platformApps = read('admin/src/components/PlatformAppsPanel.vue'); for (const pattern of ['listPlatformApplications', 'updatePlatformApplicationConfig', 'bindPlatformApplication', 'AppSecret', 'extraConfigText']) { assert.match(platformApps, new RegExp(pattern)); } const platformRoutes = read('backend/src/routes/platform-management.ts'); assert.match(platformRoutes, /'\/admin-api\/platform-apps'/); assert.match(platformRoutes, /PLATFORM_MANAGEMENT_FORBIDDEN/); const routes = read('backend/src/routes/business-statistics.ts'); assert.match(routes, /'\/app-api\/management\/statistics', '\/admin-api\/statistics'/); const routeTest = read('backend/tests/business-statistics.test.mjs'); assert.match(routeTest, /url: '\/admin-api\/statistics\?/); const storeRepository = read('backend/src/stores/store-room-repository.ts'); assert.match(storeRepository, /wifi_password = COALESCE\(\?, wifi_password\)/); assert.match(storeRepository, /FROM qipai_store_business_hours/); assert.match(storeRepository, /wifiConfigured/); const orderQueryRoutes = read('backend/src/routes/order-query.ts'); assert.match(orderQueryRoutes, /'\/admin-api\/orders'/); const orderStateRoutes = read('backend/src/routes/order-state.ts'); assert.match(orderStateRoutes, /'\/admin-api\/orders\/:orderId\/history'/); const styles = read('admin/src/styles.css'); for (const pattern of [ '.overview-page', '.overview-toolbar', '.overview-metrics', '.overview-grid', '.revenue-bars', '.module-grid', '.store-layout', '.store-card', '.hours-editor', '.order-page-summary', '.history-card', '.payment-metrics', '.payment-tools-grid', '.third-party-metrics', '.settings-preview', '.people-metrics', '.member-detail-grid', '.device-metrics', '.device-topology-grid', '.platform-app-metrics', '@media (max-width: 980px)', '@media (max-width: 560px)' ]) { assert.match(styles, new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))); } console.log('PASS: M08-D admin operations overview and scoped business statistics are present.');