Files
ERP/element-plus-lab/scripts/generate-element-plus-inventory.mjs
T
QiufengandClaude Opus 4.8 5e51dc3f56 SNAPSHOT W7 已部署稳定态 — 凯迪ERP+OA一体化平台 (MET 73.3%)
恢复点(restore point)。别人改崩后可 git reset --hard 回到此提交。

== 此快照内容 ==
- 后端 oa-backend: 734 控制器 / 711 实体 (Spring Boot 3.2.5 + SQLite, 端口8091)
- 前端 modern-ui/app: Vue3+Vite, 约700页 (构建产物已在 oa-backend/src/main/resources/static)
- 数据库 oa-backend/data/oa.db: 含全部演示数据 (强制入库, 6.6MB)
- 交接文档 go.md + go-code-reference/endpoints/entities/database.md
- 多代理建设脚本 .claude/wf-*.js

== 状态 ==
- 对 凯迪科技ERP_20260507.xlsx 合规 MET ~73.3% (PARTIAL 75: 34可建+6种子/bug+35外部硬天花板)
- 安全: 5轮红队+5轮复检, default-deny分级鉴权, 连续零可利用
- W3~W7 累计补完436缺口; W8末轮(40缺口)为半成品(源码树可编译但未集成)
- 运行: cd oa-backend; java -jar build/libs/oa-backend-0.1.0.jar --server.port=8091; admin/123456

== 排除(gitignore, 可再生) ==
node_modules / oa-backend/build / .jdks / *.log / Backup-ERP-* / 弃用的OFBiz核心(只保留modern-ui)
完整文件夹备份见同目录 Backup-ERP-20260615-191517/ (含上述全部, 仅缺 node_modules)

时间戳: 20260615-191517

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:19:15 +08:00

239 lines
10 KiB
JavaScript

import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const componentsDir = path.join(root, 'node_modules/element-plus/es/components')
const directivesDir = path.join(root, 'node_modules/element-plus/es/directives')
const packageJson = JSON.parse(fs.readFileSync(path.join(root, 'node_modules/element-plus/package.json'), 'utf8'))
const elementPlus = await import('element-plus')
const componentSlugs = fs
.readdirSync(componentsDir, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.sort()
const directiveSlugs = fs
.readdirSync(directivesDir, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.sort()
const publicExports = Object.keys(elementPlus).filter((name) => /^El/.test(name)).sort()
const groupRules = [
['基础', ['affix', 'backtop', 'button', 'button-group', 'config-provider', 'divider', 'icon', 'link', 'scrollbar', 'space', 'text', 'watermark']],
['布局', ['aside', 'card', 'col', 'collapse', 'collapse-item', 'container', 'footer', 'header', 'main', 'row', 'splitter', 'splitter-panel']],
['表单', ['autocomplete', 'cascader', 'cascader-panel', 'checkbox', 'checkbox-button', 'checkbox-group', 'color-picker', 'color-picker-panel', 'date-picker', 'date-picker-panel', 'form', 'form-item', 'input', 'input-number', 'input-otp', 'input-tag', 'mention', 'option', 'option-group', 'radio', 'radio-button', 'radio-group', 'rate', 'select', 'select-v2', 'slider', 'switch', 'time-picker', 'time-select', 'transfer', 'tree-select', 'upload']],
['数据展示', ['avatar', 'avatar-group', 'badge', 'calendar', 'carousel', 'carousel-item', 'check-tag', 'countdown', 'descriptions', 'descriptions-item', 'empty', 'image', 'image-viewer', 'pagination', 'progress', 'result', 'skeleton', 'skeleton-item', 'statistic', 'table', 'table-column', 'table-v2', 'tag', 'tree', 'tree-v2']],
['导航', ['anchor', 'anchor-link', 'breadcrumb', 'breadcrumb-item', 'dropdown', 'dropdown-item', 'dropdown-menu', 'menu', 'menu-item', 'menu-item-group', 'page-header', 'segmented', 'step', 'steps', 'sub-menu', 'tab-pane', 'tabs', 'timeline', 'timeline-item']],
['反馈与覆盖层', ['alert', 'dialog', 'drawer', 'loading', 'message', 'message-box', 'notification', 'overlay', 'popconfirm', 'popover', 'popper', 'tooltip', 'tour', 'tour-step']],
]
const priorities = new Map(
[
'button',
'button-group',
'form',
'form-item',
'input',
'input-number',
'select',
'select-v2',
'option',
'option-group',
'date-picker',
'table',
'table-column',
'table-v2',
'pagination',
'tag',
'menu',
'menu-item',
'sub-menu',
'breadcrumb',
'breadcrumb-item',
'tabs',
'tab-pane',
'dropdown',
'dropdown-menu',
'dropdown-item',
'dialog',
'drawer',
'tree',
'tree-v2',
'tree-select',
'upload',
'descriptions',
'descriptions-item',
'alert',
'message',
'message-box',
'notification',
'popconfirm',
'tooltip',
'loading',
].map((slug) => [slug, '高'])
)
const contextOnly = new Set([
'anchor-link',
'aside',
'avatar-group',
'breadcrumb-item',
'button-group',
'carousel-item',
'cascader-panel',
'checkbox-button',
'checkbox-group',
'col',
'collapse-item',
'color-picker-panel',
'date-picker-panel',
'descriptions-item',
'dropdown-item',
'dropdown-menu',
'footer',
'form-item',
'header',
'image-viewer',
'main',
'menu-item',
'menu-item-group',
'option',
'option-group',
'radio-button',
'radio-group',
'skeleton-item',
'splitter-panel',
'step',
'sub-menu',
'tab-pane',
'table-column',
'timeline-item',
'tour-step',
])
function titleFromSlug(slug) {
const fixed = new Map([
['otp', 'OTP'],
['v2', 'V2'],
])
return slug
.split('-')
.map((part) => fixed.get(part) ?? part.charAt(0).toUpperCase() + part.slice(1))
.join(' ')
}
function exportNameFromSlug(slug) {
return `El${slug
.split('-')
.map((part) => (part === 'v2' ? 'V2' : part === 'otp' ? 'Otp' : part.charAt(0).toUpperCase() + part.slice(1)))
.join('')}`
}
function groupFor(slug) {
const match = groupRules.find(([, slugs]) => slugs.includes(slug))
return match?.[0] ?? '内部基础'
}
function codeFor(slug, tag, name) {
const snippets = {
alert: '<el-alert title="信用检查未完成" type="warning" show-icon :closable="false" />',
autocomplete: '<el-autocomplete v-model="keyword" :fetch-suggestions="querySearch" placeholder="搜索客户" />',
badge: '<el-badge :value="8"><el-button>待办</el-button></el-badge>',
breadcrumb: '<el-breadcrumb separator="/"><el-breadcrumb-item>销售管理</el-breadcrumb-item><el-breadcrumb-item>销售订单</el-breadcrumb-item></el-breadcrumb>',
button: '<el-button type="primary">新建订单</el-button>',
calendar: '<el-calendar />',
card: '<el-card><template #header>订单摘要</template>SO-10642</el-card>',
checkbox: '<el-checkbox v-model="checked">仅看异常</el-checkbox>',
'color-picker': '<el-color-picker v-model="color" />',
'date-picker': '<el-date-picker v-model="dateRange" type="daterange" />',
descriptions: '<el-descriptions :column="3" border><el-descriptions-item label="客户">上海辰光零售</el-descriptions-item></el-descriptions>',
dialog: '<el-dialog v-model="visible" title="审批确认">确认通过该订单?</el-dialog>',
drawer: '<el-drawer v-model="visible" title="单据详情">SO-10642</el-drawer>',
dropdown: '<el-dropdown><el-button>更多操作</el-button><template #dropdown><el-dropdown-menu><el-dropdown-item>导出</el-dropdown-item></el-dropdown-menu></template></el-dropdown>',
empty: '<el-empty description="暂无数据" />',
form: '<el-form label-position="top"><el-form-item label="客户"><el-input /></el-form-item></el-form>',
image: '<el-image style="width: 96px; height: 64px" src="/vite.svg" fit="contain" />',
input: '<el-input v-model="keyword" placeholder="搜索订单、客户、负责人" />',
'input-number': '<el-input-number v-model="amount" :min="0" />',
'input-tag': '<el-input-tag v-model="tags" placeholder="输入标签" />',
link: '<el-link type="primary">查看详情</el-link>',
menu: '<el-menu default-active="orders"><el-menu-item index="orders">销售订单</el-menu-item></el-menu>',
pagination: '<el-pagination layout="prev, pager, next, sizes" :total="128" />',
popconfirm: '<el-popconfirm title="确认作废?"><template #reference><el-button>作废</el-button></template></el-popconfirm>',
progress: '<el-progress :percentage="72" />',
radio: '<el-radio-group v-model="status"><el-radio-button label="approved">已审批</el-radio-button></el-radio-group>',
rate: '<el-rate v-model="score" />',
result: '<el-result icon="success" title="保存成功" />',
segmented: "<el-segmented v-model=\"density\" :options=\"['紧凑', '标准', '宽松']\" />",
select: '<el-select v-model="warehouse"><el-option label="华东中心仓" value="east" /></el-select>',
'select-v2': '<el-select-v2 v-model="warehouse" :options="warehouseOptions" />',
skeleton: '<el-skeleton :rows="3" animated />',
slider: '<el-slider v-model="threshold" />',
statistic: '<el-statistic title="待处理订单" :value="128" />',
switch: '<el-switch v-model="enabled" active-text="启用" />',
table: '<el-table :data="rows" class="erp-table"><el-table-column prop="orderId" label="单据号" /></el-table>',
'table-v2': '<el-table-v2 :columns="columns" :data="rows" :width="640" :height="240" />',
tabs: '<el-tabs><el-tab-pane label="摘要">订单摘要</el-tab-pane></el-tabs>',
tag: '<el-tag type="success">已审批</el-tag>',
text: '<el-text type="primary">业务提示</el-text>',
timeline: '<el-timeline><el-timeline-item timestamp="09:20">完成审批</el-timeline-item></el-timeline>',
'time-picker': '<el-time-picker v-model="time" />',
'time-select': '<el-time-select v-model="time" start="08:30" step="00:30" end="18:30" />',
tooltip: '<el-tooltip content="查看审计记录"><el-button>审计</el-button></el-tooltip>',
transfer: '<el-transfer v-model="selected" :data="transferData" />',
tree: '<el-tree :data="treeData" :props="{ label: &quot;label&quot;, children: &quot;children&quot; }" />',
'tree-select': '<el-tree-select v-model="org" :data="treeData" />',
upload: '<el-upload action="#"><el-button>上传附件</el-button></el-upload>',
}
if (snippets[slug]) return snippets[slug]
if (contextOnly.has(slug)) return `<!-- ${name} usually lives inside its parent component. -->\n<${tag}>${name}</${tag}>`
return `<${tag}>${name}</${tag}>`
}
const componentRecords = componentSlugs.map((slug) => {
const name = titleFromSlug(slug)
const exportName = exportNameFromSlug(slug)
return {
slug,
name,
tag: `el-${slug}`,
exportName,
hasPublicExport: publicExports.includes(exportName),
group: groupFor(slug),
priority: priorities.get(slug) ?? (groupFor(slug) === '内部基础' ? '低' : '中'),
contextOnly: contextOnly.has(slug),
code: codeFor(slug, `el-${slug}`, name),
}
})
const publicExportRecords = publicExports.map((name) => ({
name,
kind: componentRecords.some((record) => record.exportName === name) ? 'component' : 'service-or-subpart',
}))
const directiveRecords = directiveSlugs.map((slug) => ({
slug,
name: titleFromSlug(slug),
code: `v-${slug}`,
}))
const content = `// Generated by scripts/generate-element-plus-inventory.mjs from the local Element Plus package.\n// Do not hand-edit component counts; rerun the generator after upgrading Element Plus.\n\nexport const elementPlusInventoryMeta = ${JSON.stringify(
{
packageName: packageJson.name,
packageVersion: packageJson.version,
componentDirectories: componentRecords.length,
publicElExports: publicExportRecords.length,
directiveDirectories: directiveRecords.length,
},
null,
2
)}\n\nexport const elementPlusComponentRecords = ${JSON.stringify(componentRecords, null, 2)}\n\nexport const elementPlusPublicExportRecords = ${JSON.stringify(publicExportRecords, null, 2)}\n\nexport const elementPlusDirectiveRecords = ${JSON.stringify(directiveRecords, null, 2)}\n`
fs.writeFileSync(path.join(root, 'src/data/elementPlusInventory.js'), content)