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>
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
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: "label", children: "children" }" />',
|
||||
'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)
|
||||
Reference in New Issue
Block a user