#!/usr/bin/env node /** * 从 src/resources/oneos-web-legacy 生成 ONE-OS Web 端原型与侧边栏菜单项。 * * 用法:node scripts/sync-oneos-web-prototypes.mjs */ import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const projectRoot = path.resolve(__dirname, '..'); const legacyRoot = path.join(projectRoot, 'src/resources/oneos-web-legacy'); const prototypesRoot = path.join(projectRoot, 'src/prototypes'); const sidebarPath = path.join(projectRoot, '.axhub/make/sidebar-tree.json'); const PAGE_ID_RE = /^[a-z0-9-]+$/u; const XLL_MODULES = [ { slug: 'oneos-web-safety-training', title: '业务-司机安全培训', skipCodegen: true }, ]; const MODULES = [ { slug: 'oneos-web-workbench', title: '工作台', files: ['工作台.jsx'] }, { slug: 'oneos-web-login', title: '登录', files: ['登录.jsx'] }, { slug: 'oneos-web-vehicle-asset', title: '车辆管理', files: ['车辆管理.jsx', '车辆管理-查看.jsx'] }, { slug: 'oneos-web-contract-template', title: '合同模板管理', files: ['合同模板管理.jsx'] }, { slug: 'oneos-web-help-center', title: '帮助中心', dir: '帮助中心' }, { slug: 'oneos-web-finance', title: '财务管理', dir: '财务管理', excludeDirs: ['文档'] }, { slug: 'oneos-web-procurement', title: '采购管理', dir: '采购管理' }, { slug: 'oneos-web-lease-contract', title: '车辆租赁合同', dir: '车辆租赁合同' }, { slug: 'oneos-web-h2-station', title: '加氢站管理', dir: '加氢站管理', excludeDirs: ['export-tools', 'AI-加氢站站点信息-complete'] }, { slug: 'oneos-web-data-analysis', title: '数据分析', dir: '数据分析' }, { slug: 'oneos-web-ledger-data', title: '台账数据', dir: '台账数据', excludeDirs: ['docs'] }, { slug: 'oneos-web-business', title: '业务管理', dir: '业务管理', excludeDirs: ['文档', 'export-tools', 'AI-保险采购-complete'] }, { slug: 'oneos-web-ops', title: '运维管理', dir: '运维管理' }, { slug: 'oneos-web-requirements', title: '需求说明', dir: '需求说明', docsOnly: true }, ]; function slugifyPageId(relPath) { const stem = relPath.replace(/\.jsx$/u, '').replace(/[\\/]/g, '-'); const ascii = stem.replace(/[^\x00-\x7F]/gu, (ch) => `u${ch.charCodeAt(0).toString(16)}`); let id = ascii.toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, ''); if (!id || !PAGE_ID_RE.test(id)) { id = `p${Math.abs(hashCode(relPath)).toString(36)}`; } return id.slice(0, 48); } function hashCode(value) { let hash = 0; for (let i = 0; i < value.length; i += 1) { hash = ((hash << 5) - hash) + value.charCodeAt(i); hash |= 0; } return hash; } function collectJsxFiles(module) { if (module.docsOnly) { return []; } if (module.files) { return module.files.map((file) => ({ rel: file, abs: path.join(legacyRoot, file), title: file.replace(/\.jsx$/u, ''), })); } const baseDir = path.join(legacyRoot, module.dir); const results = []; const walk = (dir, prefix = '') => { if (!fs.existsSync(dir)) return; for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { if (entry.name.startsWith('.')) continue; const full = path.join(dir, entry.name); const rel = path.posix.join(module.dir, prefix, entry.name).replace(/\\/g, '/'); if (entry.isDirectory()) { if (module.excludeDirs?.includes(entry.name)) continue; walk(full, prefix ? `${prefix}/${entry.name}` : entry.name); continue; } if (!entry.name.endsWith('.jsx')) continue; results.push({ rel, abs: full, title: entry.name.replace(/\.jsx$/u, ''), }); } }; walk(baseDir); return results.sort((a, b) => a.rel.localeCompare(b.rel, 'zh-CN')); } function prepareLegacyJsx(sourcePath, targetPath) { const raw = fs.readFileSync(sourcePath, 'utf8'); let content = raw; if (!/export\s+default\s+Component/u.test(content)) { content = `${content.trim()}\n\nexport default Component;\n`; } fs.mkdirSync(path.dirname(targetPath), { recursive: true }); fs.writeFileSync(targetPath, content, 'utf8'); } function writeRequirementsPrototype(module) { const protoDir = path.join(prototypesRoot, module.slug); fs.mkdirSync(protoDir, { recursive: true }); const indexTsx = `/** * @name ${module.title} * 自 ONE-OS web端/需求说明 归档 */ import React, { useMemo, useState } from 'react'; import { Card, Col, Input, List, Row, Typography } from 'antd'; const DOC_MAP = import.meta.glob('../../resources/oneos-web-legacy/需求说明/**/*', { query: '?raw', import: 'default', eager: true, }); const FILES = Object.keys(DOC_MAP) .map((key) => key.replace('../../resources/oneos-web-legacy/需求说明/', '')) .filter(Boolean) .sort((a, b) => a.localeCompare(b, 'zh-CN')) .map((rel) => ({ rel, title: rel })); export default function OneosWebRequirementsPage() { const [query, setQuery] = useState(''); const [active, setActive] = useState(FILES[0]?.rel ?? ''); const filtered = useMemo(() => { const q = query.trim(); if (!q) return FILES; return FILES.filter((item) => item.title.includes(q)); }, [query]); const content = useMemo(() => { if (!active) return ''; const key = \`../../resources/oneos-web-legacy/需求说明/\${active}\`; return String(DOC_MAP[key] ?? '无法加载文档'); }, [active]); return ( setQuery(e.target.value)} style={{ width: 160 }} />}> ( setActive(item.rel)}> {item.title} )} /> {content} ); } `; fs.writeFileSync(path.join(protoDir, 'index.tsx'), indexTsx, 'utf8'); } function writePrototype(module, pages) { const protoDir = path.join(prototypesRoot, module.slug); const pagesDir = path.join(protoDir, 'pages'); fs.mkdirSync(pagesDir, { recursive: true }); const pageEntries = []; const importLines = []; const usedIds = new Set(); pages.forEach((page, index) => { const fileName = `${String(index + 1).padStart(2, '0')}-${path.basename(page.rel)}`; const target = path.join(pagesDir, fileName); prepareLegacyJsx(page.abs, target); const importName = `Page${index + 1}`; importLines.push(`import ${importName} from './pages/${fileName}';`); let id = slugifyPageId(page.rel); while (usedIds.has(id)) id = `${id}-${index + 1}`; usedIds.add(id); pageEntries.push({ id, title: page.title, importName }); }); const pagesArray = pageEntries.map((p) => ` { id: '${p.id}', title: '${p.title.replace(/'/g, "\\'")}', component: ${p.importName} },`).join('\n'); const indexTsx = `/** * @name ${module.title} * 自 ONE-OS web端 原稿复刻(${pages.length} 个页面) */ import '../../common/oneosWebLegacy/legacyGlobals'; import React from 'react'; ${importLines.join('\n')} import { OneosWebLegacyShell } from '../../common/oneosWebLegacy/OneosWebLegacyShell'; const pages = [ ${pagesArray} ]; export default function ${toComponentName(module.slug)}() { return ( ); } `; fs.writeFileSync(path.join(protoDir, 'index.tsx'), indexTsx, 'utf8'); } function toComponentName(slug) { return slug .split('-') .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) .join(''); } function toSidebarItem(module) { return { id: `item:prototypes:${module.slug}`, kind: 'item', title: module.title, itemKey: `prototypes/${module.slug}`, }; } function updateSidebar(webModules, xllModules) { const sidebar = JSON.parse(fs.readFileSync(sidebarPath, 'utf8')); const prefix = 'item:prototypes:oneos-web-'; const existing = (sidebar.prototypes ?? []).filter( (item) => item.id !== 'folder-prototypes-oneos-web' && item.id !== 'folder-prototypes-xll-miniapp' && item.itemKey !== 'prototypes/contract-template-management' && !String(item.id).startsWith(prefix) && !String(item.itemKey).startsWith('prototypes/oneos-web-'), ); const xllFolder = { id: 'folder-prototypes-xll-miniapp', kind: 'folder', title: '小羚羚小程序', children: xllModules.map(toSidebarItem), }; const oneosFolder = { id: 'folder-prototypes-oneos-web', kind: 'folder', title: 'ONE-OS Web 端', defaultExpanded: true, children: webModules.map(toSidebarItem), }; sidebar.prototypes = [...existing, xllFolder, oneosFolder]; sidebar.updatedAt = new Date().toISOString(); fs.writeFileSync(sidebarPath, `${JSON.stringify(sidebar, null, 2)}\n`, 'utf8'); } function main() { if (!fs.existsSync(legacyRoot)) { console.error('缺少 src/resources/oneos-web-legacy,请先复制 ONE-OS web端 资料。'); process.exit(1); } const allModules = [...XLL_MODULES, ...MODULES]; for (const module of allModules) { if (module.skipCodegen) { console.log(`[skip codegen] ${module.slug}(入口由项目内手写维护)`); continue; } if (module.docsOnly) { writeRequirementsPrototype(module); console.log(`[ok] ${module.slug} (需求说明文档浏览)`); continue; } const pages = collectJsxFiles(module); if (!pages.length) { console.warn(`[skip] ${module.slug}: 未找到 jsx 页面`); continue; } writePrototype(module, pages); console.log(`[ok] ${module.slug} (${pages.length} pages)`); } updateSidebar(MODULES, XLL_MODULES); console.log('已更新 .axhub/make/sidebar-tree.json'); } main();