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>
@@ -0,0 +1,609 @@
|
||||
# OFBiz Element Plus UI Lab
|
||||
|
||||
This directory is the UI lab for rebuilding OFBiz screens with Vue 3 and Element Plus. It is not wired to the OFBiz backend yet; it is the place to define reusable UI rules, component wrappers, and visual behavior before replacing real pages.
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm dev --port 5174
|
||||
pnpm build
|
||||
node scripts/generate-element-plus-inventory.mjs
|
||||
```
|
||||
|
||||
Local preview:
|
||||
|
||||
```text
|
||||
http://localhost:5174/
|
||||
```
|
||||
|
||||
Direct page preview:
|
||||
|
||||
```text
|
||||
http://localhost:5174/#/page/navigation
|
||||
http://localhost:5174/#/page/catalog?group=数据展示
|
||||
http://localhost:5174/#/component/table
|
||||
http://localhost:5174/#/component/table?view=code
|
||||
```
|
||||
|
||||
## File Map
|
||||
|
||||
```text
|
||||
src/main.js
|
||||
Imports Element Plus, global CSS, and mounts the app.
|
||||
|
||||
src/App.vue
|
||||
Thin app entry. It renders ElementPlusShowcase only.
|
||||
|
||||
src/components/ElementPlusShowcase.vue
|
||||
Page shell for the lab: sidebar, topbar, active page state, and page composition.
|
||||
|
||||
src/components/docs/DemoBlock.vue
|
||||
Shared documentation block. Every component example uses 展示 / 代码 tabs.
|
||||
|
||||
src/components/showcase/
|
||||
One file per documentation page. Keep examples split by component family.
|
||||
|
||||
src/data/showcaseData.js
|
||||
Page menu, Element Plus inventory, ERP priority levels, sample order rows, metric cards, and utility functions.
|
||||
|
||||
src/data/elementPlusInventory.js
|
||||
Generated full Element Plus inventory. Contains every local component directory, public El* export, and directive.
|
||||
|
||||
src/data/erpComponentDocs.js
|
||||
ERP documentation layer on top of the generated inventory. Adds wrapper names, local CSS classes,
|
||||
preview types, original code, ERP code, local style snippets, and OFBiz usage notes for every component.
|
||||
|
||||
scripts/generate-element-plus-inventory.mjs
|
||||
Regenerates the full inventory from node_modules/element-plus. Run after upgrading Element Plus.
|
||||
|
||||
src/styles/tokens.css
|
||||
ERP design tokens: color, spacing, radius, typography, shadows.
|
||||
|
||||
src/styles/erp-ui.css
|
||||
Global UI entry. Import this once before page-specific CSS in every modern OFBiz frontend.
|
||||
|
||||
src/styles/base.css
|
||||
Global reset and page baseline.
|
||||
|
||||
src/styles/element-overrides.css
|
||||
Element Plus CSS variable mapping and direct Element class overrides.
|
||||
|
||||
src/styles/erp-patterns.css
|
||||
Reusable ERP-specific classes such as .erp-table, .erp-form, .risk-dot, .component-chip.
|
||||
|
||||
src/styles/app.css
|
||||
Lab shell and demo-only layout styles. Do not put reusable product patterns here.
|
||||
```
|
||||
|
||||
## Element Plus Inventory
|
||||
|
||||
The catalog in `src/data/showcaseData.js` is checked against the local package:
|
||||
|
||||
```text
|
||||
element-plus: 2.14.1
|
||||
node_modules/element-plus/es/components: 122 directories
|
||||
public El* exports from element-plus: 123
|
||||
node_modules/element-plus/es/directives: 4 directories
|
||||
```
|
||||
|
||||
The showcase intentionally separates three things:
|
||||
|
||||
```text
|
||||
Component directories
|
||||
Concrete Element Plus component folders, including subcomponents such as Table Column,
|
||||
Tab Pane, Option, Dropdown Item, Carousel Item, and internal foundations.
|
||||
|
||||
Public El* exports
|
||||
Components plus public services/directives such as ElMessage, ElMessageBox,
|
||||
ElNotification, ElLoadingService, and Popper subparts.
|
||||
|
||||
Directive directories
|
||||
click-outside, mousewheel, repeat-click, trap-focus.
|
||||
```
|
||||
|
||||
When Element Plus is upgraded, run:
|
||||
|
||||
```bash
|
||||
node scripts/generate-element-plus-inventory.mjs
|
||||
```
|
||||
|
||||
Do not hand-edit component counts. The generated facts stay in `src/data/elementPlusInventory.js`.
|
||||
ERP-specific decisions stay in `src/data/erpComponentDocs.js`, so package upgrades do not wipe local design decisions.
|
||||
|
||||
## Local Component Docs
|
||||
|
||||
Every Element Plus component directory gets a local ERP doc record:
|
||||
|
||||
```js
|
||||
{
|
||||
slug: 'table',
|
||||
tag: 'el-table',
|
||||
exportName: 'ElTable',
|
||||
group: '数据展示',
|
||||
priority: '高',
|
||||
wrapperName: 'ErpTable',
|
||||
localClass: 'erp-table',
|
||||
originalCode: '<el-table ... />',
|
||||
erpCode: '<el-table class="erp-table" ... />',
|
||||
styleCode: '/* src/styles/erp-patterns.css */ ...',
|
||||
detailRoute: '#/component/table'
|
||||
}
|
||||
```
|
||||
|
||||
The catalog page reads `erpComponentDocs`, not the raw inventory directly. That is the split:
|
||||
|
||||
```text
|
||||
elementPlusInventory.js = package truth
|
||||
erpComponentDocs.js = OFBiz local usage and style truth
|
||||
```
|
||||
|
||||
High-frequency ERP components already have concrete preview and ERP code patterns:
|
||||
|
||||
```text
|
||||
Table / Table V2 / Pagination
|
||||
Menu / Sub Menu / Menu Item
|
||||
Breadcrumb / Breadcrumb Item / Page Header
|
||||
Form / Form Item / Input / Select / Select V2 / Date Picker
|
||||
Button / Button Group / Dropdown
|
||||
Dialog / Drawer / Popconfirm / Tooltip
|
||||
Tag / Alert / Message / MessageBox / Notification
|
||||
Tree / Tree V2 / Tree Select / Upload / Descriptions
|
||||
Empty / Progress / Result / Skeleton
|
||||
Timeline / Steps / Collapse / Tabs / Segmented
|
||||
Calendar / Image / Carousel / Statistic
|
||||
Switch / Slider / Rate / Color Picker / Transfer
|
||||
Space / Text / Link / Divider
|
||||
```
|
||||
|
||||
Subcomponents such as `Table Column`, `Breadcrumb Item`, `Menu Item`, `Dropdown Item`, `Option`, and `Tab Pane`
|
||||
are retained as independent detail pages, but their wrapper guidance points back to the parent component.
|
||||
|
||||
## CSS Order
|
||||
|
||||
Use the single global entry in future OFBiz frontend pages:
|
||||
|
||||
```js
|
||||
import './styles/erp-ui.css'
|
||||
import './styles/app.css'
|
||||
```
|
||||
|
||||
`erp-ui.css` keeps this internal order:
|
||||
|
||||
1. Element Plus official CSS.
|
||||
2. ERP tokens.
|
||||
3. Global baseline.
|
||||
4. Element Plus overrides.
|
||||
5. Reusable ERP patterns.
|
||||
|
||||
Page-specific styles come after `erp-ui.css`. Do not import Element Plus CSS directly in feature pages.
|
||||
|
||||
## Component Rules
|
||||
|
||||
Use Element Plus as the raw component layer. Do not use raw Element Plus directly in OFBiz feature pages once a reusable ERP wrapper exists.
|
||||
|
||||
Preferred path:
|
||||
|
||||
```text
|
||||
Element Plus primitive -> ERP wrapper/pattern -> OFBiz feature page
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```vue
|
||||
<el-table :data="rows" class="erp-table" stripe>
|
||||
<el-table-column prop="orderId" label="单据号" width="126" />
|
||||
<el-table-column prop="amount" label="金额" align="right" />
|
||||
</el-table>
|
||||
```
|
||||
|
||||
Later wrapper target:
|
||||
|
||||
```vue
|
||||
<ErpTable :rows="rows" :columns="columns" row-key="orderId" />
|
||||
```
|
||||
|
||||
## Component Decision Rules
|
||||
|
||||
Pick components by business task first, not by visual taste.
|
||||
|
||||
```text
|
||||
Page skeleton
|
||||
Container / Aside / Main / Card / Collapse / Splitter
|
||||
Use for module frame, page sections, details summary, and collapsible advanced content.
|
||||
|
||||
Navigation location
|
||||
Menu / Breadcrumb / Page Header / Tabs / Steps / Timeline
|
||||
Use for module switching, current path, local views, process position, and audit history.
|
||||
|
||||
Query and input
|
||||
Form / Input / Select / Date Picker / Tree Select / Autocomplete / Upload
|
||||
Use for search bars, editing forms, entity lookup, legal values, dates, and attachments.
|
||||
|
||||
Data scanning
|
||||
Table / Table V2 / Pagination / Descriptions / Tree / Statistic / Tag
|
||||
Use for dense business objects, read-only summaries, hierarchy, metrics, and statuses.
|
||||
|
||||
Actions and feedback
|
||||
Button / Dropdown / Popconfirm / Dialog / Drawer / Message / Alert / Result
|
||||
Use for primary actions, secondary action menus, confirmations, details, and completion states.
|
||||
|
||||
State and loading
|
||||
Empty / Skeleton / Progress / Badge / Calendar / Image / Transfer
|
||||
Use for no-data states, loading, measurable progress, counters, schedules, media, and assignment.
|
||||
```
|
||||
|
||||
Important component choices:
|
||||
|
||||
- Use `Table` for auditable ERP records; use `Table V2` only when row count and scroll performance are the actual problem.
|
||||
- Use `Descriptions` for read-only details; do not make read-only content look like disabled forms.
|
||||
- Use `Tabs` for views inside the same business object; use `Menu` for module-level navigation.
|
||||
- Use `Steps` for current process position; use `Timeline` for history and audit records.
|
||||
- Use `Drawer` for side details and audit context; use `Dialog` for short blocking confirmation or editing.
|
||||
- Use `Message` for short save feedback; use `Alert` for persistent warnings; use `Result` only at a flow endpoint.
|
||||
- Use `Autocomplete`, `Tree Select`, or `Cascader` for entity lookup; do not overload plain `Input` with hidden lookup logic.
|
||||
- Use `Dropdown` for secondary or low-frequency actions; never hide the main workflow action inside it.
|
||||
|
||||
## Page Layout Playbook
|
||||
|
||||
Use these four page skeletons before selecting individual controls.
|
||||
|
||||
```text
|
||||
List page
|
||||
Breadcrumb / Page Header
|
||||
Query Form
|
||||
Toolbar with one primary action
|
||||
Table
|
||||
Pagination
|
||||
Drawer for row details or audit context
|
||||
|
||||
Detail page
|
||||
Breadcrumb / object title / status tags
|
||||
Descriptions summary
|
||||
Tabs for lines, audit, attachments
|
||||
Table for line items
|
||||
Drawer for side context
|
||||
|
||||
Entry page
|
||||
Two-column Form
|
||||
Entity pickers for customer, supplier, org, warehouse
|
||||
Upload only near attachment fields
|
||||
Footer actions with one primary submit
|
||||
Popconfirm for destructive or irreversible actions
|
||||
|
||||
Process page
|
||||
Steps for current progress
|
||||
Alert for blocking warning
|
||||
Progress for measurable work
|
||||
Skeleton while content loads
|
||||
Result only when the process has ended
|
||||
```
|
||||
|
||||
Layout defaults:
|
||||
|
||||
- Query forms use 4 columns on desktop. Editing forms use 2 columns. Detail summaries use 2-3 columns.
|
||||
- List pages follow this order: page context, filters, toolbar, table, pagination, detail drawer.
|
||||
- The visible work area gets one `primary` button. Secondary actions are normal buttons, links, or dropdown items.
|
||||
- Numbers and money align right. Status is near the business object it describes.
|
||||
- Dense table pages should keep vertical rhythm tight; dialogs and destructive confirmations should keep more reading space.
|
||||
|
||||
## Catalog Layout Contract
|
||||
|
||||
The full component catalog is intentionally split into four layers:
|
||||
|
||||
```text
|
||||
Catalog summary
|
||||
Count and completeness signals for the local Element Plus inventory.
|
||||
|
||||
Page playbook
|
||||
OFBiz page templates that map common page types to component sets.
|
||||
|
||||
Usage map
|
||||
Business scenarios that link directly to the components used in that scenario.
|
||||
|
||||
Grouped component library
|
||||
All 122 Element Plus component directories, grouped by component family.
|
||||
```
|
||||
|
||||
Every component card includes:
|
||||
|
||||
```text
|
||||
Component name
|
||||
Element Plus tag and export
|
||||
ERP priority
|
||||
One-line usage rule
|
||||
Independent 展示 / 代码 tabs
|
||||
Direct detail link
|
||||
Direct component code page link
|
||||
```
|
||||
|
||||
Every component detail page includes:
|
||||
|
||||
```text
|
||||
展示
|
||||
原始代码
|
||||
ERP代码
|
||||
本地样式
|
||||
什么时候用
|
||||
什么时候不用
|
||||
排版规则
|
||||
落地 CSS 文件
|
||||
同组组件跳转
|
||||
```
|
||||
|
||||
## Page Split Contract
|
||||
|
||||
The lab is a multi-page component document. The left menu switches pages instead of scrolling to anchors.
|
||||
|
||||
Routes are hash based so every page and component can be linked directly:
|
||||
|
||||
```text
|
||||
#/page/catalog
|
||||
#/page/catalog?group=数据展示
|
||||
#/component/table
|
||||
#/component/menu
|
||||
#/component/breadcrumb
|
||||
```
|
||||
|
||||
The left menu calls `navigateTo('page/<id>')`, so page changes are real URL changes. The full catalog card titles
|
||||
and detail buttons call `navigateTo('component/<slug>')`, so every component has a direct page.
|
||||
|
||||
Pages are registered in `docPages` inside `src/data/showcaseData.js`. Each page carries:
|
||||
|
||||
```text
|
||||
id
|
||||
label
|
||||
icon
|
||||
title
|
||||
description
|
||||
```
|
||||
|
||||
Each file under `src/components/showcase/` owns one documentation page:
|
||||
|
||||
```text
|
||||
FoundationShowcase.vue Tokens and product tone
|
||||
MetricsShowcase.vue Statistic cards and dashboard summaries
|
||||
ComponentCatalogShowcase.vue Full Element Plus inventory
|
||||
ButtonShowcase.vue Buttons, groups, dropdown actions
|
||||
FormShowcase.vue Form, input, select, picker, switches
|
||||
TableShowcase.vue Table, columns, pagination, cells
|
||||
StatusShowcase.vue Tag, alert, progress, badge, timeline
|
||||
NavigationShowcase.vue Side menu, top menu, breadcrumb, page header
|
||||
TokenRail.vue Token notes and next component queue
|
||||
DocumentDrawer.vue Drawer detail preview
|
||||
```
|
||||
|
||||
When adding a new component family, create a new `*Showcase.vue` file, register it in `docPages`, and render it from `ElementPlusShowcase.vue`. Put sample data in `src/data/showcaseData.js` unless it is purely local demo state.
|
||||
|
||||
## Demo / Code Tabs
|
||||
|
||||
Every component page should use `DemoBlock.vue` so examples always include both the rendered component and the corresponding code.
|
||||
|
||||
```vue
|
||||
<DemoBlock
|
||||
:code="tableCode"
|
||||
description="ERP 表格优先保证扫描效率、状态清晰、数字右对齐和固定操作列。"
|
||||
eyebrow="Data Table"
|
||||
title="订单工作台表格"
|
||||
>
|
||||
<el-table :data="orderRows" class="erp-table" stripe />
|
||||
</DemoBlock>
|
||||
```
|
||||
|
||||
Code snippets live near the component demo as string constants. Keep snippets short and focused; do not paste an entire page when the user only needs the component call pattern.
|
||||
|
||||
The full catalog has its own tab contract:
|
||||
|
||||
```text
|
||||
Catalog card tabs:
|
||||
展示
|
||||
代码
|
||||
|
||||
Single component detail tabs:
|
||||
展示
|
||||
原始代码
|
||||
ERP代码
|
||||
本地样式
|
||||
```
|
||||
|
||||
Catalog card tab state is per component slug, so switching `Table` to `代码` does not switch `Button` or other cards.
|
||||
|
||||
Use direct links when reviewing:
|
||||
|
||||
```text
|
||||
http://localhost:5174/#/page/catalog
|
||||
http://localhost:5174/#/page/catalog?group=数据展示
|
||||
http://localhost:5174/#/component/table
|
||||
http://localhost:5174/#/component/table?view=code
|
||||
http://localhost:5174/#/component/menu
|
||||
http://localhost:5174/#/component/breadcrumb
|
||||
```
|
||||
|
||||
## Local Style Ownership
|
||||
|
||||
Reusable product CSS lives here:
|
||||
|
||||
```text
|
||||
src/styles/tokens.css
|
||||
Color, radius, spacing, typography, surface, state tokens.
|
||||
|
||||
src/styles/element-overrides.css
|
||||
Element Plus CSS variables and global Element class overrides.
|
||||
|
||||
src/styles/erp-patterns.css
|
||||
OFBiz reusable patterns: .erp-table, .erp-side-menu, .erp-breadcrumb,
|
||||
.erp-field-control, .erp-dense-pagination, .erp-tabs, .erp-status-tag,
|
||||
.erp-detail-descriptions, .erp-entity-card, .erp-upload.
|
||||
```
|
||||
|
||||
The lab-only page layout stays in `src/styles/app.css`. Do not move product wrapper styles there.
|
||||
|
||||
## Calling Pattern
|
||||
|
||||
State lives in the composition shell while each showcase section receives explicit props and emits updates:
|
||||
|
||||
```vue
|
||||
<FormShowcase
|
||||
v-model:check-values="checkValues"
|
||||
v-model:date-range="dateRange"
|
||||
v-model:order-status="orderStatus"
|
||||
/>
|
||||
```
|
||||
|
||||
Inside the section, keep Element Plus calls direct and obvious:
|
||||
|
||||
```vue
|
||||
<el-select :model-value="orderStatus" @update:model-value="$emit('update:orderStatus', $event)">
|
||||
<el-option label="已审批" value="approved" />
|
||||
</el-select>
|
||||
```
|
||||
|
||||
This is the migration pattern for OFBiz pages too: page state stays near the business workflow; reusable display or input behavior moves into an ERP wrapper only after it repeats.
|
||||
|
||||
## Reusable CSS
|
||||
|
||||
Use these files first:
|
||||
|
||||
```text
|
||||
src/styles/tokens.css
|
||||
src/styles/element-overrides.css
|
||||
src/styles/erp-patterns.css
|
||||
```
|
||||
|
||||
Current reusable classes:
|
||||
|
||||
```text
|
||||
.erp-form
|
||||
.erp-metric-card
|
||||
.erp-metric-card--large
|
||||
.erp-metric-card--small
|
||||
.erp-metric-card--mini
|
||||
.erp-table
|
||||
.form-grid
|
||||
.table-search
|
||||
.icon-cell
|
||||
.risk-dot
|
||||
.component-chip
|
||||
```
|
||||
|
||||
Component usage data lives in:
|
||||
|
||||
```text
|
||||
src/data/erpComponentDocs.js
|
||||
groupContracts
|
||||
groupGuidance
|
||||
previewGuidance
|
||||
componentDocOverrides
|
||||
erpComponentUsageMap
|
||||
erpLayoutPlaybook
|
||||
```
|
||||
|
||||
Catalog UI and per-component detail layout live in:
|
||||
|
||||
```text
|
||||
src/components/showcase/ComponentCatalogShowcase.vue
|
||||
```
|
||||
|
||||
Only product-reusable styling belongs in:
|
||||
|
||||
```text
|
||||
src/styles/erp-patterns.css
|
||||
src/styles/element-overrides.css
|
||||
src/styles/tokens.css
|
||||
```
|
||||
|
||||
Catalog-only grouping, playbook cards, mini previews, and documentation layout belong in:
|
||||
|
||||
```text
|
||||
src/styles/app.css
|
||||
```
|
||||
|
||||
Rule of thumb:
|
||||
|
||||
- If a style should apply across OFBiz modules, put it in `erp-patterns.css`.
|
||||
- If it changes Element Plus global behavior, put it in `element-overrides.css`.
|
||||
- If it is only for the lab page layout, put it in `app.css`.
|
||||
- If it is a primitive value such as color, spacing, radius, font, or shadow, put it in `tokens.css`.
|
||||
|
||||
## Metric Cards
|
||||
|
||||
Do not use the old left-color-bar card for OFBiz dashboards. It is too decorative and does not read like a durable ERP control.
|
||||
|
||||
Use one KPI component with three size levels:
|
||||
|
||||
```vue
|
||||
<div class="erp-metric-card erp-metric-card--large is-primary">...</div>
|
||||
<div class="erp-metric-card erp-metric-card--small is-warning">...</div>
|
||||
<div class="erp-metric-card erp-metric-card--mini is-danger">...</div>
|
||||
```
|
||||
|
||||
Default dashboard size: `erp-metric-card--large`.
|
||||
|
||||
Use `--large` for dashboard summary rows and landing workspaces. Use `--small` above dense tables or within secondary panels. Use `--mini` inside narrow sidebars, table toolbars, or pages with many metrics.
|
||||
|
||||
## Design Intent
|
||||
|
||||
This UI system should feel quiet, durable, and operational. OFBiz users need to scan dense business data, make safe changes, and repeat workflows all day.
|
||||
|
||||
Keep these defaults:
|
||||
|
||||
- Typography: 12/13/14/18/24.
|
||||
- Main radius: 6px for controls, 8px for panels.
|
||||
- Tables: compact row height, sticky action column when needed, numbers right-aligned.
|
||||
- Colors: low-saturation status colors, strong enough for scanning.
|
||||
- Buttons: one primary action per visible work area.
|
||||
- Forms: grouped fields, predictable labels, clear validation states.
|
||||
- Avoid marketing layouts, oversized heroes, decorative cards, and one-hue themes.
|
||||
|
||||
## Priority For OFBiz
|
||||
|
||||
High-priority components to turn into ERP wrappers first:
|
||||
|
||||
```text
|
||||
Table / Table V2
|
||||
Form
|
||||
Input / Select / Date Picker / Tree Select
|
||||
Button / Dropdown
|
||||
Tag / Alert / Message / Popconfirm
|
||||
Tabs / Menu / Breadcrumb / Steps
|
||||
Drawer / Dialog
|
||||
Upload
|
||||
Descriptions
|
||||
Tree / Tree V2
|
||||
Pagination
|
||||
```
|
||||
|
||||
Lower-priority components can stay as direct Element Plus usage until real OFBiz screens need them.
|
||||
|
||||
## Adding A Wrapper
|
||||
|
||||
Create wrappers under a future `src/components/erp/` directory. Keep wrappers narrow and composable.
|
||||
|
||||
Example target structure:
|
||||
|
||||
```text
|
||||
src/components/erp/ErpTable.vue
|
||||
src/components/erp/ErpSearchBar.vue
|
||||
src/components/erp/ErpStatusTag.vue
|
||||
src/components/erp/ErpMoneyCell.vue
|
||||
src/components/erp/ErpEntityPicker.vue
|
||||
src/components/erp/ErpAuditTimeline.vue
|
||||
```
|
||||
|
||||
Wrapper acceptance criteria:
|
||||
|
||||
- Has a simple prop API.
|
||||
- Uses Element Plus internally.
|
||||
- Uses tokens and ERP classes rather than hardcoded one-off styles.
|
||||
- Handles empty, loading, disabled, error, and long-content states.
|
||||
- Is demonstrated in a showcase section before being used in OFBiz pages.
|
||||
|
||||
## Backend Integration Direction
|
||||
|
||||
Keep OFBiz backend unchanged at first. Build the modern frontend page by page:
|
||||
|
||||
1. Recreate the target page in the lab with sample data.
|
||||
2. Extract reusable wrappers.
|
||||
3. Connect the page to OFBiz services/API.
|
||||
4. Preserve permissions, auditability, and business workflow semantics.
|
||||
5. Replace the legacy page only after visual and workflow checks pass.
|
||||
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>OFBiz Element Plus Lab</title>
|
||||
<script type="module" crossorigin src="/assets/index-P1o3hAD-.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-zvyUJsMr.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>OFBiz Element Plus Lab</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "ofbiz-element-plus-lab",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview --host 0.0.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"element-plus": "^2.14.1",
|
||||
"vue": "^3.5.24"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.2",
|
||||
"vite": "^7.2.7"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,997 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@element-plus/icons-vue':
|
||||
specifier: ^2.3.2
|
||||
version: 2.3.2(vue@3.5.35)
|
||||
element-plus:
|
||||
specifier: ^2.14.1
|
||||
version: 2.14.1(vue@3.5.35)
|
||||
vue:
|
||||
specifier: ^3.5.24
|
||||
version: 3.5.35
|
||||
devDependencies:
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^6.0.2
|
||||
version: 6.0.7(vite@7.3.5)(vue@3.5.35)
|
||||
vite:
|
||||
specifier: ^7.2.7
|
||||
version: 7.3.5
|
||||
|
||||
packages:
|
||||
|
||||
'@babel/helper-string-parser@7.29.7':
|
||||
resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-identifier@7.29.7':
|
||||
resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/parser@7.29.7':
|
||||
resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@babel/types@7.29.7':
|
||||
resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@ctrl/tinycolor@4.2.0':
|
||||
resolution: {integrity: sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
'@element-plus/icons-vue@2.3.2':
|
||||
resolution: {integrity: sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==}
|
||||
peerDependencies:
|
||||
vue: ^3.2.0
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.7':
|
||||
resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/android-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.27.7':
|
||||
resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.27.7':
|
||||
resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/darwin-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.27.7':
|
||||
resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.27.7':
|
||||
resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/linux-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.27.7':
|
||||
resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.27.7':
|
||||
resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.27.7':
|
||||
resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.27.7':
|
||||
resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.27.7':
|
||||
resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.27.7':
|
||||
resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.27.7':
|
||||
resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.27.7':
|
||||
resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/netbsd-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-x64@0.27.7':
|
||||
resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/openbsd-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.27.7':
|
||||
resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openharmony-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@esbuild/sunos-x64@0.27.7':
|
||||
resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/win32-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.27.7':
|
||||
resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.27.7':
|
||||
resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@floating-ui/core@1.7.5':
|
||||
resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
|
||||
|
||||
'@floating-ui/dom@1.7.6':
|
||||
resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==}
|
||||
|
||||
'@floating-ui/utils@0.2.11':
|
||||
resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
'@rolldown/pluginutils@1.0.1':
|
||||
resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.61.1':
|
||||
resolution: {integrity: sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-android-arm64@4.61.1':
|
||||
resolution: {integrity: sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.61.1':
|
||||
resolution: {integrity: sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.61.1':
|
||||
resolution: {integrity: sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.61.1':
|
||||
resolution: {integrity: sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.61.1':
|
||||
resolution: {integrity: sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.61.1':
|
||||
resolution: {integrity: sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.61.1':
|
||||
resolution: {integrity: sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.61.1':
|
||||
resolution: {integrity: sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.61.1':
|
||||
resolution: {integrity: sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.61.1':
|
||||
resolution: {integrity: sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.61.1':
|
||||
resolution: {integrity: sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.61.1':
|
||||
resolution: {integrity: sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.61.1':
|
||||
resolution: {integrity: sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@rollup/rollup-openharmony-arm64@4.61.1':
|
||||
resolution: {integrity: sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.61.1':
|
||||
resolution: {integrity: sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.61.1':
|
||||
resolution: {integrity: sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-x64-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.61.1':
|
||||
resolution: {integrity: sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@sxzz/popperjs-es@2.11.8':
|
||||
resolution: {integrity: sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==}
|
||||
|
||||
'@types/estree@1.0.9':
|
||||
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
|
||||
|
||||
'@types/lodash-es@4.17.12':
|
||||
resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
|
||||
|
||||
'@types/lodash@4.17.24':
|
||||
resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==}
|
||||
|
||||
'@types/web-bluetooth@0.0.21':
|
||||
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
|
||||
|
||||
'@vitejs/plugin-vue@6.0.7':
|
||||
resolution: {integrity: sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
peerDependencies:
|
||||
vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
vue: ^3.2.25
|
||||
|
||||
'@vue/compiler-core@3.5.35':
|
||||
resolution: {integrity: sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==}
|
||||
|
||||
'@vue/compiler-dom@3.5.35':
|
||||
resolution: {integrity: sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==}
|
||||
|
||||
'@vue/compiler-sfc@3.5.35':
|
||||
resolution: {integrity: sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw==}
|
||||
|
||||
'@vue/compiler-ssr@3.5.35':
|
||||
resolution: {integrity: sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==}
|
||||
|
||||
'@vue/reactivity@3.5.35':
|
||||
resolution: {integrity: sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==}
|
||||
|
||||
'@vue/runtime-core@3.5.35':
|
||||
resolution: {integrity: sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg==}
|
||||
|
||||
'@vue/runtime-dom@3.5.35':
|
||||
resolution: {integrity: sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA==}
|
||||
|
||||
'@vue/server-renderer@3.5.35':
|
||||
resolution: {integrity: sha512-NkebSOYdB97wi8OQcO3HqzZSlymJi/aWsN/7h74OSVhRTm6qGs3Jp3e0rCXynmWwSlKeRrnlIug+ilYoHBmQDA==}
|
||||
peerDependencies:
|
||||
vue: 3.5.35
|
||||
|
||||
'@vue/shared@3.5.35':
|
||||
resolution: {integrity: sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==}
|
||||
|
||||
'@vueuse/core@14.3.0':
|
||||
resolution: {integrity: sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==}
|
||||
peerDependencies:
|
||||
vue: ^3.5.0
|
||||
|
||||
'@vueuse/metadata@14.3.0':
|
||||
resolution: {integrity: sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw==}
|
||||
|
||||
'@vueuse/shared@14.3.0':
|
||||
resolution: {integrity: sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg==}
|
||||
peerDependencies:
|
||||
vue: ^3.5.0
|
||||
|
||||
async-validator@4.2.5:
|
||||
resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
|
||||
|
||||
csstype@3.2.3:
|
||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||
|
||||
dayjs@1.11.21:
|
||||
resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==}
|
||||
|
||||
element-plus@2.14.1:
|
||||
resolution: {integrity: sha512-UFnm1+BckNi+azkKJ7L32q1uXs9ekr99Z9pWTQPeDR05jqEWUwQq51ro4kZMVrANbjknX3Z7ukCZwTi2T6Tr9A==}
|
||||
peerDependencies:
|
||||
vue: ^3.3.7
|
||||
|
||||
entities@7.0.1:
|
||||
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
esbuild@0.27.7:
|
||||
resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
estree-walker@2.0.2:
|
||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||
|
||||
fdir@6.5.0:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
picomatch: ^3 || ^4
|
||||
peerDependenciesMeta:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
lodash-es@4.18.1:
|
||||
resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==}
|
||||
|
||||
lodash-unified@1.0.3:
|
||||
resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==}
|
||||
peerDependencies:
|
||||
'@types/lodash-es': '*'
|
||||
lodash: '*'
|
||||
lodash-es: '*'
|
||||
|
||||
lodash@4.18.1:
|
||||
resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
|
||||
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
memoize-one@6.0.0:
|
||||
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
|
||||
|
||||
nanoid@3.3.12:
|
||||
resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
normalize-wheel-es@1.2.0:
|
||||
resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
picomatch@4.0.4:
|
||||
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
postcss@8.5.15:
|
||||
resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
rollup@4.61.1:
|
||||
resolution: {integrity: sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
tinyglobby@0.2.17:
|
||||
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
vite@7.3.5:
|
||||
resolution: {integrity: sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/node': ^20.19.0 || >=22.12.0
|
||||
jiti: '>=1.21.0'
|
||||
less: ^4.0.0
|
||||
lightningcss: ^1.21.0
|
||||
sass: ^1.70.0
|
||||
sass-embedded: ^1.70.0
|
||||
stylus: '>=0.54.8'
|
||||
sugarss: ^5.0.0
|
||||
terser: ^5.16.0
|
||||
tsx: ^4.8.1
|
||||
yaml: ^2.4.2
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
jiti:
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
lightningcss:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
sass-embedded:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
sugarss:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
tsx:
|
||||
optional: true
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
vue-component-type-helpers@3.3.3:
|
||||
resolution: {integrity: sha512-x4nsFpy5Pe8fqPzp/5vkTPeTTDBpAx4WVtV47Ejt0+2FQrq4pRRsJs7JmYRqMFzTu/LW+pCWEjQ3YVCkPV7f9g==}
|
||||
|
||||
vue@3.5.35:
|
||||
resolution: {integrity: sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==}
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@babel/helper-string-parser@7.29.7': {}
|
||||
|
||||
'@babel/helper-validator-identifier@7.29.7': {}
|
||||
|
||||
'@babel/parser@7.29.7':
|
||||
dependencies:
|
||||
'@babel/types': 7.29.7
|
||||
|
||||
'@babel/types@7.29.7':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.29.7
|
||||
'@babel/helper-validator-identifier': 7.29.7
|
||||
|
||||
'@ctrl/tinycolor@4.2.0': {}
|
||||
|
||||
'@element-plus/icons-vue@2.3.2(vue@3.5.35)':
|
||||
dependencies:
|
||||
vue: 3.5.35
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openharmony-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@floating-ui/core@1.7.5':
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.2.11
|
||||
|
||||
'@floating-ui/dom@1.7.6':
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.7.5
|
||||
'@floating-ui/utils': 0.2.11
|
||||
|
||||
'@floating-ui/utils@0.2.11': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
'@rolldown/pluginutils@1.0.1': {}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-android-arm64@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-openharmony-arm64@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-x64-gnu@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@sxzz/popperjs-es@2.11.8': {}
|
||||
|
||||
'@types/estree@1.0.9': {}
|
||||
|
||||
'@types/lodash-es@4.17.12':
|
||||
dependencies:
|
||||
'@types/lodash': 4.17.24
|
||||
|
||||
'@types/lodash@4.17.24': {}
|
||||
|
||||
'@types/web-bluetooth@0.0.21': {}
|
||||
|
||||
'@vitejs/plugin-vue@6.0.7(vite@7.3.5)(vue@3.5.35)':
|
||||
dependencies:
|
||||
'@rolldown/pluginutils': 1.0.1
|
||||
vite: 7.3.5
|
||||
vue: 3.5.35
|
||||
|
||||
'@vue/compiler-core@3.5.35':
|
||||
dependencies:
|
||||
'@babel/parser': 7.29.7
|
||||
'@vue/shared': 3.5.35
|
||||
entities: 7.0.1
|
||||
estree-walker: 2.0.2
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@vue/compiler-dom@3.5.35':
|
||||
dependencies:
|
||||
'@vue/compiler-core': 3.5.35
|
||||
'@vue/shared': 3.5.35
|
||||
|
||||
'@vue/compiler-sfc@3.5.35':
|
||||
dependencies:
|
||||
'@babel/parser': 7.29.7
|
||||
'@vue/compiler-core': 3.5.35
|
||||
'@vue/compiler-dom': 3.5.35
|
||||
'@vue/compiler-ssr': 3.5.35
|
||||
'@vue/shared': 3.5.35
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.21
|
||||
postcss: 8.5.15
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@vue/compiler-ssr@3.5.35':
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.5.35
|
||||
'@vue/shared': 3.5.35
|
||||
|
||||
'@vue/reactivity@3.5.35':
|
||||
dependencies:
|
||||
'@vue/shared': 3.5.35
|
||||
|
||||
'@vue/runtime-core@3.5.35':
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.5.35
|
||||
'@vue/shared': 3.5.35
|
||||
|
||||
'@vue/runtime-dom@3.5.35':
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.5.35
|
||||
'@vue/runtime-core': 3.5.35
|
||||
'@vue/shared': 3.5.35
|
||||
csstype: 3.2.3
|
||||
|
||||
'@vue/server-renderer@3.5.35(vue@3.5.35)':
|
||||
dependencies:
|
||||
'@vue/compiler-ssr': 3.5.35
|
||||
'@vue/shared': 3.5.35
|
||||
vue: 3.5.35
|
||||
|
||||
'@vue/shared@3.5.35': {}
|
||||
|
||||
'@vueuse/core@14.3.0(vue@3.5.35)':
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.21
|
||||
'@vueuse/metadata': 14.3.0
|
||||
'@vueuse/shared': 14.3.0(vue@3.5.35)
|
||||
vue: 3.5.35
|
||||
|
||||
'@vueuse/metadata@14.3.0': {}
|
||||
|
||||
'@vueuse/shared@14.3.0(vue@3.5.35)':
|
||||
dependencies:
|
||||
vue: 3.5.35
|
||||
|
||||
async-validator@4.2.5: {}
|
||||
|
||||
csstype@3.2.3: {}
|
||||
|
||||
dayjs@1.11.21: {}
|
||||
|
||||
element-plus@2.14.1(vue@3.5.35):
|
||||
dependencies:
|
||||
'@ctrl/tinycolor': 4.2.0
|
||||
'@element-plus/icons-vue': 2.3.2(vue@3.5.35)
|
||||
'@floating-ui/dom': 1.7.6
|
||||
'@popperjs/core': '@sxzz/popperjs-es@2.11.8'
|
||||
'@types/lodash': 4.17.24
|
||||
'@types/lodash-es': 4.17.12
|
||||
'@vueuse/core': 14.3.0(vue@3.5.35)
|
||||
async-validator: 4.2.5
|
||||
dayjs: 1.11.21
|
||||
lodash: 4.18.1
|
||||
lodash-es: 4.18.1
|
||||
lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.18.1)(lodash@4.18.1)
|
||||
memoize-one: 6.0.0
|
||||
normalize-wheel-es: 1.2.0
|
||||
vue: 3.5.35
|
||||
vue-component-type-helpers: 3.3.3
|
||||
|
||||
entities@7.0.1: {}
|
||||
|
||||
esbuild@0.27.7:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.27.7
|
||||
'@esbuild/android-arm': 0.27.7
|
||||
'@esbuild/android-arm64': 0.27.7
|
||||
'@esbuild/android-x64': 0.27.7
|
||||
'@esbuild/darwin-arm64': 0.27.7
|
||||
'@esbuild/darwin-x64': 0.27.7
|
||||
'@esbuild/freebsd-arm64': 0.27.7
|
||||
'@esbuild/freebsd-x64': 0.27.7
|
||||
'@esbuild/linux-arm': 0.27.7
|
||||
'@esbuild/linux-arm64': 0.27.7
|
||||
'@esbuild/linux-ia32': 0.27.7
|
||||
'@esbuild/linux-loong64': 0.27.7
|
||||
'@esbuild/linux-mips64el': 0.27.7
|
||||
'@esbuild/linux-ppc64': 0.27.7
|
||||
'@esbuild/linux-riscv64': 0.27.7
|
||||
'@esbuild/linux-s390x': 0.27.7
|
||||
'@esbuild/linux-x64': 0.27.7
|
||||
'@esbuild/netbsd-arm64': 0.27.7
|
||||
'@esbuild/netbsd-x64': 0.27.7
|
||||
'@esbuild/openbsd-arm64': 0.27.7
|
||||
'@esbuild/openbsd-x64': 0.27.7
|
||||
'@esbuild/openharmony-arm64': 0.27.7
|
||||
'@esbuild/sunos-x64': 0.27.7
|
||||
'@esbuild/win32-arm64': 0.27.7
|
||||
'@esbuild/win32-ia32': 0.27.7
|
||||
'@esbuild/win32-x64': 0.27.7
|
||||
|
||||
estree-walker@2.0.2: {}
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.4):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.4
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
lodash-es@4.18.1: {}
|
||||
|
||||
lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.18.1)(lodash@4.18.1):
|
||||
dependencies:
|
||||
'@types/lodash-es': 4.17.12
|
||||
lodash: 4.18.1
|
||||
lodash-es: 4.18.1
|
||||
|
||||
lodash@4.18.1: {}
|
||||
|
||||
magic-string@0.30.21:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
memoize-one@6.0.0: {}
|
||||
|
||||
nanoid@3.3.12: {}
|
||||
|
||||
normalize-wheel-es@1.2.0: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@4.0.4: {}
|
||||
|
||||
postcss@8.5.15:
|
||||
dependencies:
|
||||
nanoid: 3.3.12
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
rollup@4.61.1:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.9
|
||||
optionalDependencies:
|
||||
'@rollup/rollup-android-arm-eabi': 4.61.1
|
||||
'@rollup/rollup-android-arm64': 4.61.1
|
||||
'@rollup/rollup-darwin-arm64': 4.61.1
|
||||
'@rollup/rollup-darwin-x64': 4.61.1
|
||||
'@rollup/rollup-freebsd-arm64': 4.61.1
|
||||
'@rollup/rollup-freebsd-x64': 4.61.1
|
||||
'@rollup/rollup-linux-arm-gnueabihf': 4.61.1
|
||||
'@rollup/rollup-linux-arm-musleabihf': 4.61.1
|
||||
'@rollup/rollup-linux-arm64-gnu': 4.61.1
|
||||
'@rollup/rollup-linux-arm64-musl': 4.61.1
|
||||
'@rollup/rollup-linux-loong64-gnu': 4.61.1
|
||||
'@rollup/rollup-linux-loong64-musl': 4.61.1
|
||||
'@rollup/rollup-linux-ppc64-gnu': 4.61.1
|
||||
'@rollup/rollup-linux-ppc64-musl': 4.61.1
|
||||
'@rollup/rollup-linux-riscv64-gnu': 4.61.1
|
||||
'@rollup/rollup-linux-riscv64-musl': 4.61.1
|
||||
'@rollup/rollup-linux-s390x-gnu': 4.61.1
|
||||
'@rollup/rollup-linux-x64-gnu': 4.61.1
|
||||
'@rollup/rollup-linux-x64-musl': 4.61.1
|
||||
'@rollup/rollup-openbsd-x64': 4.61.1
|
||||
'@rollup/rollup-openharmony-arm64': 4.61.1
|
||||
'@rollup/rollup-win32-arm64-msvc': 4.61.1
|
||||
'@rollup/rollup-win32-ia32-msvc': 4.61.1
|
||||
'@rollup/rollup-win32-x64-gnu': 4.61.1
|
||||
'@rollup/rollup-win32-x64-msvc': 4.61.1
|
||||
fsevents: 2.3.3
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
tinyglobby@0.2.17:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
|
||||
vite@7.3.5:
|
||||
dependencies:
|
||||
esbuild: 0.27.7
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
postcss: 8.5.15
|
||||
rollup: 4.61.1
|
||||
tinyglobby: 0.2.17
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
vue-component-type-helpers@3.3.3: {}
|
||||
|
||||
vue@3.5.35:
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.5.35
|
||||
'@vue/compiler-sfc': 3.5.35
|
||||
'@vue/runtime-dom': 3.5.35
|
||||
'@vue/server-renderer': 3.5.35(vue@3.5.35)
|
||||
'@vue/shared': 3.5.35
|
||||
|
After Width: | Height: | Size: 589 KiB |
|
After Width: | Height: | Size: 333 KiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 285 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 172 KiB |
|
After Width: | Height: | Size: 199 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 168 KiB |
|
After Width: | Height: | Size: 322 KiB |
|
After Width: | Height: | Size: 222 KiB |
|
After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 231 KiB |
|
After Width: | Height: | Size: 256 KiB |
|
After Width: | Height: | Size: 263 KiB |
|
After Width: | Height: | Size: 297 KiB |
|
After Width: | Height: | Size: 295 KiB |
|
After Width: | Height: | Size: 363 KiB |
|
After Width: | Height: | Size: 378 KiB |
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 165 KiB |
@@ -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)
|
||||
@@ -0,0 +1,7 @@
|
||||
<script setup>
|
||||
import ElementPlusShowcase from './components/ElementPlusShowcase.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElementPlusShowcase />
|
||||
</template>
|
||||
@@ -0,0 +1,114 @@
|
||||
<script setup>
|
||||
import { Download, Refresh } from '@element-plus/icons-vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { navigateTo, useHashRoute } from '../composables/useHashRoute'
|
||||
import { docPages } from '../data/showcaseData'
|
||||
import ButtonShowcase from './showcase/ButtonShowcase.vue'
|
||||
import ComponentCatalogShowcase from './showcase/ComponentCatalogShowcase.vue'
|
||||
import DocumentDrawer from './showcase/DocumentDrawer.vue'
|
||||
import FormShowcase from './showcase/FormShowcase.vue'
|
||||
import FoundationShowcase from './showcase/FoundationShowcase.vue'
|
||||
import MetricsShowcase from './showcase/MetricsShowcase.vue'
|
||||
import NavigationShowcase from './showcase/NavigationShowcase.vue'
|
||||
import StatusShowcase from './showcase/StatusShowcase.vue'
|
||||
import TableShowcase from './showcase/TableShowcase.vue'
|
||||
import TokenRail from './showcase/TokenRail.vue'
|
||||
|
||||
const { route } = useHashRoute()
|
||||
const drawerVisible = ref(false)
|
||||
const showAdvanced = ref(true)
|
||||
const checkValues = ref(['overdue', 'inventory'])
|
||||
const density = ref('standard')
|
||||
const sliderValue = ref(68)
|
||||
const dateRange = ref('')
|
||||
const selectedWarehouse = ref('central')
|
||||
const currentPage = ref(1)
|
||||
const orderStatus = ref('approved')
|
||||
const note = ref('客户要求分批发货,财务需要在出库前完成信用检查。')
|
||||
|
||||
const componentStats = computed(() => [
|
||||
{ label: '组件族', value: '8' },
|
||||
{ label: '基础 Token', value: '34' },
|
||||
{ label: '表格密度', value: '紧凑' },
|
||||
])
|
||||
|
||||
const activeComponentSlug = computed(() => (route.value.segments[0] === 'component' ? route.value.segments[1] : ''))
|
||||
const activePage = computed(() => {
|
||||
if (activeComponentSlug.value) return 'catalog'
|
||||
const pageFromRoute = route.value.segments[0] === 'page' ? route.value.segments[1] : ''
|
||||
return docPages.some((page) => page.id === pageFromRoute) ? pageFromRoute : 'navigation'
|
||||
})
|
||||
const activePageMeta = computed(() => docPages.find((page) => page.id === activePage.value) ?? docPages[0])
|
||||
|
||||
function handleMenuSelect(pageId) {
|
||||
navigateTo(`page/${pageId}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-config-provider>
|
||||
<div class="lab-shell">
|
||||
<aside class="sidebar">
|
||||
<div class="brand">
|
||||
<div class="brand-mark">E</div>
|
||||
<div>
|
||||
<div class="brand-title">Element ERP Lab</div>
|
||||
<div class="brand-subtitle">OFBiz 前端组件定制</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-menu :default-active="activePage" class="lab-menu" @select="handleMenuSelect">
|
||||
<el-menu-item v-for="page in docPages" :key="page.id" :index="page.id">
|
||||
<el-icon><component :is="page.icon" /></el-icon>
|
||||
<span>{{ page.label }}</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<div v-for="stat in componentStats" :key="stat.label" class="sidebar-stat">
|
||||
<span>{{ stat.label }}</span>
|
||||
<strong>{{ stat.value }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="workspace">
|
||||
<header class="topbar">
|
||||
<div>
|
||||
<p class="eyebrow">Design System Draft</p>
|
||||
<h1>{{ activePageMeta.title }}</h1>
|
||||
<p class="page-summary">{{ activePageMeta.description }}</p>
|
||||
</div>
|
||||
<div class="topbar-actions">
|
||||
<el-button :icon="Refresh">同步 Token</el-button>
|
||||
<el-button :icon="Download" type="primary">导出规范</el-button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<FoundationShowcase v-if="activePage === 'foundations'" />
|
||||
<ComponentCatalogShowcase
|
||||
v-else-if="activePage === 'catalog'"
|
||||
:component-slug="activeComponentSlug"
|
||||
/>
|
||||
<MetricsShowcase v-else-if="activePage === 'metrics'" />
|
||||
<ButtonShowcase v-else-if="activePage === 'buttons'" v-model:density="density" />
|
||||
<FormShowcase
|
||||
v-else-if="activePage === 'forms'"
|
||||
v-model:check-values="checkValues"
|
||||
v-model:date-range="dateRange"
|
||||
v-model:note="note"
|
||||
v-model:order-status="orderStatus"
|
||||
v-model:selected-warehouse="selectedWarehouse"
|
||||
v-model:show-advanced="showAdvanced"
|
||||
v-model:slider-value="sliderValue"
|
||||
/>
|
||||
<TableShowcase v-else-if="activePage === 'table'" v-model:current-page="currentPage" />
|
||||
<StatusShowcase v-else-if="activePage === 'status'" />
|
||||
<NavigationShowcase v-else-if="activePage === 'navigation'" @open-drawer="drawerVisible = true" />
|
||||
</main>
|
||||
|
||||
<TokenRail />
|
||||
<DocumentDrawer v-model="drawerVisible" />
|
||||
</div>
|
||||
</el-config-provider>
|
||||
</template>
|
||||
@@ -0,0 +1,46 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps({
|
||||
code: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
eyebrow: {
|
||||
type: String,
|
||||
default: 'Component',
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const initialView = new URLSearchParams(window.location.search).get('view')
|
||||
const activeTab = ref(initialView === 'code' ? 'code' : 'demo')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="demo-block">
|
||||
<div class="section-heading compact">
|
||||
<div>
|
||||
<p class="eyebrow">{{ eyebrow }}</p>
|
||||
<h2>{{ title }}</h2>
|
||||
<p v-if="description" class="demo-description">{{ description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeTab" class="demo-tabs">
|
||||
<el-tab-pane label="展示" name="demo">
|
||||
<slot />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="代码" name="code">
|
||||
<pre class="code-block"><code>{{ code }}</code></pre>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,82 @@
|
||||
<script setup>
|
||||
import {
|
||||
ArrowDown,
|
||||
Check,
|
||||
Close,
|
||||
Download,
|
||||
Filter,
|
||||
MoreFilled,
|
||||
Plus,
|
||||
Printer,
|
||||
Search,
|
||||
Warning,
|
||||
} from '@element-plus/icons-vue'
|
||||
import DemoBlock from '../docs/DemoBlock.vue'
|
||||
|
||||
defineProps({
|
||||
density: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
defineEmits(['update:density'])
|
||||
|
||||
const buttonCode = `<el-button :icon="Search">查询</el-button>
|
||||
<el-button :icon="Filter">高级筛选</el-button>
|
||||
<el-button :icon="Plus" type="primary">新建订单</el-button>
|
||||
<el-button :icon="Check" type="success">审批通过</el-button>
|
||||
<el-button :icon="Warning" type="warning">标记异常</el-button>
|
||||
<el-button :icon="Close" type="danger">作废</el-button>
|
||||
|
||||
<el-button-group>
|
||||
<el-button :icon="Printer">打印</el-button>
|
||||
<el-button :icon="Download">导出</el-button>
|
||||
<el-button :icon="MoreFilled" />
|
||||
</el-button-group>`
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DemoBlock
|
||||
:code="buttonCode"
|
||||
description="按钮层级控制主动作、辅助动作、危险动作和批量动作。"
|
||||
eyebrow="Buttons"
|
||||
title="按钮与动作层级"
|
||||
>
|
||||
<div class="section-heading">
|
||||
<el-segmented
|
||||
:model-value="density"
|
||||
:options="['compact', 'standard', 'spacious']"
|
||||
@update:model-value="$emit('update:density', $event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="component-row">
|
||||
<el-button :icon="Search">查询</el-button>
|
||||
<el-button :icon="Filter">高级筛选</el-button>
|
||||
<el-button :icon="Plus" type="primary">新建订单</el-button>
|
||||
<el-button :icon="Check" type="success">审批通过</el-button>
|
||||
<el-button :icon="Warning" type="warning">标记异常</el-button>
|
||||
<el-button :icon="Close" type="danger">作废</el-button>
|
||||
</div>
|
||||
<div class="component-row">
|
||||
<el-button-group>
|
||||
<el-button :icon="Printer">打印</el-button>
|
||||
<el-button :icon="Download">导出</el-button>
|
||||
<el-button :icon="MoreFilled" />
|
||||
</el-button-group>
|
||||
<el-dropdown>
|
||||
<el-button>
|
||||
更多操作
|
||||
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>复制为新订单</el-dropdown-item>
|
||||
<el-dropdown-item>生成采购需求</el-dropdown-item>
|
||||
<el-dropdown-item divided>查看审计记录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</DemoBlock>
|
||||
</template>
|
||||
@@ -0,0 +1,995 @@
|
||||
<script setup>
|
||||
import { computed, h, ref, watch } from 'vue'
|
||||
import {
|
||||
Files,
|
||||
Goods,
|
||||
Money,
|
||||
Plus,
|
||||
Search,
|
||||
Tickets,
|
||||
} from '@element-plus/icons-vue'
|
||||
import { navigateTo, useHashRoute } from '../../composables/useHashRoute'
|
||||
import {
|
||||
elementPlusDirectiveRecords,
|
||||
elementPlusInventoryMeta,
|
||||
elementPlusPublicExportRecords,
|
||||
} from '../../data/elementPlusInventory'
|
||||
import { erpComponentDocs, erpComponentUsageMap, erpLayoutPlaybook } from '../../data/erpComponentDocs'
|
||||
import { getRiskClass, orderRows } from '../../data/showcaseData'
|
||||
|
||||
const props = defineProps({
|
||||
componentSlug: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
const { route } = useHashRoute()
|
||||
const activeGroup = ref(route.value.params.get('group') || '全部')
|
||||
const searchText = ref('')
|
||||
const cardViews = ref({})
|
||||
const detailView = ref(route.value.params.get('view') || 'demo')
|
||||
const selectedRows = ref([])
|
||||
const sampleDateRange = ref('')
|
||||
const sampleChoice = ref(['overdue'])
|
||||
const sampleRadio = ref('approved')
|
||||
const sampleInput = ref('')
|
||||
const sampleNumber = ref(128)
|
||||
const sampleSelect = ref('east')
|
||||
const sampleTags = ref(['重点客户'])
|
||||
const sampleTime = ref('')
|
||||
const sampleColor = ref('#2764e7')
|
||||
const sampleRate = ref(3)
|
||||
const sampleSlider = ref(68)
|
||||
const sampleSwitch = ref(true)
|
||||
const transferValue = ref([1])
|
||||
const activeTabs = ref('summary')
|
||||
const activeCollapse = ref(['base'])
|
||||
const activeMenu = ref('orders')
|
||||
const activeCheckTag = ref(true)
|
||||
|
||||
const warehouseOptions = [
|
||||
{ label: '华东中心仓', value: 'east' },
|
||||
{ label: '华南备货仓', value: 'south' },
|
||||
{ label: '财务共享中心', value: 'finance' },
|
||||
]
|
||||
|
||||
const treeData = [
|
||||
{
|
||||
label: '集团总部',
|
||||
children: [
|
||||
{ label: '销售中心' },
|
||||
{ label: '采购中心' },
|
||||
{ label: '华东中心仓' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const cascaderOptions = [
|
||||
{
|
||||
value: 'east',
|
||||
label: '华东区域',
|
||||
children: [{ value: 'shanghai', label: '上海仓' }],
|
||||
},
|
||||
]
|
||||
|
||||
const transferData = [
|
||||
{ key: 1, label: '销售订单' },
|
||||
{ key: 2, label: '采购需求' },
|
||||
{ key: 3, label: '库存预警' },
|
||||
{ key: 4, label: '财务应收' },
|
||||
]
|
||||
|
||||
const v2Columns = [
|
||||
{ key: 'orderId', dataKey: 'orderId', title: '单据号', width: 140 },
|
||||
{ key: 'customer', dataKey: 'customer', title: '客户', width: 220 },
|
||||
{ key: 'amount', dataKey: 'amount', title: '金额', width: 140, align: 'right' },
|
||||
]
|
||||
|
||||
const groupedCounts = computed(() => {
|
||||
const groups = ['全部', ...new Set(erpComponentDocs.map((component) => component.group))]
|
||||
return groups.map((group) => ({
|
||||
group,
|
||||
count: group === '全部'
|
||||
? erpComponentDocs.length
|
||||
: erpComponentDocs.filter((component) => component.group === group).length,
|
||||
}))
|
||||
})
|
||||
|
||||
const selectedComponent = computed(() =>
|
||||
erpComponentDocs.find((component) => component.slug === props.componentSlug)
|
||||
)
|
||||
|
||||
const filteredComponents = computed(() => {
|
||||
const scoped = activeGroup.value === '全部'
|
||||
? erpComponentDocs
|
||||
: erpComponentDocs.filter((component) => component.group === activeGroup.value)
|
||||
const keyword = searchText.value.trim().toLowerCase()
|
||||
if (!keyword) return scoped
|
||||
|
||||
return scoped.filter((component) =>
|
||||
[
|
||||
component.name,
|
||||
component.slug,
|
||||
component.tag,
|
||||
component.exportName,
|
||||
component.group,
|
||||
component.wrapperName,
|
||||
component.localClass,
|
||||
].some((value) => value.toLowerCase().includes(keyword))
|
||||
)
|
||||
})
|
||||
|
||||
const catalogSummary = computed(() => [
|
||||
{
|
||||
label: 'ERP 文档层',
|
||||
value: erpComponentDocs.length,
|
||||
note: '每个组件都有本地用法、代码和样式入口',
|
||||
},
|
||||
{
|
||||
label: '高优先级',
|
||||
value: erpComponentDocs.filter((component) => component.priority === '高').length,
|
||||
note: '先沉淀为 OFBiz wrapper 或固定 pattern',
|
||||
},
|
||||
{
|
||||
label: '使用场景',
|
||||
value: erpComponentUsageMap.length,
|
||||
note: '从业务场景反查组件,不从组件名硬猜',
|
||||
},
|
||||
{
|
||||
label: '页面模板',
|
||||
value: erpLayoutPlaybook.length,
|
||||
note: '列表、详情、录入、流程四类骨架',
|
||||
},
|
||||
])
|
||||
|
||||
const groupedFilteredComponents = computed(() => {
|
||||
const groupOrder = [...new Set(erpComponentDocs.map((component) => component.group))]
|
||||
return groupOrder
|
||||
.map((group) => ({
|
||||
group,
|
||||
components: filteredComponents.value.filter((component) => component.group === group),
|
||||
}))
|
||||
.filter((section) => section.components.length > 0)
|
||||
})
|
||||
|
||||
const relatedComponents = computed(() => {
|
||||
if (!selectedComponent.value) return []
|
||||
return erpComponentDocs
|
||||
.filter((component) => component.group === selectedComponent.value.group && component.slug !== selectedComponent.value.slug)
|
||||
.slice(0, 10)
|
||||
})
|
||||
|
||||
const priorityComponents = computed(() =>
|
||||
erpComponentDocs.filter((component) => component.priority === '高').slice(0, 18)
|
||||
)
|
||||
|
||||
const layoutPrinciples = [
|
||||
{
|
||||
title: '先结构后控件',
|
||||
body: '页面先确定导航、标题、筛选、内容、分页和反馈位置,再选择具体组件。',
|
||||
},
|
||||
{
|
||||
title: '密度按任务分层',
|
||||
body: '查询和表格保持紧凑,详情和确认弹层保留阅读间距,指标只用大/小/最小三档。',
|
||||
},
|
||||
{
|
||||
title: '主动作只留一个',
|
||||
body: '同一工作区只保留一个 primary,次要动作降级为普通按钮、link 或 dropdown。',
|
||||
},
|
||||
{
|
||||
title: '状态必须可读',
|
||||
body: '颜色只做辅助,Tag、Alert、Timeline、Progress 都要有明确文字说明。',
|
||||
},
|
||||
{
|
||||
title: '代码随组件走',
|
||||
body: '每个组件页保留原始代码、ERP 代码和本地样式,复用 CSS 不散落到业务页。',
|
||||
},
|
||||
{
|
||||
title: '页面模板先行',
|
||||
body: '改 OFBiz 页面时先选列表、详情、录入或流程模板,再补具体控件。',
|
||||
},
|
||||
]
|
||||
|
||||
watch(
|
||||
() => route.value.params.get('group'),
|
||||
(group) => {
|
||||
activeGroup.value = group || '全部'
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => route.value.params.get('view'),
|
||||
(view) => {
|
||||
detailView.value = view || 'demo'
|
||||
}
|
||||
)
|
||||
|
||||
function viewFor(slug) {
|
||||
return cardViews.value[slug] ?? 'demo'
|
||||
}
|
||||
|
||||
function setCardView(slug, view) {
|
||||
cardViews.value = {
|
||||
...cardViews.value,
|
||||
[slug]: view,
|
||||
}
|
||||
}
|
||||
|
||||
function selectGroup(group) {
|
||||
activeGroup.value = group
|
||||
navigateTo('page/catalog', group === '全部' ? {} : { group })
|
||||
}
|
||||
|
||||
function openComponent(slug, view = 'demo') {
|
||||
navigateTo(`component/${slug}`, view === 'demo' ? {} : { view })
|
||||
}
|
||||
|
||||
function updateDetailView(view) {
|
||||
detailView.value = view
|
||||
openComponent(selectedComponent.value.slug, view)
|
||||
}
|
||||
|
||||
function renderMiniPreview(component) {
|
||||
const kind = component.previewKind
|
||||
|
||||
if (kind === 'table' || kind === 'table-v2') {
|
||||
return h('div', { class: 'mini-table-preview' }, [
|
||||
h('span', 'SO-10642'),
|
||||
h('span', '已审批'),
|
||||
h('strong', '¥128k'),
|
||||
])
|
||||
}
|
||||
|
||||
if (kind === 'menu') {
|
||||
return h('div', { class: 'mini-menu-preview' }, [
|
||||
h('span', '销售管理'),
|
||||
h('span', '销售订单'),
|
||||
])
|
||||
}
|
||||
|
||||
if (kind === 'form' || kind === 'field' || kind === 'select' || kind === 'date' || kind === 'lookup') {
|
||||
return h('div', { class: 'mini-field-preview' }, [
|
||||
h('span', component.name),
|
||||
h('small', component.localClass),
|
||||
])
|
||||
}
|
||||
|
||||
if (kind === 'button' || kind === 'dropdown') {
|
||||
return h('div', { class: 'mini-action-preview' }, [
|
||||
h('span', '查询'),
|
||||
h('strong', '新建'),
|
||||
])
|
||||
}
|
||||
|
||||
if (kind === 'breadcrumb') {
|
||||
return h('div', { class: 'mini-breadcrumb-preview' }, ['销售 / 订单 / 详情'])
|
||||
}
|
||||
|
||||
if (kind === 'status' || kind === 'feedback' || kind === 'service' || kind === 'overlay') {
|
||||
return h('div', { class: 'mini-status-preview' }, [
|
||||
h('span', component.name),
|
||||
h('small', 'warning'),
|
||||
])
|
||||
}
|
||||
|
||||
if (['empty', 'progress', 'skeleton', 'calendar', 'timeline', 'steps'].includes(kind)) {
|
||||
return h('div', { class: 'mini-status-preview' }, [
|
||||
h('span', component.name),
|
||||
h('small', 'state'),
|
||||
])
|
||||
}
|
||||
|
||||
return h('div', { class: 'mini-generic-preview' }, [
|
||||
h('span', component.name),
|
||||
h('small', component.family),
|
||||
])
|
||||
}
|
||||
|
||||
const MiniPreview = {
|
||||
props: {
|
||||
component: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(miniProps) {
|
||||
return () => renderMiniPreview(miniProps.component)
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="selectedComponent" class="component-detail-page">
|
||||
<section class="demo-block component-detail-hero">
|
||||
<div class="component-detail-head">
|
||||
<div>
|
||||
<el-button link type="primary" @click="navigateTo('page/catalog')">
|
||||
返回全组件目录
|
||||
</el-button>
|
||||
<p class="eyebrow">{{ selectedComponent.group }} / {{ selectedComponent.family }}</p>
|
||||
<h2>{{ selectedComponent.name }}</h2>
|
||||
<p class="demo-description">
|
||||
{{ selectedComponent.tag }} / {{ selectedComponent.exportName }} / wrapper: {{ selectedComponent.wrapperName }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="component-doc-card__tags">
|
||||
<el-tag effect="plain">{{ selectedComponent.group }}</el-tag>
|
||||
<el-tag :type="selectedComponent.priority === '高' ? 'primary' : 'info'" effect="light">
|
||||
{{ selectedComponent.priority }}
|
||||
</el-tag>
|
||||
<el-tag v-if="selectedComponent.parentSlug" type="warning" effect="plain">
|
||||
跟随 {{ selectedComponent.parentSlug }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="demo-block">
|
||||
<el-tabs :model-value="detailView" class="component-detail-tabs" @update:model-value="updateDetailView">
|
||||
<el-tab-pane label="展示" name="demo">
|
||||
<div class="component-demo-layout">
|
||||
<div class="component-live-preview">
|
||||
<template v-if="selectedComponent.previewKind === 'table'">
|
||||
<el-table :data="orderRows" class="erp-table" stripe>
|
||||
<el-table-column fixed prop="orderId" label="单据号" width="126" />
|
||||
<el-table-column prop="customer" label="客户 / 对象" min-width="180" />
|
||||
<el-table-column prop="status" label="状态" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.statusType" effect="light">{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="amount" label="金额" width="140" align="right" />
|
||||
<el-table-column prop="risk" label="风险" width="90">
|
||||
<template #default="{ row }">
|
||||
<span :class="['risk-dot', getRiskClass(row.risk)]">{{ row.risk }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'table-v2'">
|
||||
<el-table-v2
|
||||
class="erp-virtual-table"
|
||||
:columns="v2Columns"
|
||||
:data="orderRows"
|
||||
:height="260"
|
||||
:width="760"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'pagination'">
|
||||
<el-pagination
|
||||
class="erp-dense-pagination"
|
||||
layout="prev, pager, next, sizes"
|
||||
:page-size="20"
|
||||
:page-sizes="[20, 50, 100]"
|
||||
:total="128"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'menu'">
|
||||
<div class="detail-menu-preview">
|
||||
<el-menu class="erp-side-menu" :default-active="activeMenu" @select="activeMenu = $event">
|
||||
<el-sub-menu index="sales">
|
||||
<template #title>
|
||||
<el-icon><Tickets /></el-icon>
|
||||
<span>销售管理</span>
|
||||
</template>
|
||||
<el-menu-item index="orders">销售订单</el-menu-item>
|
||||
<el-menu-item index="returns">退货处理</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-menu-item index="inventory">
|
||||
<el-icon><Goods /></el-icon>
|
||||
<span>库存管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="finance">
|
||||
<el-icon><Money /></el-icon>
|
||||
<span>财务应收</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'breadcrumb'">
|
||||
<div class="breadcrumb-demo">
|
||||
<el-breadcrumb class="erp-breadcrumb" separator="/">
|
||||
<el-breadcrumb-item>销售管理</el-breadcrumb-item>
|
||||
<el-breadcrumb-item>销售订单</el-breadcrumb-item>
|
||||
<el-breadcrumb-item>SO-10642</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
<el-page-header class="erp-page-header" title="返回订单列表" content="SO-10642 销售订单" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'form'">
|
||||
<el-form class="erp-form" label-position="top">
|
||||
<div class="form-grid">
|
||||
<el-form-item label="客户 / 供应商">
|
||||
<el-input v-model="sampleInput" placeholder="输入名称、编码或联系人" :prefix-icon="Search" />
|
||||
</el-form-item>
|
||||
<el-form-item label="仓库">
|
||||
<el-select v-model="sampleSelect">
|
||||
<el-option label="华东中心仓" value="east" />
|
||||
<el-option label="华南备货仓" value="south" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="业务日期">
|
||||
<el-date-picker v-model="sampleDateRange" type="daterange" start-placeholder="开始" end-placeholder="结束" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'field'">
|
||||
<div class="detail-field-grid">
|
||||
<el-input v-model="sampleInput" placeholder="搜索订单、客户、负责人" :prefix-icon="Search" />
|
||||
<el-input-number v-model="sampleNumber" :min="0" />
|
||||
<el-input-tag v-model="sampleTags" placeholder="输入标签" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'select'">
|
||||
<div class="detail-field-grid">
|
||||
<el-select v-model="sampleSelect" placeholder="选择仓库">
|
||||
<el-option label="华东中心仓" value="east" />
|
||||
<el-option label="华南备货仓" value="south" />
|
||||
</el-select>
|
||||
<el-select-v2 v-model="sampleSelect" :options="warehouseOptions" placeholder="虚拟选择器" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'date'">
|
||||
<div class="detail-field-grid">
|
||||
<el-date-picker v-model="sampleDateRange" type="daterange" start-placeholder="开始日期" end-placeholder="结束日期" />
|
||||
<el-time-picker v-model="sampleTime" placeholder="选择时间" />
|
||||
<el-time-select v-model="sampleTime" start="08:30" step="00:30" end="18:30" placeholder="选择时段" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'lookup'">
|
||||
<div class="detail-field-grid">
|
||||
<el-autocomplete v-model="sampleInput" placeholder="搜索客户编码、名称或联系人" />
|
||||
<el-cascader v-model="selectedRows" :options="cascaderOptions" placeholder="选择组织 / 仓库" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'choice'">
|
||||
<div class="detail-choice-stack">
|
||||
<el-checkbox-group v-model="sampleChoice">
|
||||
<el-checkbox-button label="overdue">逾期</el-checkbox-button>
|
||||
<el-checkbox-button label="inventory">库存不足</el-checkbox-button>
|
||||
<el-checkbox-button label="credit">信用风险</el-checkbox-button>
|
||||
</el-checkbox-group>
|
||||
<el-radio-group v-model="sampleRadio">
|
||||
<el-radio-button label="draft">草稿</el-radio-button>
|
||||
<el-radio-button label="approved">已审批</el-radio-button>
|
||||
<el-radio-button label="shipping">待发货</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'button'">
|
||||
<div class="component-row">
|
||||
<el-button :icon="Search">查询</el-button>
|
||||
<el-button :icon="Plus" type="primary">新建订单</el-button>
|
||||
<el-button type="warning">标记异常</el-button>
|
||||
<el-button type="danger">作废</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'dropdown'">
|
||||
<el-dropdown class="erp-action-dropdown">
|
||||
<el-button>更多操作</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>复制为新订单</el-dropdown-item>
|
||||
<el-dropdown-item>生成采购需求</el-dropdown-item>
|
||||
<el-dropdown-item divided>查看审计记录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'status'">
|
||||
<div class="tag-grid">
|
||||
<el-tag type="success" effect="light">已审批</el-tag>
|
||||
<el-tag type="warning" effect="light">信用检查</el-tag>
|
||||
<el-tag type="danger" effect="light">逾期</el-tag>
|
||||
<el-check-tag v-model:checked="activeCheckTag">仅看异常</el-check-tag>
|
||||
<el-badge :value="8"><el-button>待办</el-button></el-badge>
|
||||
</div>
|
||||
<el-alert title="信用检查未完成" type="warning" show-icon :closable="false" />
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'descriptions'">
|
||||
<el-descriptions class="erp-detail-descriptions" :column="3" border>
|
||||
<el-descriptions-item label="客户">上海辰光零售</el-descriptions-item>
|
||||
<el-descriptions-item label="账期">Net 30</el-descriptions-item>
|
||||
<el-descriptions-item label="币种">CNY</el-descriptions-item>
|
||||
<el-descriptions-item label="仓库">华东中心仓</el-descriptions-item>
|
||||
<el-descriptions-item label="审批状态">已审批</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'overlay'">
|
||||
<div class="overlay-static-preview">
|
||||
<header>{{ selectedComponent.name }}</header>
|
||||
<p>确认通过该订单?系统会写入审批审计记录。</p>
|
||||
<footer>
|
||||
<el-button>取消</el-button>
|
||||
<el-button type="primary">确认</el-button>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'service'">
|
||||
<div class="service-preview">
|
||||
<el-alert title="服务类组件通过函数触发,不直接在模板中常驻" type="info" show-icon :closable="false" />
|
||||
<el-button type="primary">触发 {{ selectedComponent.name }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'tabs'">
|
||||
<el-tabs v-model="activeTabs" class="erp-tabs">
|
||||
<el-tab-pane label="摘要" name="summary">订单摘要、金额和履约状态。</el-tab-pane>
|
||||
<el-tab-pane label="明细" name="lines">商品行、税额、折扣和发运计划。</el-tab-pane>
|
||||
<el-tab-pane label="审计" name="audit">审批、修改、通知记录。</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'tree'">
|
||||
<div class="detail-field-grid">
|
||||
<el-tree class="erp-tree" :data="treeData" default-expand-all />
|
||||
<el-tree-select v-model="sampleSelect" class="erp-tree-select" :data="treeData" placeholder="选择组织" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'card'">
|
||||
<section class="erp-entity-card">
|
||||
<header>订单摘要</header>
|
||||
<strong>SO-10642</strong>
|
||||
<p>上海辰光零售 / ¥128,420.00 / 已审批</p>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'layout'">
|
||||
<div class="layout-static-preview">
|
||||
<aside>模块导航</aside>
|
||||
<main>业务内容区</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'data'">
|
||||
<div class="data-static-preview">
|
||||
<el-statistic title="待处理订单" :value="128" />
|
||||
<el-progress :percentage="72" />
|
||||
<el-empty description="暂无异常数据" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'empty'">
|
||||
<el-empty class="erp-empty-state" description="暂无符合条件的单据">
|
||||
<el-button type="primary">新建订单</el-button>
|
||||
</el-empty>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'progress'">
|
||||
<div class="detail-status-stack">
|
||||
<el-progress class="erp-progress" :percentage="72" />
|
||||
<el-progress class="erp-progress" :percentage="36" status="warning" />
|
||||
<el-progress class="erp-progress" :percentage="88" status="success" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'result'">
|
||||
<el-result class="erp-result-state" icon="success" title="保存成功" sub-title="销售订单 SO-10642 已写入审批记录">
|
||||
<template #extra>
|
||||
<el-button type="primary">查看订单</el-button>
|
||||
<el-button>返回列表</el-button>
|
||||
</template>
|
||||
</el-result>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'skeleton'">
|
||||
<div class="erp-skeleton">
|
||||
<el-skeleton :rows="4" animated />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'calendar'">
|
||||
<div class="calendar-preview">
|
||||
<el-calendar class="erp-calendar" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'timeline'">
|
||||
<el-timeline class="erp-timeline">
|
||||
<el-timeline-item timestamp="09:20" type="success">销售订单完成审批</el-timeline-item>
|
||||
<el-timeline-item timestamp="10:05" type="primary">库存锁定 18 件</el-timeline-item>
|
||||
<el-timeline-item timestamp="10:44" type="warning">触发信用额度复核</el-timeline-item>
|
||||
</el-timeline>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'steps'">
|
||||
<el-steps class="erp-steps" :active="2" finish-status="success" align-center>
|
||||
<el-step title="创建订单" />
|
||||
<el-step title="信用检查" />
|
||||
<el-step title="库存锁定" />
|
||||
<el-step title="发运" />
|
||||
</el-steps>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'collapse'">
|
||||
<el-collapse v-model="activeCollapse" class="erp-collapse">
|
||||
<el-collapse-item title="基础信息" name="base">客户、币种、账期、业务员。</el-collapse-item>
|
||||
<el-collapse-item title="履约信息" name="fulfillment">仓库、计划日期、发运批次。</el-collapse-item>
|
||||
</el-collapse>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'media'">
|
||||
<div class="media-preview">
|
||||
<el-image
|
||||
class="erp-media-preview"
|
||||
fit="cover"
|
||||
src="https://images.unsplash.com/photo-1586528116311-ad8dd3c8310d?auto=format&fit=crop&w=360&q=80"
|
||||
/>
|
||||
<span>附件、商品图或仓库现场图需要固定比例展示。</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'carousel'">
|
||||
<el-carousel class="erp-carousel" height="180px">
|
||||
<el-carousel-item>
|
||||
<div class="carousel-panel">待处理订单 128</div>
|
||||
</el-carousel-item>
|
||||
<el-carousel-item>
|
||||
<div class="carousel-panel">库存预警 36</div>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'typography'">
|
||||
<div class="typography-preview">
|
||||
<el-text class="erp-inline-text" type="primary">业务提示:客户信用额度接近上限。</el-text>
|
||||
<el-link class="erp-inline-link" type="primary">查看信用明细</el-link>
|
||||
<el-divider>审计记录</el-divider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'space'">
|
||||
<el-space class="erp-space" wrap>
|
||||
<el-button>查询</el-button>
|
||||
<el-button type="primary">新建</el-button>
|
||||
<el-tag type="warning" effect="light">信用检查</el-tag>
|
||||
</el-space>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'statistic'">
|
||||
<div class="statistic-preview">
|
||||
<el-statistic class="erp-statistic" title="待处理订单" :value="128" />
|
||||
<el-statistic class="erp-statistic" title="今日发运" :value="214" />
|
||||
<el-statistic class="erp-statistic" title="库存预警" :value="36" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'switch'">
|
||||
<el-switch v-model="sampleSwitch" class="erp-switch" active-text="启用自动锁库" inactive-text="关闭" />
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'slider'">
|
||||
<div class="detail-status-stack">
|
||||
<span>优先级阈值 {{ sampleSlider }}</span>
|
||||
<el-slider v-model="sampleSlider" class="erp-slider" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'rate'">
|
||||
<div class="detail-status-stack">
|
||||
<span>供应商评分</span>
|
||||
<el-rate v-model="sampleRate" class="erp-rate" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'color'">
|
||||
<div class="detail-status-stack">
|
||||
<span>状态色映射</span>
|
||||
<el-color-picker v-model="sampleColor" class="erp-color-picker" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'transfer'">
|
||||
<el-transfer
|
||||
v-model="transferValue"
|
||||
class="erp-transfer"
|
||||
:data="transferData"
|
||||
:titles="['可选模块', '已授权']"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="selectedComponent.previewKind === 'upload'">
|
||||
<el-upload class="erp-upload" action="#" drag>
|
||||
<el-icon><Files /></el-icon>
|
||||
<div>拖拽附件到此处,或点击上传</div>
|
||||
</el-upload>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div class="generic-component-preview">
|
||||
<MiniPreview :component="selectedComponent" />
|
||||
<p>{{ selectedComponent.usage }}</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<aside class="component-usage-panel">
|
||||
<h3>本地化判断</h3>
|
||||
<p>{{ selectedComponent.usage }}</p>
|
||||
<div class="usage-rule-block">
|
||||
<strong>什么时候用</strong>
|
||||
<ul>
|
||||
<li v-for="item in selectedComponent.whenToUse" :key="item">{{ item }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="usage-rule-block">
|
||||
<strong>什么时候不用</strong>
|
||||
<ul>
|
||||
<li v-for="item in selectedComponent.avoidWhen" :key="item">{{ item }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="usage-rule-block">
|
||||
<strong>排版规则</strong>
|
||||
<ul>
|
||||
<li v-for="item in selectedComponent.layoutRules" :key="item">{{ item }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>本地 class</dt>
|
||||
<dd>{{ selectedComponent.localClass }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>样式重点</dt>
|
||||
<dd>{{ selectedComponent.styleFocus }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>落地文件</dt>
|
||||
<dd>{{ selectedComponent.cssFiles.join(' / ') }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</aside>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="原始代码" name="raw">
|
||||
<pre class="code-block"><code>{{ selectedComponent.originalCode }}</code></pre>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="ERP代码" name="code">
|
||||
<pre class="code-block"><code>{{ selectedComponent.erpCode }}</code></pre>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="本地样式" name="style">
|
||||
<pre class="code-block"><code>{{ selectedComponent.styleCode }}</code></pre>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</section>
|
||||
|
||||
<section class="demo-block">
|
||||
<div class="section-heading compact">
|
||||
<div>
|
||||
<p class="eyebrow">Related</p>
|
||||
<h2>同组组件</h2>
|
||||
<p class="demo-description">这些会复用同一组 ERP token、密度和状态规则。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="related-components">
|
||||
<el-button
|
||||
v-for="component in relatedComponents"
|
||||
:key="component.slug"
|
||||
@click="openComponent(component.slug)"
|
||||
>
|
||||
{{ component.name }}
|
||||
</el-button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div v-else class="catalog-page">
|
||||
<section class="demo-block">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<p class="eyebrow">Full Element Plus</p>
|
||||
<h2>本地 Element Plus 全量组件</h2>
|
||||
<p class="demo-description">
|
||||
从本地 node_modules 生成完整清单,再叠加 ERP 本地化文档层。每张卡片可独立切展示/代码,每个组件可进入独立页面。
|
||||
</p>
|
||||
</div>
|
||||
<div class="catalog-stats">
|
||||
<el-tag effect="plain">版本:{{ elementPlusInventoryMeta.packageVersion }}</el-tag>
|
||||
<el-tag effect="plain">组件目录:{{ elementPlusInventoryMeta.componentDirectories }}</el-tag>
|
||||
<el-tag effect="plain">El* 导出:{{ elementPlusInventoryMeta.publicElExports }}</el-tag>
|
||||
<el-tag effect="plain">指令目录:{{ elementPlusInventoryMeta.directiveDirectories }}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="catalog-summary-grid">
|
||||
<article v-for="item in catalogSummary" :key="item.label">
|
||||
<span>{{ item.label }}</span>
|
||||
<strong>{{ item.value }}</strong>
|
||||
<p>{{ item.note }}</p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="catalog-filter">
|
||||
<el-segmented
|
||||
:model-value="activeGroup"
|
||||
:options="groupedCounts.map((item) => item.group)"
|
||||
@update:model-value="selectGroup"
|
||||
/>
|
||||
<el-input v-model="searchText" class="catalog-search" clearable placeholder="搜索 Table / Menu / Breadcrumb" />
|
||||
<div class="catalog-counts">
|
||||
<span v-for="item in groupedCounts" :key="item.group">{{ item.group }} {{ item.count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="demo-block layout-playbook">
|
||||
<div class="section-heading compact">
|
||||
<div>
|
||||
<p class="eyebrow">Page Playbook</p>
|
||||
<h2>OFBiz 页面组合模板</h2>
|
||||
<p class="demo-description">先选页面骨架,再选组件。这样重构一个页面时,结构、密度、动作位置会先被约束住。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="playbook-grid">
|
||||
<article v-for="play in erpLayoutPlaybook" :key="play.title" class="playbook-card">
|
||||
<header>
|
||||
<div>
|
||||
<h3>{{ play.title }}</h3>
|
||||
<p>{{ play.description }}</p>
|
||||
</div>
|
||||
<el-tag effect="plain">{{ play.density }}</el-tag>
|
||||
</header>
|
||||
<ol>
|
||||
<li v-for="rule in play.rules" :key="rule">{{ rule }}</li>
|
||||
</ol>
|
||||
<div class="usage-map-links">
|
||||
<button
|
||||
v-for="component in play.components"
|
||||
:key="component.slug"
|
||||
type="button"
|
||||
@click="openComponent(component.slug)"
|
||||
>
|
||||
{{ component.name }}
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="usage-map-grid">
|
||||
<article v-for="section in erpComponentUsageMap" :key="section.title" class="usage-map-card">
|
||||
<div>
|
||||
<p class="eyebrow">Usage Map</p>
|
||||
<h3>{{ section.title }}</h3>
|
||||
<p>{{ section.description }}</p>
|
||||
</div>
|
||||
<div class="usage-map-links">
|
||||
<button
|
||||
v-for="component in section.components"
|
||||
:key="component.slug"
|
||||
type="button"
|
||||
@click="openComponent(component.slug)"
|
||||
>
|
||||
{{ component.name }}
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="demo-block layout-principles">
|
||||
<div class="section-heading compact">
|
||||
<div>
|
||||
<p class="eyebrow">Layout Rules</p>
|
||||
<h2>ERP 页面排版规则</h2>
|
||||
<p class="demo-description">先用这些规则约束页面,再挑组件。这样每个 OFBiz 页面会长得像同一个系统。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="principle-grid">
|
||||
<article v-for="principle in layoutPrinciples" :key="principle.title">
|
||||
<strong>{{ principle.title }}</strong>
|
||||
<p>{{ principle.body }}</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="demo-block priority-map">
|
||||
<div class="section-heading compact">
|
||||
<div>
|
||||
<p class="eyebrow">ERP Priority</p>
|
||||
<h2>优先本地化组件</h2>
|
||||
<p class="demo-description">先做 OFBiz 最高频控件:表格、菜单、面包屑、表单、按钮、弹层和状态反馈。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="priority-link-grid">
|
||||
<button
|
||||
v-for="component in priorityComponents"
|
||||
:key="component.slug"
|
||||
type="button"
|
||||
@click="openComponent(component.slug)"
|
||||
>
|
||||
<span>{{ component.name }}</span>
|
||||
<small>{{ component.localClass }}</small>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="component-library-sections">
|
||||
<section v-for="section in groupedFilteredComponents" :key="section.group" class="component-group-section">
|
||||
<header class="component-group-heading">
|
||||
<div>
|
||||
<p class="eyebrow">Component Group</p>
|
||||
<h2>{{ section.group }}</h2>
|
||||
</div>
|
||||
<span>{{ section.components.length }} 个组件</span>
|
||||
</header>
|
||||
|
||||
<div class="full-component-grid">
|
||||
<article v-for="component in section.components" :key="component.slug" class="component-doc-card">
|
||||
<div class="component-doc-card__head">
|
||||
<div>
|
||||
<button class="component-title-button" type="button" @click="openComponent(component.slug)">
|
||||
{{ component.name }}
|
||||
</button>
|
||||
<p>{{ component.tag }} / {{ component.exportName }}</p>
|
||||
</div>
|
||||
<div class="component-doc-card__tags">
|
||||
<el-tag size="small" effect="plain">{{ component.group }}</el-tag>
|
||||
<el-tag size="small" :type="component.priority === '高' ? 'primary' : 'info'" effect="light">
|
||||
{{ component.priority }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="component-doc-card__rule">{{ component.whenToUse[0] }}</p>
|
||||
|
||||
<el-tabs :model-value="viewFor(component.slug)" class="component-doc-tabs" @update:model-value="setCardView(component.slug, $event)">
|
||||
<el-tab-pane label="展示" name="demo">
|
||||
<div class="component-preview">
|
||||
<MiniPreview :component="component" />
|
||||
<el-button link type="primary" @click="openComponent(component.slug)">详情</el-button>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="代码" name="code">
|
||||
<pre class="component-code"><code>{{ component.erpCode }}</code></pre>
|
||||
<el-button link type="primary" @click="openComponent(component.slug, 'code')">打开单组件代码页</el-button>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="demo-block">
|
||||
<div class="section-heading compact">
|
||||
<div>
|
||||
<p class="eyebrow">Exports & Directives</p>
|
||||
<h2>公开导出与指令</h2>
|
||||
<p class="demo-description">服务类导出、Popper 子件、指令也保留在清单里,避免遗漏行为层能力。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="component-chip-list">
|
||||
<span v-for="record in elementPlusPublicExportRecords" :key="record.name" class="component-chip">
|
||||
{{ record.name }}
|
||||
<small>{{ record.kind === 'component' ? '组件' : '服务' }}</small>
|
||||
</span>
|
||||
</div>
|
||||
<div class="directive-list">
|
||||
<el-tag v-for="directive in elementPlusDirectiveRecords" :key="directive.slug" effect="plain">
|
||||
{{ directive.code }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,27 @@
|
||||
<script setup>
|
||||
import { Calendar, CreditCard } from '@element-plus/icons-vue'
|
||||
|
||||
defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
defineEmits(['update:modelValue'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-drawer :model-value="modelValue" title="SO-10642 单据预览" size="460px" @update:model-value="$emit('update:modelValue', $event)">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="客户">上海辰光零售</el-descriptions-item>
|
||||
<el-descriptions-item label="金额">¥128,420.00</el-descriptions-item>
|
||||
<el-descriptions-item label="计划日期">2026-06-08</el-descriptions-item>
|
||||
<el-descriptions-item label="负责人">Lina Chen</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div class="drawer-actions">
|
||||
<el-button :icon="Calendar">排期</el-button>
|
||||
<el-button :icon="CreditCard" type="primary">发起收款</el-button>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
@@ -0,0 +1,129 @@
|
||||
<script setup>
|
||||
import { User } from '@element-plus/icons-vue'
|
||||
import DemoBlock from '../docs/DemoBlock.vue'
|
||||
|
||||
defineProps({
|
||||
checkValues: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
dateRange: {
|
||||
type: [String, Array, Date],
|
||||
default: '',
|
||||
},
|
||||
note: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
orderStatus: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
selectedWarehouse: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
showAdvanced: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
sliderValue: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
defineEmits([
|
||||
'update:checkValues',
|
||||
'update:dateRange',
|
||||
'update:note',
|
||||
'update:orderStatus',
|
||||
'update:selectedWarehouse',
|
||||
'update:showAdvanced',
|
||||
'update:sliderValue',
|
||||
])
|
||||
|
||||
const formCode = `<el-form class="erp-form" label-position="top">
|
||||
<div class="form-grid">
|
||||
<el-form-item label="客户 / 供应商">
|
||||
<el-input placeholder="输入名称、编码或联系人" :prefix-icon="User" />
|
||||
</el-form-item>
|
||||
<el-form-item label="仓库">
|
||||
<el-select v-model="selectedWarehouse">
|
||||
<el-option label="华东中心仓" value="central" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="业务日期">
|
||||
<el-date-picker v-model="dateRange" type="daterange" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>`
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DemoBlock
|
||||
:code="formCode"
|
||||
description="查询表单默认紧凑、分组清晰,复杂条件放到第二行或高级区。"
|
||||
eyebrow="Forms"
|
||||
title="表单控件与查询条件"
|
||||
>
|
||||
<div class="section-heading">
|
||||
<el-switch
|
||||
:model-value="showAdvanced"
|
||||
active-text="显示高级项"
|
||||
@update:model-value="$emit('update:showAdvanced', $event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-form class="erp-form" label-position="top">
|
||||
<div class="form-grid">
|
||||
<el-form-item label="客户 / 供应商">
|
||||
<el-input placeholder="输入名称、编码或联系人" :prefix-icon="User" />
|
||||
</el-form-item>
|
||||
<el-form-item label="仓库">
|
||||
<el-select
|
||||
:model-value="selectedWarehouse"
|
||||
@update:model-value="$emit('update:selectedWarehouse', $event)"
|
||||
>
|
||||
<el-option label="华东中心仓" value="central" />
|
||||
<el-option label="华南备货仓" value="south" />
|
||||
<el-option label="财务共享中心" value="finance" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="业务日期">
|
||||
<el-date-picker
|
||||
:model-value="dateRange"
|
||||
end-placeholder="结束日期"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
type="daterange"
|
||||
@update:model-value="$emit('update:dateRange', $event)"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="订单状态">
|
||||
<el-select :model-value="orderStatus" @update:model-value="$emit('update:orderStatus', $event)">
|
||||
<el-option label="草稿" value="draft" />
|
||||
<el-option label="已审批" value="approved" />
|
||||
<el-option label="待发货" value="shipping" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<div class="form-grid secondary">
|
||||
<el-form-item label="关注事项">
|
||||
<el-checkbox-group :model-value="checkValues" @update:model-value="$emit('update:checkValues', $event)">
|
||||
<el-checkbox-button label="overdue">逾期</el-checkbox-button>
|
||||
<el-checkbox-button label="inventory">库存不足</el-checkbox-button>
|
||||
<el-checkbox-button label="credit">信用风险</el-checkbox-button>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="优先级阈值">
|
||||
<el-slider :model-value="sliderValue" @update:model-value="$emit('update:sliderValue', $event)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="处理备注">
|
||||
<el-input :model-value="note" type="textarea" @update:model-value="$emit('update:note', $event)" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
</DemoBlock>
|
||||
</template>
|
||||
@@ -0,0 +1,53 @@
|
||||
<script setup>
|
||||
import DemoBlock from '../docs/DemoBlock.vue'
|
||||
|
||||
const tokenCode = `import './styles/erp-ui.css'
|
||||
|
||||
:root {
|
||||
--erp-color-primary: #2764e7;
|
||||
--erp-color-success: #16845b;
|
||||
--erp-radius-sm: 6px;
|
||||
--erp-font-size-sm: 13px;
|
||||
}`
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DemoBlock
|
||||
:code="tokenCode"
|
||||
description="这套基线刻意避开营销感和装饰感,优先保证高密度扫描、稳定状态表达、清晰动作层级和长时间操作的视觉耐受度。"
|
||||
eyebrow="Foundations"
|
||||
title="基础 Token"
|
||||
>
|
||||
<div class="hero-panel">
|
||||
<div class="panel-copy">
|
||||
<h2>先锁定产品气质,再逐个组件收敛</h2>
|
||||
</div>
|
||||
<div class="token-grid">
|
||||
<div class="token-card">
|
||||
<span>Primary</span>
|
||||
<strong>#2764e7</strong>
|
||||
</div>
|
||||
<div class="token-card muted">
|
||||
<span>Surface</span>
|
||||
<strong>#f7f8fb</strong>
|
||||
</div>
|
||||
<div class="token-card success">
|
||||
<span>Success</span>
|
||||
<strong>#16845b</strong>
|
||||
</div>
|
||||
<div class="token-card warning">
|
||||
<span>Warning</span>
|
||||
<strong>#b86b00</strong>
|
||||
</div>
|
||||
<div class="token-card danger">
|
||||
<span>Danger</span>
|
||||
<strong>#c73535</strong>
|
||||
</div>
|
||||
<div class="token-card neutral">
|
||||
<span>Neutral</span>
|
||||
<strong>#1d2433</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DemoBlock>
|
||||
</template>
|
||||
@@ -0,0 +1,45 @@
|
||||
<script setup>
|
||||
import { metricCardVariants, metricCards } from '../../data/showcaseData'
|
||||
import DemoBlock from '../docs/DemoBlock.vue'
|
||||
|
||||
const metricCode = `<div class="erp-metric-card erp-metric-card--large is-primary">
|
||||
<div class="erp-metric-card__head">
|
||||
<span>待处理订单</span>
|
||||
<el-icon><Tickets /></el-icon>
|
||||
</div>
|
||||
<strong>128</strong>
|
||||
<small>+12.4%</small>
|
||||
</div>
|
||||
|
||||
<div class="erp-metric-card erp-metric-card--small is-warning">...</div>
|
||||
<div class="erp-metric-card erp-metric-card--mini is-danger">...</div>`
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DemoBlock
|
||||
:code="metricCode"
|
||||
description="同一个 KPI 组件按业务密度拆成大卡片、小卡片、最小卡片。"
|
||||
eyebrow="Metrics"
|
||||
title="KPI 指标卡"
|
||||
>
|
||||
<div class="metric-showcase">
|
||||
<div v-for="variant in metricCardVariants" :key="variant.id" class="metric-variant">
|
||||
<div class="metric-variant-title">{{ variant.label }}</div>
|
||||
<div :class="['metrics-grid', `metrics-grid--${variant.id}`]">
|
||||
<div
|
||||
v-for="metric in metricCards"
|
||||
:key="`${variant.id}-${metric.label}`"
|
||||
:class="['erp-metric-card', variant.className, `is-${metric.tone}`]"
|
||||
>
|
||||
<div class="erp-metric-card__head">
|
||||
<span>{{ metric.label }}</span>
|
||||
<el-icon><component :is="metric.icon" /></el-icon>
|
||||
</div>
|
||||
<strong>{{ metric.value }}</strong>
|
||||
<small>{{ metric.delta }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DemoBlock>
|
||||
</template>
|
||||
@@ -0,0 +1,171 @@
|
||||
<script setup>
|
||||
import {
|
||||
ArrowDown,
|
||||
Bell,
|
||||
Files,
|
||||
Goods,
|
||||
Grid,
|
||||
HomeFilled,
|
||||
Money,
|
||||
Search,
|
||||
Setting,
|
||||
Tickets,
|
||||
User,
|
||||
} from '@element-plus/icons-vue'
|
||||
import DemoBlock from '../docs/DemoBlock.vue'
|
||||
|
||||
defineEmits(['open-drawer'])
|
||||
|
||||
const sideMenuCode = `<el-menu default-active="orders" class="erp-side-menu">
|
||||
<el-sub-menu index="sales">
|
||||
<template #title>
|
||||
<el-icon><Tickets /></el-icon>
|
||||
<span>销售管理</span>
|
||||
</template>
|
||||
<el-menu-item index="orders">销售订单</el-menu-item>
|
||||
<el-menu-item index="returns">退货处理</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-menu-item index="inventory">
|
||||
<el-icon><Goods /></el-icon>
|
||||
<span>库存管理</span>
|
||||
</el-menu-item>
|
||||
</el-menu>`
|
||||
|
||||
const topMenuCode = `<header class="erp-top-menu">
|
||||
<el-menu mode="horizontal" default-active="sales">
|
||||
<el-menu-item index="sales">销售</el-menu-item>
|
||||
<el-menu-item index="purchase">采购</el-menu-item>
|
||||
<el-menu-item index="finance">财务</el-menu-item>
|
||||
</el-menu>
|
||||
<div class="erp-top-menu__actions">
|
||||
<el-input :prefix-icon="Search" placeholder="搜索菜单、单据、客户" />
|
||||
<el-button :icon="Bell" />
|
||||
</div>
|
||||
</header>`
|
||||
|
||||
const breadcrumbCode = `<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item>销售管理</el-breadcrumb-item>
|
||||
<el-breadcrumb-item>销售订单</el-breadcrumb-item>
|
||||
<el-breadcrumb-item>SO-10642</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
|
||||
<el-page-header title="返回订单列表" content="SO-10642 销售订单" />`
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="docs-stack">
|
||||
<DemoBlock
|
||||
:code="sideMenuCode"
|
||||
description="左侧菜单承载 OFBiz 模块树,默认使用可折叠分组,避免所有入口平铺。"
|
||||
eyebrow="Navigation"
|
||||
title="左侧菜单"
|
||||
>
|
||||
<div class="nav-layout-demo">
|
||||
<aside class="nav-sidebar-demo">
|
||||
<div class="nav-brand-demo">
|
||||
<el-icon><Grid /></el-icon>
|
||||
<strong>OFBiz</strong>
|
||||
</div>
|
||||
<el-menu default-active="orders" class="erp-side-menu">
|
||||
<el-sub-menu index="sales">
|
||||
<template #title>
|
||||
<el-icon><Tickets /></el-icon>
|
||||
<span>销售管理</span>
|
||||
</template>
|
||||
<el-menu-item index="orders">销售订单</el-menu-item>
|
||||
<el-menu-item index="returns">退货处理</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-sub-menu index="purchase">
|
||||
<template #title>
|
||||
<el-icon><Files /></el-icon>
|
||||
<span>采购管理</span>
|
||||
</template>
|
||||
<el-menu-item index="requests">采购需求</el-menu-item>
|
||||
<el-menu-item index="vendors">供应商</el-menu-item>
|
||||
</el-sub-menu>
|
||||
<el-menu-item index="inventory">
|
||||
<el-icon><Goods /></el-icon>
|
||||
<span>库存管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="finance">
|
||||
<el-icon><Money /></el-icon>
|
||||
<span>财务应收</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</aside>
|
||||
<main class="nav-content-demo">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item>销售管理</el-breadcrumb-item>
|
||||
<el-breadcrumb-item>销售订单</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
<h3>销售订单工作台</h3>
|
||||
<p>左侧用于模块级导航,页面内部的筛选、表格、抽屉承载具体业务操作。</p>
|
||||
</main>
|
||||
</div>
|
||||
</DemoBlock>
|
||||
|
||||
<DemoBlock
|
||||
:code="topMenuCode"
|
||||
description="顶部菜单适合跨模块一层导航,同时保留搜索、通知和用户入口。"
|
||||
eyebrow="Navigation"
|
||||
title="顶部菜单"
|
||||
>
|
||||
<header class="erp-top-menu">
|
||||
<el-menu mode="horizontal" default-active="sales" class="erp-top-menu__nav">
|
||||
<el-menu-item index="home">
|
||||
<el-icon><HomeFilled /></el-icon>
|
||||
首页
|
||||
</el-menu-item>
|
||||
<el-menu-item index="sales">销售</el-menu-item>
|
||||
<el-menu-item index="purchase">采购</el-menu-item>
|
||||
<el-menu-item index="inventory">库存</el-menu-item>
|
||||
<el-menu-item index="finance">财务</el-menu-item>
|
||||
</el-menu>
|
||||
<div class="erp-top-menu__actions">
|
||||
<el-input class="top-search-demo" :prefix-icon="Search" placeholder="搜索菜单、单据、客户" />
|
||||
<el-button :icon="Bell" />
|
||||
<el-dropdown>
|
||||
<el-button>
|
||||
Lina Chen
|
||||
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item :icon="User">个人设置</el-dropdown-item>
|
||||
<el-dropdown-item :icon="Setting">系统偏好</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</header>
|
||||
</DemoBlock>
|
||||
|
||||
<DemoBlock
|
||||
:code="breadcrumbCode"
|
||||
description="面包屑和页面头用于标识当前位置,抽屉适合承载单据详情与审计信息。"
|
||||
eyebrow="Navigation"
|
||||
title="面包屑与页面头"
|
||||
>
|
||||
<div class="breadcrumb-demo">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item>销售管理</el-breadcrumb-item>
|
||||
<el-breadcrumb-item>销售订单</el-breadcrumb-item>
|
||||
<el-breadcrumb-item>SO-10642</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
<el-page-header title="返回订单列表" content="SO-10642 销售订单">
|
||||
<template #extra>
|
||||
<el-button :icon="Files" @click="$emit('open-drawer')">打开单据抽屉</el-button>
|
||||
</template>
|
||||
</el-page-header>
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item label="客户">上海辰光零售</el-descriptions-item>
|
||||
<el-descriptions-item label="账期">Net 30</el-descriptions-item>
|
||||
<el-descriptions-item label="币种">CNY</el-descriptions-item>
|
||||
<el-descriptions-item label="业务员">Lina Chen</el-descriptions-item>
|
||||
<el-descriptions-item label="仓库">华东中心仓</el-descriptions-item>
|
||||
<el-descriptions-item label="审批状态">已审批</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</DemoBlock>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,67 @@
|
||||
<script setup>
|
||||
import { Bell } from '@element-plus/icons-vue'
|
||||
import { timelineItems } from '../../data/showcaseData'
|
||||
import DemoBlock from '../docs/DemoBlock.vue'
|
||||
|
||||
const statusCode = `<el-tag type="success">已审批</el-tag>
|
||||
<el-tag type="warning">待复核</el-tag>
|
||||
<el-tag type="danger">逾期</el-tag>
|
||||
|
||||
<el-alert
|
||||
title="信用检查未完成"
|
||||
type="warning"
|
||||
show-icon
|
||||
/>
|
||||
|
||||
<el-timeline>
|
||||
<el-timeline-item timestamp="09:20" type="success">
|
||||
销售订单完成审批
|
||||
</el-timeline-item>
|
||||
</el-timeline>`
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DemoBlock
|
||||
:code="statusCode"
|
||||
description="状态、反馈和审计线要低饱和但高可读,用于提醒风险而不是装饰页面。"
|
||||
eyebrow="Status"
|
||||
title="状态、反馈、审计线"
|
||||
>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="section-heading compact">
|
||||
</div>
|
||||
<div class="tag-grid">
|
||||
<el-tag>草稿</el-tag>
|
||||
<el-tag type="success">已审批</el-tag>
|
||||
<el-tag type="warning">待复核</el-tag>
|
||||
<el-tag type="danger">逾期</el-tag>
|
||||
<el-tag type="info">已归档</el-tag>
|
||||
</div>
|
||||
<el-alert
|
||||
title="信用检查未完成"
|
||||
description="建议在出库前确认客户账期和应收余额,避免后续冲销。"
|
||||
type="warning"
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
<div class="feedback-row">
|
||||
<el-progress :percentage="72" />
|
||||
<el-badge :value="8">
|
||||
<el-button :icon="Bell">待办</el-button>
|
||||
</el-badge>
|
||||
</div>
|
||||
</div>
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="item in timelineItems"
|
||||
:key="item.timestamp"
|
||||
:timestamp="item.timestamp"
|
||||
:type="item.type"
|
||||
>
|
||||
{{ item.content }}
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
</DemoBlock>
|
||||
</template>
|
||||
@@ -0,0 +1,86 @@
|
||||
<script setup>
|
||||
import { Location, Search, Sort } from '@element-plus/icons-vue'
|
||||
import { getRiskClass, orderRows } from '../../data/showcaseData'
|
||||
import DemoBlock from '../docs/DemoBlock.vue'
|
||||
|
||||
defineProps({
|
||||
currentPage: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
defineEmits(['update:currentPage'])
|
||||
|
||||
const tableCode = `<el-table :data="orderRows" class="erp-table" stripe>
|
||||
<el-table-column fixed prop="orderId" label="单据号" width="126" />
|
||||
<el-table-column prop="customer" label="客户 / 对象" min-width="180" />
|
||||
<el-table-column prop="status" label="状态" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.statusType" effect="light">{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="amount" label="金额" width="140" align="right" />
|
||||
<el-table-column fixed="right" label="操作" width="152" />
|
||||
</el-table>`
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DemoBlock
|
||||
:code="tableCode"
|
||||
description="ERP 表格优先保证扫描效率、状态清晰、数字右对齐和固定操作列。"
|
||||
eyebrow="Data Table"
|
||||
title="订单工作台表格"
|
||||
>
|
||||
<div class="section-heading">
|
||||
<div class="table-tools">
|
||||
<el-input class="table-search" placeholder="搜索订单、客户、负责人" :prefix-icon="Search" />
|
||||
<el-button :icon="Sort">列设置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table :data="orderRows" class="erp-table" stripe>
|
||||
<el-table-column fixed prop="orderId" label="单据号" width="126" />
|
||||
<el-table-column prop="customer" label="客户 / 对象" min-width="180" />
|
||||
<el-table-column prop="status" label="状态" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.statusType" effect="light">{{ row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="amount" label="金额" width="140" align="right" />
|
||||
<el-table-column prop="warehouse" label="业务地点" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<span class="icon-cell">
|
||||
<el-icon><Location /></el-icon>
|
||||
{{ row.warehouse }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="owner" label="负责人" width="130" />
|
||||
<el-table-column prop="due" label="计划日期" width="124" />
|
||||
<el-table-column prop="risk" label="风险" width="90">
|
||||
<template #default="{ row }">
|
||||
<span :class="['risk-dot', getRiskClass(row.risk)]">{{ row.risk }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" label="操作" width="152">
|
||||
<template #default>
|
||||
<el-button link type="primary">查看</el-button>
|
||||
<el-button link>流转</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="table-footer">
|
||||
<span>共 4 条样例数据,后续接 OFBiz 真实实体。</span>
|
||||
<el-pagination
|
||||
:current-page="currentPage"
|
||||
layout="prev, pager, next, sizes"
|
||||
:page-size="20"
|
||||
:page-sizes="[20, 50, 100]"
|
||||
:total="128"
|
||||
@update:current-page="$emit('update:currentPage', $event)"
|
||||
/>
|
||||
</div>
|
||||
</DemoBlock>
|
||||
</template>
|
||||
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<aside class="token-rail">
|
||||
<div class="rail-card">
|
||||
<p class="eyebrow">Token Notes</p>
|
||||
<ul>
|
||||
<li>字号:12/13/14/18/24,避免随视口缩放。</li>
|
||||
<li>圆角:主控件 6px,卡片 8px。</li>
|
||||
<li>表格:行高 40px,数字右对齐。</li>
|
||||
<li>状态色:低饱和、高可读。</li>
|
||||
<li>按钮:主动作每屏只保留一个。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="rail-card">
|
||||
<p class="eyebrow">Next Components</p>
|
||||
<el-tag effect="plain">Tree</el-tag>
|
||||
<el-tag effect="plain">Upload</el-tag>
|
||||
<el-tag effect="plain">Virtual Table</el-tag>
|
||||
<el-tag effect="plain">Dialog</el-tag>
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
@@ -0,0 +1,52 @@
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
function parseHash() {
|
||||
const rawHash = window.location.hash.replace(/^#\/?/, '')
|
||||
const [path = '', queryString = ''] = rawHash.split('?')
|
||||
const params = new URLSearchParams(queryString)
|
||||
const segments = path.split('/').filter(Boolean)
|
||||
|
||||
return {
|
||||
path,
|
||||
params,
|
||||
segments,
|
||||
}
|
||||
}
|
||||
|
||||
const routeState = ref(parseHash())
|
||||
|
||||
function syncRoute() {
|
||||
routeState.value = parseHash()
|
||||
}
|
||||
|
||||
function buildHash(path, params = {}) {
|
||||
const search = new URLSearchParams()
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
search.set(key, value)
|
||||
}
|
||||
})
|
||||
|
||||
const query = search.toString()
|
||||
return `#/${path.replace(/^\/+/, '')}${query ? `?${query}` : ''}`
|
||||
}
|
||||
|
||||
export function navigateTo(path, params = {}) {
|
||||
window.location.hash = buildHash(path, params)
|
||||
}
|
||||
|
||||
export function useHashRoute() {
|
||||
onMounted(() => {
|
||||
window.addEventListener('hashchange', syncRoute)
|
||||
syncRoute()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('hashchange', syncRoute)
|
||||
})
|
||||
|
||||
return {
|
||||
route: computed(() => routeState.value),
|
||||
navigateTo,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,759 @@
|
||||
import { elementPlusComponentRecords } from './elementPlusInventory.js'
|
||||
|
||||
const groupContracts = {
|
||||
基础: {
|
||||
family: '基础控件',
|
||||
localClass: 'erp-basic-control',
|
||||
previewKind: 'basic',
|
||||
usage: '用于低风险通用控件,先继承 ERP token,再按业务动作层级收敛到 wrapper。',
|
||||
styleFocus: '字号、按钮高度、焦点态、禁用态和图标间距统一。',
|
||||
},
|
||||
布局: {
|
||||
family: '布局容器',
|
||||
localClass: 'erp-layout-region',
|
||||
previewKind: 'layout',
|
||||
usage: '用于页面骨架和内容区组织,避免卡片套卡片,保持业务页面可扫描。',
|
||||
styleFocus: '边框、留白、分区标题和内容密度统一。',
|
||||
},
|
||||
表单: {
|
||||
family: '表单控件',
|
||||
localClass: 'erp-field-control',
|
||||
previewKind: 'form',
|
||||
usage: '用于查询条件和业务录入,默认 label-position="top",复杂字段进入高级区。',
|
||||
styleFocus: '输入高度、label 字重、校验色、组合控件间距统一。',
|
||||
},
|
||||
数据展示: {
|
||||
family: '数据展示',
|
||||
localClass: 'erp-data-view',
|
||||
previewKind: 'data',
|
||||
usage: '用于列表、详情、树和状态摘要,优先保证扫描效率和数字对齐。',
|
||||
styleFocus: '表头、行高、状态色、空态和分页密度统一。',
|
||||
},
|
||||
导航: {
|
||||
family: '导航控件',
|
||||
localClass: 'erp-navigation-control',
|
||||
previewKind: 'navigation',
|
||||
usage: '用于模块树、顶部导航、面包屑和页面路径,保持当前位置明确。',
|
||||
styleFocus: '选中态、悬停态、层级缩进和横向菜单 1px 下划线统一。',
|
||||
},
|
||||
反馈与覆盖层: {
|
||||
family: '反馈与覆盖层',
|
||||
localClass: 'erp-feedback-control',
|
||||
previewKind: 'feedback',
|
||||
usage: '用于保存结果、审批确认、异常提示和侧边详情,控制打扰程度。',
|
||||
styleFocus: '弹层宽度、按钮顺序、遮罩、提示色和危险动作层级统一。',
|
||||
},
|
||||
内部基础: {
|
||||
family: '内部基础',
|
||||
localClass: 'erp-internal-foundation',
|
||||
previewKind: 'internal',
|
||||
usage: '多数不在业务页面直接调用,保留在清单中用于识别组合组件依赖。',
|
||||
styleFocus: '不单独定制视觉,跟随父组件 wrapper 和全局 Element Plus token。',
|
||||
},
|
||||
}
|
||||
|
||||
const groupGuidance = {
|
||||
基础: {
|
||||
useWhen: ['需要触发动作、表达轻量文本、组织间距或控制全局配置时。'],
|
||||
avoidWhen: ['不要用基础控件承担业务结构;重复业务模式应沉淀为 ERP wrapper。'],
|
||||
layoutRules: ['基础控件跟随所在区域密度,按钮组靠近被操作对象,图标按钮必须有明确语义。'],
|
||||
},
|
||||
布局: {
|
||||
useWhen: ['需要组织页面骨架、分区、栅格、折叠内容或详情摘要时。'],
|
||||
avoidWhen: ['不要把页面切成多层浮动卡片;不要为了装饰而增加容器。'],
|
||||
layoutRules: ['页面优先使用左导航 + 顶部上下文 + 主工作区;分区间距使用 20/24px。'],
|
||||
},
|
||||
表单: {
|
||||
useWhen: ['需要查询条件、实体录入、筛选、选择器、上传或复杂字段编辑时。'],
|
||||
avoidWhen: ['不要把低频高级条件塞进第一行;不要混用不同 label 对齐方式。'],
|
||||
layoutRules: ['查询表单默认 4 列,录入表单默认 2 列;label 在上,主按钮靠右。'],
|
||||
},
|
||||
数据展示: {
|
||||
useWhen: ['需要扫描业务对象、查看状态、对比金额、浏览树结构或展示空/加载状态时。'],
|
||||
avoidWhen: ['不要用大卡片替代表格;有批量操作和排序需求时优先表格。'],
|
||||
layoutRules: ['列表区顺序是筛选栏、工具栏、表格、分页;数字右对齐,状态靠近主对象。'],
|
||||
},
|
||||
导航: {
|
||||
useWhen: ['需要模块切换、页面路径、局部 tab、流程步骤或审计时间线时。'],
|
||||
avoidWhen: ['不要在同一屏放多套主导航;顶部菜单选中态只用 1px 下划线。'],
|
||||
layoutRules: ['模块级导航在左侧,页面内导航用 tabs/steps,当前位置用 breadcrumb/page-header。'],
|
||||
},
|
||||
反馈与覆盖层: {
|
||||
useWhen: ['需要确认危险动作、提示保存结果、展示抽屉详情、阻塞式编辑或轻提示时。'],
|
||||
avoidWhen: ['不要让 toast 承载必须阅读的信息;不要把复杂流程塞进小 popover。'],
|
||||
layoutRules: ['非阻塞反馈用 message/notification,确认用 popconfirm/dialog,详情用 drawer。'],
|
||||
},
|
||||
内部基础: {
|
||||
useWhen: ['作为组合组件依赖存在,或为了理解 Element Plus 能力边界时。'],
|
||||
avoidWhen: ['不要在 OFBiz 业务页直接调用内部基础件。'],
|
||||
layoutRules: ['跟随父组件语境,不单独规定排版。'],
|
||||
},
|
||||
}
|
||||
|
||||
const previewGuidance = {
|
||||
table: {
|
||||
useWhen: ['对象数量多、字段多、需要排序筛选、批量操作或固定操作列时。'],
|
||||
avoidWhen: ['不要用表格展示少量摘要;少于 3 项的摘要优先 descriptions 或 metric card。'],
|
||||
layoutRules: ['上方放查询和批量动作;表头紧凑,金额右对齐,分页贴近表格底部。'],
|
||||
},
|
||||
'table-v2': {
|
||||
useWhen: ['行数非常多、滚动性能成为瓶颈、列结构稳定时。'],
|
||||
avoidWhen: ['需要复杂插槽、树形展开或高度动态内容时先用普通 Table。'],
|
||||
layoutRules: ['固定宽高,先定义列宽;不要让虚拟表格撑开页面布局。'],
|
||||
},
|
||||
pagination: {
|
||||
useWhen: ['列表数据超过一页,或用户需要切换 page size 时。'],
|
||||
avoidWhen: ['小型静态列表不需要分页;无限滚动不适合审计型 ERP 数据。'],
|
||||
layoutRules: ['分页放在表格右下角,显示条数选择和总数。'],
|
||||
},
|
||||
form: {
|
||||
useWhen: ['需要成组收集业务字段、校验、提交或查询。'],
|
||||
avoidWhen: ['只读详情不要伪装成表单,使用 descriptions。'],
|
||||
layoutRules: ['查询 4 列、编辑 2 列;高级条件第二行,按钮右对齐。'],
|
||||
},
|
||||
field: {
|
||||
useWhen: ['短文本、编码、金额、数量、标签等单字段输入。'],
|
||||
avoidWhen: ['实体查找不要用普通 input,应使用 autocomplete/tree-select/cascader。'],
|
||||
layoutRules: ['输入框宽度跟随列,不单独拉满整行,单位和帮助信息贴近字段。'],
|
||||
},
|
||||
select: {
|
||||
useWhen: ['选项有限、语义明确、需要从合法值中选择时。'],
|
||||
avoidWhen: ['超过几十项且需要搜索实体时用 Autocomplete 或 Select V2。'],
|
||||
layoutRules: ['单选宽度随字段列,多选要预留换行高度,选项文案保持业务名词。'],
|
||||
},
|
||||
lookup: {
|
||||
useWhen: ['需要从客户、供应商、组织、仓库等实体中查找并选择时。'],
|
||||
avoidWhen: ['简单枚举值不要使用实体查找控件。'],
|
||||
layoutRules: ['搜索提示写明可搜编码/名称/联系人;结果要显示可区分的次要信息。'],
|
||||
},
|
||||
choice: {
|
||||
useWhen: ['状态过滤、互斥状态、少量布尔/枚举条件。'],
|
||||
avoidWhen: ['选项超过 6 个时不要横排铺满,改用 Select。'],
|
||||
layoutRules: ['横向组选项保持同一语义层级,按钮式选择只用于高频筛选。'],
|
||||
},
|
||||
button: {
|
||||
useWhen: ['需要明确动作:查询、新建、审批、导出、作废。'],
|
||||
avoidWhen: ['不要让多个 primary 同屏竞争;弱动作优先 link/dropdown。'],
|
||||
layoutRules: ['一个工作区一个主按钮;危险动作靠后并用二次确认。'],
|
||||
},
|
||||
dropdown: {
|
||||
useWhen: ['低频动作、多动作集合或表格行更多操作。'],
|
||||
avoidWhen: ['核心主流程不要藏进 dropdown。'],
|
||||
layoutRules: ['主动作外露,更多动作收进 dropdown;危险项分隔。'],
|
||||
},
|
||||
menu: {
|
||||
useWhen: ['模块级导航、业务域切换、多层入口树。'],
|
||||
avoidWhen: ['不要把页面内部筛选或 tab 放进主菜单。'],
|
||||
layoutRules: ['左侧菜单分组折叠,选中态安静,图标只用于一级模块。'],
|
||||
},
|
||||
breadcrumb: {
|
||||
useWhen: ['需要说明当前位置、可返回路径或详情页上下文。'],
|
||||
avoidWhen: ['单层页面或无层级路径时不要强塞 breadcrumb。'],
|
||||
layoutRules: ['放在页面标题上方或详情头部,最后一级不做跳转。'],
|
||||
},
|
||||
tabs: {
|
||||
useWhen: ['同一业务对象下切换摘要、明细、审计、附件等局部视图。'],
|
||||
avoidWhen: ['跨模块导航不要用 Tabs,使用 Menu。'],
|
||||
layoutRules: ['标签数量控制在 2-6 个,内容区高度稳定,默认打开最高频页。'],
|
||||
},
|
||||
status: {
|
||||
useWhen: ['需要展示审批、逾期、风险、库存等状态。'],
|
||||
avoidWhen: ['不要用颜色替代文字;状态必须有可读标签。'],
|
||||
layoutRules: ['状态靠近对象名或状态列,颜色低饱和,危险态只用于真实风险。'],
|
||||
},
|
||||
overlay: {
|
||||
useWhen: ['确认、编辑、详情、阻塞式流程或轻量上下文说明。'],
|
||||
avoidWhen: ['多步骤复杂流程不要塞入 Popover 或小 Dialog。'],
|
||||
layoutRules: ['Dialog 做确认/短表单,Drawer 做详情和审计,按钮顺序稳定。'],
|
||||
},
|
||||
service: {
|
||||
useWhen: ['保存结果、异步完成、全局通知、加载锁定。'],
|
||||
avoidWhen: ['必须持久存在的信息不要只用 Message。'],
|
||||
layoutRules: ['反馈文案短、明确、可恢复;错误信息给下一步动作。'],
|
||||
},
|
||||
descriptions: {
|
||||
useWhen: ['只读详情、审批上下文、实体摘要。'],
|
||||
avoidWhen: ['需要编辑时不要用 descriptions 模拟输入。'],
|
||||
layoutRules: ['2-3 列,label 固定宽度,重要字段靠前。'],
|
||||
},
|
||||
tree: {
|
||||
useWhen: ['组织、目录、权限、仓库层级。'],
|
||||
avoidWhen: ['平铺枚举不要用树。'],
|
||||
layoutRules: ['默认展开到可决策层级,节点文字短,右侧展示详情或表格。'],
|
||||
},
|
||||
empty: {
|
||||
useWhen: ['搜索无结果、列表初始无数据、筛选条件过窄。'],
|
||||
avoidWhen: ['加载中不要显示 Empty,应使用 Skeleton/Loading。'],
|
||||
layoutRules: ['空态给原因和下一步动作,不要占据整屏视觉重心。'],
|
||||
},
|
||||
progress: {
|
||||
useWhen: ['导入、批处理、履约进度、库存同步进度。'],
|
||||
avoidWhen: ['状态不可量化时不要使用进度条。'],
|
||||
layoutRules: ['进度条旁边要有任务名或百分比解释。'],
|
||||
},
|
||||
result: {
|
||||
useWhen: ['流程完成、提交成功、不可恢复错误结果页。'],
|
||||
avoidWhen: ['普通保存成功用 Message,不要跳大结果页。'],
|
||||
layoutRules: ['结果页只用于流程终点,提供返回或继续动作。'],
|
||||
},
|
||||
skeleton: {
|
||||
useWhen: ['表格、详情或卡片数据还在加载。'],
|
||||
avoidWhen: ['加载时间极短时不要闪烁骨架屏。'],
|
||||
layoutRules: ['骨架形状贴近最终内容,避免布局跳动。'],
|
||||
},
|
||||
timeline: {
|
||||
useWhen: ['审计记录、审批流、订单流转、系统事件。'],
|
||||
avoidWhen: ['当前进度步骤用 Steps,不用 Timeline。'],
|
||||
layoutRules: ['时间在左,事件短句,异常事件用 warning/danger。'],
|
||||
},
|
||||
steps: {
|
||||
useWhen: ['流程当前位置明确,如创建、审批、锁库、发运。'],
|
||||
avoidWhen: ['历史记录不要用 Steps,用 Timeline。'],
|
||||
layoutRules: ['横向步骤不超过 5 个,当前态清楚,完成态低调。'],
|
||||
},
|
||||
transfer: {
|
||||
useWhen: ['权限分配、模块授权、双列表选择。'],
|
||||
avoidWhen: ['普通多选不要用 Transfer。'],
|
||||
layoutRules: ['左右标题明确,列表高度固定,搜索与批量选择可用。'],
|
||||
},
|
||||
}
|
||||
|
||||
const parentUsage = {
|
||||
'anchor-link': 'anchor',
|
||||
'avatar-group': 'avatar',
|
||||
'breadcrumb-item': 'breadcrumb',
|
||||
'button-group': 'button',
|
||||
'carousel-item': 'carousel',
|
||||
'cascader-panel': 'cascader',
|
||||
'checkbox-button': 'checkbox-group',
|
||||
'checkbox-group': 'checkbox',
|
||||
'col': 'row',
|
||||
'collapse-item': 'collapse',
|
||||
'color-picker-panel': 'color-picker',
|
||||
'date-picker-panel': 'date-picker',
|
||||
'descriptions-item': 'descriptions',
|
||||
'dropdown-item': 'dropdown',
|
||||
'dropdown-menu': 'dropdown',
|
||||
'form-item': 'form',
|
||||
'image-viewer': 'image',
|
||||
'menu-item': 'menu',
|
||||
'menu-item-group': 'menu',
|
||||
option: 'select',
|
||||
'option-group': 'select',
|
||||
'radio-button': 'radio-group',
|
||||
'radio-group': 'radio',
|
||||
'skeleton-item': 'skeleton',
|
||||
'splitter-panel': 'splitter',
|
||||
step: 'steps',
|
||||
'sub-menu': 'menu',
|
||||
'tab-pane': 'tabs',
|
||||
'table-column': 'table',
|
||||
'timeline-item': 'timeline',
|
||||
'tour-step': 'tour',
|
||||
}
|
||||
|
||||
const componentDocOverrides = {
|
||||
alert: {
|
||||
previewKind: 'status',
|
||||
localClass: 'erp-status-alert',
|
||||
erpCode: '<el-alert class="erp-status-alert" title="信用检查未完成" type="warning" show-icon :closable="false" />',
|
||||
},
|
||||
autocomplete: {
|
||||
previewKind: 'lookup',
|
||||
localClass: 'erp-entity-lookup',
|
||||
erpCode: '<el-autocomplete class="erp-entity-lookup" v-model="keyword" :fetch-suggestions="queryCustomer" placeholder="搜索客户编码、名称或联系人" />',
|
||||
},
|
||||
badge: {
|
||||
previewKind: 'status',
|
||||
localClass: 'erp-status-badge',
|
||||
erpCode: '<el-badge class="erp-status-badge" :value="8"><el-button>待办事项</el-button></el-badge>',
|
||||
},
|
||||
breadcrumb: {
|
||||
previewKind: 'breadcrumb',
|
||||
localClass: 'erp-breadcrumb',
|
||||
erpCode: '<el-breadcrumb class="erp-breadcrumb" separator="/"><el-breadcrumb-item>销售管理</el-breadcrumb-item><el-breadcrumb-item>销售订单</el-breadcrumb-item></el-breadcrumb>',
|
||||
},
|
||||
'breadcrumb-item': {
|
||||
previewKind: 'breadcrumb',
|
||||
localClass: 'erp-breadcrumb',
|
||||
},
|
||||
button: {
|
||||
previewKind: 'button',
|
||||
localClass: 'erp-action-button',
|
||||
erpCode: '<el-button class="erp-action-button" type="primary">新建订单</el-button>',
|
||||
},
|
||||
'button-group': {
|
||||
previewKind: 'button',
|
||||
localClass: 'erp-action-group',
|
||||
},
|
||||
calendar: {
|
||||
previewKind: 'calendar',
|
||||
localClass: 'erp-calendar',
|
||||
},
|
||||
card: {
|
||||
previewKind: 'card',
|
||||
localClass: 'erp-entity-card',
|
||||
erpCode: '<section class="erp-entity-card"><header>订单摘要</header><strong>SO-10642</strong></section>',
|
||||
},
|
||||
carousel: {
|
||||
previewKind: 'carousel',
|
||||
localClass: 'erp-carousel',
|
||||
},
|
||||
'carousel-item': {
|
||||
previewKind: 'carousel',
|
||||
localClass: 'erp-carousel',
|
||||
},
|
||||
cascader: {
|
||||
previewKind: 'lookup',
|
||||
localClass: 'erp-field-control',
|
||||
erpCode: '<el-cascader class="erp-field-control" v-model="region" :options="regionOptions" placeholder="选择组织 / 仓库" />',
|
||||
},
|
||||
checkbox: {
|
||||
previewKind: 'choice',
|
||||
localClass: 'erp-choice-group',
|
||||
},
|
||||
'checkbox-button': {
|
||||
previewKind: 'choice',
|
||||
localClass: 'erp-choice-group',
|
||||
},
|
||||
'checkbox-group': {
|
||||
previewKind: 'choice',
|
||||
localClass: 'erp-choice-group',
|
||||
},
|
||||
'color-picker': {
|
||||
previewKind: 'color',
|
||||
localClass: 'erp-color-picker',
|
||||
},
|
||||
'color-picker-panel': {
|
||||
previewKind: 'color',
|
||||
localClass: 'erp-color-picker',
|
||||
},
|
||||
collapse: {
|
||||
previewKind: 'collapse',
|
||||
localClass: 'erp-collapse',
|
||||
},
|
||||
'collapse-item': {
|
||||
previewKind: 'collapse',
|
||||
localClass: 'erp-collapse',
|
||||
},
|
||||
'date-picker': {
|
||||
previewKind: 'date',
|
||||
localClass: 'erp-date-range',
|
||||
erpCode: '<el-date-picker class="erp-date-range" v-model="dateRange" type="daterange" start-placeholder="开始日期" end-placeholder="结束日期" />',
|
||||
},
|
||||
'date-picker-panel': {
|
||||
previewKind: 'date',
|
||||
localClass: 'erp-date-range',
|
||||
},
|
||||
descriptions: {
|
||||
previewKind: 'descriptions',
|
||||
localClass: 'erp-detail-descriptions',
|
||||
erpCode: '<el-descriptions class="erp-detail-descriptions" :column="3" border><el-descriptions-item label="客户">上海辰光零售</el-descriptions-item></el-descriptions>',
|
||||
},
|
||||
'descriptions-item': {
|
||||
previewKind: 'descriptions',
|
||||
localClass: 'erp-detail-descriptions',
|
||||
},
|
||||
dialog: {
|
||||
previewKind: 'overlay',
|
||||
localClass: 'erp-dialog',
|
||||
erpCode: '<el-dialog v-model="visible" class="erp-dialog" title="审批确认">确认通过该订单?</el-dialog>',
|
||||
},
|
||||
drawer: {
|
||||
previewKind: 'overlay',
|
||||
localClass: 'erp-drawer',
|
||||
erpCode: '<el-drawer v-model="visible" class="erp-drawer" title="单据详情">SO-10642</el-drawer>',
|
||||
},
|
||||
dropdown: {
|
||||
previewKind: 'dropdown',
|
||||
localClass: 'erp-action-dropdown',
|
||||
erpCode: '<el-dropdown class="erp-action-dropdown"><el-button>更多操作</el-button><template #dropdown><el-dropdown-menu><el-dropdown-item>导出</el-dropdown-item></el-dropdown-menu></template></el-dropdown>',
|
||||
},
|
||||
'dropdown-item': {
|
||||
previewKind: 'dropdown',
|
||||
localClass: 'erp-action-dropdown',
|
||||
},
|
||||
'dropdown-menu': {
|
||||
previewKind: 'dropdown',
|
||||
localClass: 'erp-action-dropdown',
|
||||
},
|
||||
empty: {
|
||||
previewKind: 'empty',
|
||||
localClass: 'erp-empty-state',
|
||||
},
|
||||
image: {
|
||||
previewKind: 'media',
|
||||
localClass: 'erp-media-preview',
|
||||
},
|
||||
'image-viewer': {
|
||||
previewKind: 'media',
|
||||
localClass: 'erp-media-preview',
|
||||
},
|
||||
form: {
|
||||
previewKind: 'form',
|
||||
localClass: 'erp-form',
|
||||
erpCode: '<el-form class="erp-form" label-position="top"><el-form-item label="客户"><el-input placeholder="输入名称、编码或联系人" /></el-form-item></el-form>',
|
||||
},
|
||||
'form-item': {
|
||||
previewKind: 'form',
|
||||
localClass: 'erp-form',
|
||||
},
|
||||
input: {
|
||||
previewKind: 'field',
|
||||
localClass: 'erp-field-control',
|
||||
erpCode: '<el-input class="erp-field-control" v-model="keyword" placeholder="搜索订单、客户、负责人" />',
|
||||
},
|
||||
'input-number': {
|
||||
previewKind: 'field',
|
||||
localClass: 'erp-field-control',
|
||||
},
|
||||
'input-tag': {
|
||||
previewKind: 'field',
|
||||
localClass: 'erp-field-control',
|
||||
},
|
||||
link: {
|
||||
previewKind: 'typography',
|
||||
localClass: 'erp-inline-link',
|
||||
},
|
||||
loading: {
|
||||
previewKind: 'service',
|
||||
localClass: 'erp-loading-action',
|
||||
},
|
||||
menu: {
|
||||
previewKind: 'menu',
|
||||
localClass: 'erp-side-menu',
|
||||
erpCode: '<el-menu class="erp-side-menu" default-active="orders"><el-sub-menu index="sales"><template #title>销售管理</template><el-menu-item index="orders">销售订单</el-menu-item></el-sub-menu></el-menu>',
|
||||
},
|
||||
'menu-item': {
|
||||
previewKind: 'menu',
|
||||
localClass: 'erp-side-menu',
|
||||
},
|
||||
'menu-item-group': {
|
||||
previewKind: 'menu',
|
||||
localClass: 'erp-side-menu',
|
||||
},
|
||||
message: {
|
||||
previewKind: 'service',
|
||||
localClass: 'erp-message-action',
|
||||
erpCode: "ElMessage.success('订单已保存')",
|
||||
},
|
||||
'message-box': {
|
||||
previewKind: 'service',
|
||||
localClass: 'erp-message-box-action',
|
||||
erpCode: "ElMessageBox.alert('确认通过该订单?', '审批确认')",
|
||||
},
|
||||
notification: {
|
||||
previewKind: 'service',
|
||||
localClass: 'erp-notification-action',
|
||||
erpCode: "ElNotification({ title: '库存预警', message: '36 个 SKU 低于安全库存', type: 'warning' })",
|
||||
},
|
||||
option: {
|
||||
previewKind: 'select',
|
||||
localClass: 'erp-field-control',
|
||||
},
|
||||
'option-group': {
|
||||
previewKind: 'select',
|
||||
localClass: 'erp-field-control',
|
||||
},
|
||||
'page-header': {
|
||||
previewKind: 'breadcrumb',
|
||||
localClass: 'erp-page-header',
|
||||
},
|
||||
pagination: {
|
||||
previewKind: 'pagination',
|
||||
localClass: 'erp-dense-pagination',
|
||||
erpCode: '<el-pagination class="erp-dense-pagination" layout="prev, pager, next, sizes" :page-size="20" :page-sizes="[20, 50, 100]" :total="128" />',
|
||||
},
|
||||
popconfirm: {
|
||||
previewKind: 'overlay',
|
||||
localClass: 'erp-popconfirm',
|
||||
},
|
||||
progress: {
|
||||
previewKind: 'progress',
|
||||
localClass: 'erp-progress',
|
||||
},
|
||||
radio: {
|
||||
previewKind: 'choice',
|
||||
localClass: 'erp-choice-group',
|
||||
},
|
||||
'radio-button': {
|
||||
previewKind: 'choice',
|
||||
localClass: 'erp-choice-group',
|
||||
},
|
||||
'radio-group': {
|
||||
previewKind: 'choice',
|
||||
localClass: 'erp-choice-group',
|
||||
},
|
||||
rate: {
|
||||
previewKind: 'rate',
|
||||
localClass: 'erp-rate',
|
||||
},
|
||||
result: {
|
||||
previewKind: 'result',
|
||||
localClass: 'erp-result-state',
|
||||
},
|
||||
select: {
|
||||
previewKind: 'select',
|
||||
localClass: 'erp-field-control',
|
||||
erpCode: '<el-select class="erp-field-control" v-model="warehouse"><el-option label="华东中心仓" value="east" /></el-select>',
|
||||
},
|
||||
'select-v2': {
|
||||
previewKind: 'select',
|
||||
localClass: 'erp-field-control',
|
||||
erpCode: '<el-select-v2 class="erp-field-control" v-model="warehouse" :options="warehouseOptions" />',
|
||||
},
|
||||
segmented: {
|
||||
previewKind: 'tabs',
|
||||
localClass: 'erp-segmented',
|
||||
},
|
||||
skeleton: {
|
||||
previewKind: 'skeleton',
|
||||
localClass: 'erp-skeleton',
|
||||
},
|
||||
'skeleton-item': {
|
||||
previewKind: 'skeleton',
|
||||
localClass: 'erp-skeleton',
|
||||
},
|
||||
slider: {
|
||||
previewKind: 'slider',
|
||||
localClass: 'erp-slider',
|
||||
},
|
||||
space: {
|
||||
previewKind: 'space',
|
||||
localClass: 'erp-space',
|
||||
},
|
||||
statistic: {
|
||||
previewKind: 'statistic',
|
||||
localClass: 'erp-statistic',
|
||||
},
|
||||
step: {
|
||||
previewKind: 'steps',
|
||||
localClass: 'erp-steps',
|
||||
},
|
||||
steps: {
|
||||
previewKind: 'steps',
|
||||
localClass: 'erp-steps',
|
||||
},
|
||||
'sub-menu': {
|
||||
previewKind: 'menu',
|
||||
localClass: 'erp-side-menu',
|
||||
},
|
||||
switch: {
|
||||
previewKind: 'switch',
|
||||
localClass: 'erp-switch',
|
||||
},
|
||||
'tab-pane': {
|
||||
previewKind: 'tabs',
|
||||
localClass: 'erp-tabs',
|
||||
},
|
||||
table: {
|
||||
previewKind: 'table',
|
||||
localClass: 'erp-table',
|
||||
erpCode: '<el-table :data="rows" class="erp-table" stripe><el-table-column prop="orderId" label="单据号" width="126" /></el-table>',
|
||||
},
|
||||
'table-column': {
|
||||
previewKind: 'table',
|
||||
localClass: 'erp-table',
|
||||
},
|
||||
'table-v2': {
|
||||
previewKind: 'table-v2',
|
||||
localClass: 'erp-virtual-table',
|
||||
erpCode: '<el-table-v2 class="erp-virtual-table" :columns="columns" :data="rows" :width="760" :height="260" />',
|
||||
},
|
||||
tabs: {
|
||||
previewKind: 'tabs',
|
||||
localClass: 'erp-tabs',
|
||||
},
|
||||
tag: {
|
||||
previewKind: 'status',
|
||||
localClass: 'erp-status-tag',
|
||||
erpCode: '<el-tag class="erp-status-tag" type="success" effect="light">已审批</el-tag>',
|
||||
},
|
||||
text: {
|
||||
previewKind: 'typography',
|
||||
localClass: 'erp-inline-text',
|
||||
},
|
||||
timeline: {
|
||||
previewKind: 'timeline',
|
||||
localClass: 'erp-timeline',
|
||||
},
|
||||
'timeline-item': {
|
||||
previewKind: 'timeline',
|
||||
localClass: 'erp-timeline',
|
||||
},
|
||||
tooltip: {
|
||||
previewKind: 'overlay',
|
||||
localClass: 'erp-tooltip',
|
||||
},
|
||||
transfer: {
|
||||
previewKind: 'transfer',
|
||||
localClass: 'erp-transfer',
|
||||
},
|
||||
tree: {
|
||||
previewKind: 'tree',
|
||||
localClass: 'erp-tree',
|
||||
},
|
||||
'tree-select': {
|
||||
previewKind: 'tree',
|
||||
localClass: 'erp-tree-select',
|
||||
},
|
||||
'tree-v2': {
|
||||
previewKind: 'tree',
|
||||
localClass: 'erp-tree',
|
||||
},
|
||||
upload: {
|
||||
previewKind: 'upload',
|
||||
localClass: 'erp-upload',
|
||||
},
|
||||
}
|
||||
|
||||
function pascalName(name) {
|
||||
return name.replace(/\s+/g, '')
|
||||
}
|
||||
|
||||
function fallbackErpCode(record, localClass) {
|
||||
const parent = parentUsage[record.slug]
|
||||
|
||||
if (parent) {
|
||||
return `<!-- ${record.name} 跟随 ${parent} 的 ERP wrapper 使用。 -->\n<${record.tag} class="${localClass}">${record.name}</${record.tag}>`
|
||||
}
|
||||
|
||||
if (!record.hasPublicExport || record.group === '内部基础') {
|
||||
return `<!-- ${record.name} 是内部/组合基础件,不建议在 OFBiz 业务页直接调用。 -->\n<${record.tag}>${record.name}</${record.tag}>`
|
||||
}
|
||||
|
||||
return `<${record.tag} class="${localClass}">${record.name}</${record.tag}>`
|
||||
}
|
||||
|
||||
function styleCodeFor(record, localClass, contract) {
|
||||
return `/* src/styles/erp-patterns.css */\n.${localClass} {\n color: var(--erp-color-text);\n font-size: var(--erp-font-size-sm);\n border-color: var(--erp-color-border-soft);\n}\n\n/* ${record.name}: ${contract.styleFocus} */`
|
||||
}
|
||||
|
||||
function wrapperNameFor(record) {
|
||||
const parent = parentUsage[record.slug]
|
||||
if (parent) return `跟随 ${parent} wrapper`
|
||||
if (!record.hasPublicExport || record.group === '内部基础') return '不直接封装'
|
||||
return `Erp${pascalName(record.name)}`
|
||||
}
|
||||
|
||||
function guidanceFor(record, previewKind) {
|
||||
const group = groupGuidance[record.group] ?? groupGuidance['内部基础']
|
||||
const preview = previewGuidance[previewKind] ?? {}
|
||||
return {
|
||||
useWhen: preview.useWhen ?? group.useWhen,
|
||||
avoidWhen: preview.avoidWhen ?? group.avoidWhen,
|
||||
layoutRules: preview.layoutRules ?? group.layoutRules,
|
||||
}
|
||||
}
|
||||
|
||||
export const erpComponentDocs = elementPlusComponentRecords.map((record) => {
|
||||
const contract = groupContracts[record.group] ?? groupContracts['内部基础']
|
||||
const override = componentDocOverrides[record.slug] ?? {}
|
||||
const localClass = override.localClass ?? contract.localClass
|
||||
const parent = parentUsage[record.slug] ?? ''
|
||||
const previewKind = override.previewKind ?? contract.previewKind
|
||||
const guidance = guidanceFor(record, previewKind)
|
||||
|
||||
return {
|
||||
...record,
|
||||
family: override.family ?? contract.family,
|
||||
localClass,
|
||||
parentSlug: parent,
|
||||
previewKind,
|
||||
originalCode: override.originalCode ?? record.code,
|
||||
erpCode: override.erpCode ?? fallbackErpCode(record, localClass),
|
||||
styleCode: override.styleCode ?? styleCodeFor(record, localClass, contract),
|
||||
usage: override.usage ?? contract.usage,
|
||||
styleFocus: override.styleFocus ?? contract.styleFocus,
|
||||
whenToUse: override.whenToUse ?? guidance.useWhen,
|
||||
avoidWhen: override.avoidWhen ?? guidance.avoidWhen,
|
||||
layoutRules: override.layoutRules ?? guidance.layoutRules,
|
||||
wrapperName: override.wrapperName ?? wrapperNameFor(record),
|
||||
cssFiles: ['src/styles/erp-ui.css', 'src/styles/tokens.css', 'src/styles/element-overrides.css', 'src/styles/erp-patterns.css'],
|
||||
detailRoute: `#/component/${record.slug}`,
|
||||
}
|
||||
})
|
||||
|
||||
export const erpComponentGroups = Object.keys(groupContracts).map((group) => ({
|
||||
group,
|
||||
count: erpComponentDocs.filter((component) => component.group === group).length,
|
||||
contract: groupContracts[group],
|
||||
}))
|
||||
|
||||
export function getErpComponentDoc(slug) {
|
||||
return erpComponentDocs.find((component) => component.slug === slug)
|
||||
}
|
||||
|
||||
export const erpComponentUsageMap = [
|
||||
{
|
||||
title: '页面骨架',
|
||||
description: '搭建 OFBiz 模块、列表页、详情页的稳定结构。',
|
||||
components: ['container', 'aside', 'main', 'card', 'collapse', 'splitter'],
|
||||
},
|
||||
{
|
||||
title: '导航定位',
|
||||
description: '让用户知道自己在哪、可以去哪、流程到哪一步。',
|
||||
components: ['menu', 'breadcrumb', 'page-header', 'tabs', 'steps', 'timeline'],
|
||||
},
|
||||
{
|
||||
title: '查询录入',
|
||||
description: '承载筛选、编辑、实体选择和合法值输入。',
|
||||
components: ['form', 'input', 'select', 'date-picker', 'tree-select', 'autocomplete', 'upload'],
|
||||
},
|
||||
{
|
||||
title: '数据扫描',
|
||||
description: '高密度展示订单、库存、财务和组织结构。',
|
||||
components: ['table', 'table-v2', 'pagination', 'descriptions', 'tree', 'statistic', 'tag'],
|
||||
},
|
||||
{
|
||||
title: '动作反馈',
|
||||
description: '处理审批、保存、作废、异常提示和完成状态。',
|
||||
components: ['button', 'dropdown', 'popconfirm', 'dialog', 'drawer', 'message', 'alert', 'result'],
|
||||
},
|
||||
{
|
||||
title: '状态与加载',
|
||||
description: '表达空态、加载、进度、风险、附件和审计上下文。',
|
||||
components: ['empty', 'skeleton', 'progress', 'badge', 'calendar', 'image', 'transfer'],
|
||||
},
|
||||
].map((section) => ({
|
||||
...section,
|
||||
components: section.components
|
||||
.map((slug) => erpComponentDocs.find((component) => component.slug === slug))
|
||||
.filter(Boolean),
|
||||
}))
|
||||
|
||||
export const erpLayoutPlaybook = [
|
||||
{
|
||||
title: '列表页',
|
||||
description: '用于销售订单、采购需求、库存、应收等高频工作台。',
|
||||
density: '高密度',
|
||||
rules: ['面包屑和标题在顶部,查询条件一行优先。', '工具栏紧贴表格,批量动作在选择后出现。', '表格底部右侧放 Pagination。'],
|
||||
components: ['breadcrumb', 'page-header', 'form', 'input', 'select', 'date-picker', 'button', 'table', 'pagination', 'tag', 'dropdown'],
|
||||
},
|
||||
{
|
||||
title: '详情页',
|
||||
description: '用于订单、客户、供应商、财务单据等只读和局部编辑场景。',
|
||||
density: '中密度',
|
||||
rules: ['上方显示对象身份、状态和主动作。', '摘要使用 Descriptions,明细用 Tabs + Table。', '审计和附件放 Drawer 或独立 Tab。'],
|
||||
components: ['breadcrumb', 'page-header', 'tag', 'descriptions', 'tabs', 'table', 'timeline', 'drawer', 'upload'],
|
||||
},
|
||||
{
|
||||
title: '录入页',
|
||||
description: '用于创建订单、维护基础资料、批量导入前的结构化编辑。',
|
||||
density: '中低密度',
|
||||
rules: ['表单默认两列,强校验字段靠前。', '复杂实体选择用 Tree Select、Cascader 或 Autocomplete。', '提交区固定一个主按钮,危险动作二次确认。'],
|
||||
components: ['form', 'form-item', 'input', 'input-number', 'select', 'tree-select', 'cascader', 'date-picker', 'upload', 'button', 'popconfirm'],
|
||||
},
|
||||
{
|
||||
title: '流程页',
|
||||
description: '用于审批、发运、锁库、导入、结算等有明确进度的操作。',
|
||||
density: '引导密度',
|
||||
rules: ['当前步骤用 Steps,不用 Timeline。', '历史记录和系统事件用 Timeline。', '保存结果用 Message,流程终点才用 Result。'],
|
||||
components: ['steps', 'alert', 'progress', 'skeleton', 'message', 'result', 'timeline', 'dialog'],
|
||||
},
|
||||
].map((blueprint) => ({
|
||||
...blueprint,
|
||||
components: blueprint.components
|
||||
.map((slug) => erpComponentDocs.find((component) => component.slug === slug))
|
||||
.filter(Boolean),
|
||||
}))
|
||||
@@ -0,0 +1,316 @@
|
||||
import {
|
||||
CircleCheck,
|
||||
EditPen,
|
||||
Files,
|
||||
Goods,
|
||||
Grid,
|
||||
HomeFilled,
|
||||
Money,
|
||||
Tickets,
|
||||
Tools,
|
||||
Van,
|
||||
} from '@element-plus/icons-vue'
|
||||
|
||||
export const sections = [
|
||||
{ id: 'foundations', label: '基础规范', icon: Grid },
|
||||
{ id: 'catalog', label: '全组件目录', icon: Files },
|
||||
{ id: 'metrics', label: '指标卡片', icon: Tickets },
|
||||
{ id: 'buttons', label: '按钮与动作', icon: Tools },
|
||||
{ id: 'forms', label: '表单控件', icon: EditPen },
|
||||
{ id: 'table', label: '数据表格', icon: Tickets },
|
||||
{ id: 'status', label: '状态与反馈', icon: CircleCheck },
|
||||
{ id: 'navigation', label: '导航与容器', icon: HomeFilled },
|
||||
]
|
||||
|
||||
export const docPages = sections.map((section) => ({
|
||||
...section,
|
||||
title: {
|
||||
foundations: '基础规范与 Token',
|
||||
catalog: 'Element Plus 全组件目录',
|
||||
metrics: 'KPI 指标卡组件',
|
||||
buttons: '按钮与动作层级',
|
||||
forms: '表单控件与查询条件',
|
||||
table: '数据表格与分页',
|
||||
status: '状态、反馈与审计线',
|
||||
navigation: '菜单、顶部导航与面包屑',
|
||||
}[section.id],
|
||||
description: {
|
||||
foundations: '统一颜色、字号、间距、圆角和状态色,所有 OFBiz 新前端页面从这里继承。',
|
||||
catalog: '对照本地 Element Plus 包导出的完整组件、子组件、服务和指令。',
|
||||
metrics: '业务指标卡按大、小、最小三种密度复用,不再使用左侧粗色条。',
|
||||
buttons: '定义主动作、辅助动作、危险动作和批量动作的视觉层级。',
|
||||
forms: '覆盖查询条件、录入控件、选择器、日期范围和高级条件。',
|
||||
table: '定义 ERP 高密度表格、状态列、金额列、风险列和分页组合。',
|
||||
status: '覆盖标签、警告、进度、徽标、时间线等反馈模式。',
|
||||
navigation: '补齐左侧菜单、顶部菜单、面包屑、页面头和详情抽屉入口。',
|
||||
}[section.id],
|
||||
}))
|
||||
|
||||
export const metricCards = [
|
||||
{ label: '待处理订单', value: '128', delta: '+12.4%', tone: 'primary', icon: Tickets },
|
||||
{ label: '逾期应收', value: '¥842k', delta: '-3.1%', tone: 'danger', icon: Money },
|
||||
{ label: '库存预警', value: '36', delta: '+8', tone: 'warning', icon: Goods },
|
||||
{ label: '今日发运', value: '214', delta: '+21.8%', tone: 'success', icon: Van },
|
||||
]
|
||||
|
||||
export const metricCardVariants = [
|
||||
{ id: 'large', label: '大卡片 / 安静底色', className: 'erp-metric-card--large' },
|
||||
{ id: 'small', label: '小卡片 / 细边框', className: 'erp-metric-card--small' },
|
||||
{ id: 'mini', label: '最小卡片 / 紧凑列表', className: 'erp-metric-card--mini' },
|
||||
]
|
||||
|
||||
export const orderRows = [
|
||||
{
|
||||
orderId: 'SO-10642',
|
||||
customer: '上海辰光零售',
|
||||
status: '已审批',
|
||||
statusType: 'success',
|
||||
amount: '¥128,420.00',
|
||||
warehouse: '华东中心仓',
|
||||
owner: 'Lina Chen',
|
||||
due: '2026-06-08',
|
||||
risk: '低',
|
||||
},
|
||||
{
|
||||
orderId: 'SO-10643',
|
||||
customer: '杭州沐野商贸',
|
||||
status: '信用检查',
|
||||
statusType: 'warning',
|
||||
amount: '¥42,018.50',
|
||||
warehouse: '华东中心仓',
|
||||
owner: 'Ming Li',
|
||||
due: '2026-06-09',
|
||||
risk: '中',
|
||||
},
|
||||
{
|
||||
orderId: 'PO-77218',
|
||||
customer: '深圳北仓供应',
|
||||
status: '等待收货',
|
||||
statusType: 'primary',
|
||||
amount: '¥315,900.00',
|
||||
warehouse: '华南备货仓',
|
||||
owner: 'Doris Xu',
|
||||
due: '2026-06-10',
|
||||
risk: '低',
|
||||
},
|
||||
{
|
||||
orderId: 'INV-44019',
|
||||
customer: '南京泽同制造',
|
||||
status: '逾期',
|
||||
statusType: 'danger',
|
||||
amount: '¥73,600.00',
|
||||
warehouse: '财务共享中心',
|
||||
owner: 'Owen Zhang',
|
||||
due: '2026-06-03',
|
||||
risk: '高',
|
||||
},
|
||||
]
|
||||
|
||||
export const timelineItems = [
|
||||
{ timestamp: '09:20', content: '销售订单完成审批', type: 'success' },
|
||||
{ timestamp: '10:05', content: '库存锁定 18 件', type: 'primary' },
|
||||
{ timestamp: '10:44', content: '触发信用额度复核', type: 'warning' },
|
||||
]
|
||||
|
||||
export const componentInventoryMeta = {
|
||||
packageName: 'element-plus',
|
||||
packageVersion: '2.11.8',
|
||||
componentDirectories: 122,
|
||||
publicElExports: 123,
|
||||
directiveDirectories: 4,
|
||||
}
|
||||
|
||||
export const componentCatalog = [
|
||||
{
|
||||
group: '基础与全局',
|
||||
description: '基础动作、排版、全局配置、滚动、固定区域和水印。',
|
||||
items: [
|
||||
['Button', '高'],
|
||||
['Button Group', '高'],
|
||||
['Icon', '高'],
|
||||
['Link', '中'],
|
||||
['Text', '中'],
|
||||
['Config Provider', '高'],
|
||||
['Space', '高'],
|
||||
['Divider', '中'],
|
||||
['Scrollbar', '中'],
|
||||
['Affix', '中'],
|
||||
['Backtop', '低'],
|
||||
['Watermark', '中'],
|
||||
],
|
||||
},
|
||||
{
|
||||
group: '布局与容器',
|
||||
description: '页面骨架、栅格、分割面板、卡片和折叠容器。',
|
||||
items: [
|
||||
['Container', '高'],
|
||||
['Header', '中'],
|
||||
['Aside', '中'],
|
||||
['Main', '中'],
|
||||
['Footer', '低'],
|
||||
['Row', '高'],
|
||||
['Col', '高'],
|
||||
['Splitter', '高'],
|
||||
['Splitter Panel', '高'],
|
||||
['Card', '中'],
|
||||
['Collapse', '中'],
|
||||
['Collapse Item', '中'],
|
||||
],
|
||||
},
|
||||
{
|
||||
group: '表单输入',
|
||||
description: '查询条件、录入、选择、日期、上传和复杂实体选择。',
|
||||
items: [
|
||||
['Form', '高'],
|
||||
['Form Item', '高'],
|
||||
['Input', '高'],
|
||||
['Input Number', '高'],
|
||||
['Input Tag', '中'],
|
||||
['Input OTP', '低'],
|
||||
['Autocomplete', '高'],
|
||||
['Mention', '中'],
|
||||
['Select', '高'],
|
||||
['Select V2', '高'],
|
||||
['Option', '中'],
|
||||
['Option Group', '中'],
|
||||
['Cascader', '高'],
|
||||
['Cascader Panel', '中'],
|
||||
['Tree Select', '高'],
|
||||
['Checkbox', '高'],
|
||||
['Checkbox Button', '中'],
|
||||
['Checkbox Group', '高'],
|
||||
['Radio', '中'],
|
||||
['Radio Button', '中'],
|
||||
['Radio Group', '中'],
|
||||
['Switch', '中'],
|
||||
['Slider', '低'],
|
||||
['Rate', '低'],
|
||||
['Color Picker', '低'],
|
||||
['Color Picker Panel', '低'],
|
||||
['Date Picker', '高'],
|
||||
['Date Picker Panel', '中'],
|
||||
['Time Picker', '中'],
|
||||
['Time Select', '中'],
|
||||
['Transfer', '中'],
|
||||
['Upload', '高'],
|
||||
],
|
||||
},
|
||||
{
|
||||
group: '数据展示',
|
||||
description: 'ERP 的列表、详情、树、指标、图片和空状态。',
|
||||
items: [
|
||||
['Table', '高'],
|
||||
['Table Column', '高'],
|
||||
['Table V2', '高'],
|
||||
['Pagination', '高'],
|
||||
['Tag', '高'],
|
||||
['Check Tag', '中'],
|
||||
['Badge', '高'],
|
||||
['Descriptions', '高'],
|
||||
['Descriptions Item', '高'],
|
||||
['Tree', '高'],
|
||||
['Tree V2', '高'],
|
||||
['Statistic', '中'],
|
||||
['Countdown', '低'],
|
||||
['Avatar', '中'],
|
||||
['Avatar Group', '低'],
|
||||
['Calendar', '中'],
|
||||
['Image', '中'],
|
||||
['Image Viewer', '中'],
|
||||
['Carousel', '低'],
|
||||
['Carousel Item', '低'],
|
||||
['Empty', '高'],
|
||||
['Skeleton', '中'],
|
||||
['Skeleton Item', '中'],
|
||||
['Progress', '中'],
|
||||
['Result', '中'],
|
||||
],
|
||||
},
|
||||
{
|
||||
group: '导航',
|
||||
description: '模块切换、业务路径、标签页和页面标题。',
|
||||
items: [
|
||||
['Menu', '高'],
|
||||
['Menu Item', '高'],
|
||||
['Menu Item Group', '中'],
|
||||
['Sub Menu', '高'],
|
||||
['Tabs', '高'],
|
||||
['Tab Pane', '高'],
|
||||
['Breadcrumb', '高'],
|
||||
['Breadcrumb Item', '高'],
|
||||
['Dropdown', '高'],
|
||||
['Dropdown Menu', '高'],
|
||||
['Dropdown Item', '高'],
|
||||
['Page Header', '中'],
|
||||
['Steps', '高'],
|
||||
['Step', '中'],
|
||||
['Timeline', '高'],
|
||||
['Timeline Item', '中'],
|
||||
['Anchor', '中'],
|
||||
['Anchor Link', '中'],
|
||||
['Segmented', '高'],
|
||||
],
|
||||
},
|
||||
{
|
||||
group: '反馈与覆盖层',
|
||||
description: '保存结果、校验、确认、通知和异常展示。',
|
||||
items: [
|
||||
['Alert', '高'],
|
||||
['Message', '高'],
|
||||
['Message Box', '高'],
|
||||
['Notification', '中'],
|
||||
['Loading', '高'],
|
||||
['Popconfirm', '高'],
|
||||
['Tooltip', '高'],
|
||||
['Popover', '中'],
|
||||
['Dialog', '高'],
|
||||
['Drawer', '高'],
|
||||
['Popper', '低'],
|
||||
['Popper Arrow', '低'],
|
||||
['Popper Content', '低'],
|
||||
['Popper Trigger', '低'],
|
||||
['Overlay', '低'],
|
||||
['Tour', '低'],
|
||||
['Tour Step', '低'],
|
||||
],
|
||||
},
|
||||
{
|
||||
group: '虚拟化与内部基础',
|
||||
description: '多数不直接用于业务页,但会影响性能组件、组合组件和交互行为。',
|
||||
items: [
|
||||
['Auto Resizer', '高'],
|
||||
['Infinite Scroll', '中'],
|
||||
['Collapse Transition', '低'],
|
||||
['Focus Trap', '低'],
|
||||
['Virtual List', '中'],
|
||||
['Roving Focus Group', '低'],
|
||||
['Collection', '低'],
|
||||
['Slot', '低'],
|
||||
['Base', '低'],
|
||||
],
|
||||
},
|
||||
{
|
||||
group: '指令与服务导出',
|
||||
description: '来自本地 directives 目录和公开 El* 导出的行为层能力。',
|
||||
items: [
|
||||
['Click Outside', '中'],
|
||||
['Mousewheel', '低'],
|
||||
['Repeat Click', '低'],
|
||||
['Trap Focus', '低'],
|
||||
['Loading Directive', '高'],
|
||||
['Loading Service', '高'],
|
||||
['Popover Directive', '中'],
|
||||
['Message Service', '高'],
|
||||
['MessageBox Service', '高'],
|
||||
['Notification Service', '中'],
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export function getRiskClass(risk) {
|
||||
return {
|
||||
'risk-low': risk === '低',
|
||||
'risk-mid': risk === '中',
|
||||
'risk-high': risk === '高',
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { createApp } from 'vue'
|
||||
import ElementPlus from 'element-plus'
|
||||
import './styles/erp-ui.css'
|
||||
import './styles/app.css'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).use(ElementPlus).mount('#app')
|
||||
@@ -0,0 +1,23 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
min-width: 1180px;
|
||||
background: var(--erp-color-surface-muted);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
color: var(--erp-color-text);
|
||||
font-family: var(--erp-font-family);
|
||||
font-size: var(--erp-font-size-md);
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
font-family: inherit;
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
:root {
|
||||
--el-color-primary: var(--erp-color-primary);
|
||||
--el-color-primary-light-3: #6f96ef;
|
||||
--el-color-primary-light-5: #9bb7f4;
|
||||
--el-color-primary-light-7: #c9d8fa;
|
||||
--el-color-primary-light-8: #dce6fc;
|
||||
--el-color-primary-light-9: var(--erp-color-primary-soft);
|
||||
--el-color-primary-dark-2: var(--erp-color-primary-strong);
|
||||
--el-color-success: var(--erp-color-success);
|
||||
--el-color-success-light-9: var(--erp-color-success-soft);
|
||||
--el-color-warning: var(--erp-color-warning);
|
||||
--el-color-warning-light-9: var(--erp-color-warning-soft);
|
||||
--el-color-danger: var(--erp-color-danger);
|
||||
--el-color-danger-light-9: var(--erp-color-danger-soft);
|
||||
--el-color-error: var(--erp-color-danger);
|
||||
--el-color-error-light-9: var(--erp-color-danger-soft);
|
||||
--el-color-info: var(--erp-color-info);
|
||||
--el-border-radius-base: var(--erp-radius-sm);
|
||||
--el-font-family: var(--erp-font-family);
|
||||
--el-text-color-primary: var(--erp-color-text);
|
||||
--el-text-color-regular: var(--erp-color-text-muted);
|
||||
--el-text-color-secondary: var(--erp-color-text-subtle);
|
||||
--el-border-color: var(--erp-color-border);
|
||||
--el-border-color-light: var(--erp-color-border-soft);
|
||||
--el-fill-color-light: var(--erp-color-surface-muted);
|
||||
--el-bg-color-page: var(--erp-color-surface-muted);
|
||||
}
|
||||
|
||||
.el-button {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.el-button--primary {
|
||||
--el-button-bg-color: var(--erp-color-primary);
|
||||
--el-button-border-color: var(--erp-color-primary);
|
||||
--el-button-hover-bg-color: var(--erp-color-primary-strong);
|
||||
--el-button-hover-border-color: var(--erp-color-primary-strong);
|
||||
}
|
||||
|
||||
.el-button--success {
|
||||
--el-button-bg-color: var(--erp-color-success);
|
||||
--el-button-border-color: var(--erp-color-success);
|
||||
}
|
||||
|
||||
.el-button--warning {
|
||||
--el-button-bg-color: var(--erp-color-warning);
|
||||
--el-button-border-color: var(--erp-color-warning);
|
||||
}
|
||||
|
||||
.el-button--danger {
|
||||
--el-button-bg-color: var(--erp-color-danger);
|
||||
--el-button-border-color: var(--erp-color-danger);
|
||||
}
|
||||
|
||||
.el-input__wrapper,
|
||||
.el-select__wrapper,
|
||||
.el-textarea__inner {
|
||||
border-radius: var(--erp-radius-sm);
|
||||
box-shadow: 0 0 0 1px var(--erp-color-border) inset;
|
||||
}
|
||||
|
||||
.el-input__wrapper:hover,
|
||||
.el-select__wrapper:hover,
|
||||
.el-textarea__inner:hover {
|
||||
box-shadow: 0 0 0 1px #bfc8d8 inset;
|
||||
}
|
||||
|
||||
.el-tag {
|
||||
border-radius: var(--erp-radius-xs);
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.el-tag--success {
|
||||
--el-tag-bg-color: var(--erp-color-success-soft);
|
||||
--el-tag-border-color: #b8decf;
|
||||
--el-tag-text-color: var(--erp-color-success);
|
||||
}
|
||||
|
||||
.el-tag--warning {
|
||||
--el-tag-bg-color: var(--erp-color-warning-soft);
|
||||
--el-tag-border-color: #f2d39b;
|
||||
--el-tag-text-color: var(--erp-color-warning);
|
||||
}
|
||||
|
||||
.el-tag--danger {
|
||||
--el-tag-bg-color: var(--erp-color-danger-soft);
|
||||
--el-tag-border-color: #f1bbbb;
|
||||
--el-tag-text-color: var(--erp-color-danger);
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.el-menu-item {
|
||||
height: 38px;
|
||||
border-radius: var(--erp-radius-sm);
|
||||
margin: 2px 8px;
|
||||
color: #465066;
|
||||
}
|
||||
|
||||
.el-menu-item.is-active {
|
||||
color: var(--erp-color-primary);
|
||||
background: var(--erp-color-primary-soft);
|
||||
}
|
||||
|
||||
.el-tabs__item {
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.el-descriptions__label {
|
||||
color: #566176;
|
||||
font-weight: 650;
|
||||
}
|
||||
@@ -0,0 +1,481 @@
|
||||
.erp-form {
|
||||
display: grid;
|
||||
gap: var(--erp-space-4);
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: var(--erp-space-3);
|
||||
}
|
||||
|
||||
.metrics-grid--mini {
|
||||
gap: var(--erp-space-2);
|
||||
}
|
||||
|
||||
.erp-metric-card {
|
||||
--erp-metric-color: var(--erp-color-primary);
|
||||
--erp-metric-soft: var(--erp-color-primary-soft);
|
||||
position: relative;
|
||||
min-height: 104px;
|
||||
padding: var(--erp-space-4);
|
||||
background: var(--erp-color-surface);
|
||||
border: 1px solid var(--erp-color-border-soft);
|
||||
border-radius: var(--erp-radius-md);
|
||||
}
|
||||
|
||||
.erp-metric-card.is-success {
|
||||
--erp-metric-color: var(--erp-color-success);
|
||||
--erp-metric-soft: var(--erp-color-success-soft);
|
||||
}
|
||||
|
||||
.erp-metric-card.is-warning {
|
||||
--erp-metric-color: var(--erp-color-warning);
|
||||
--erp-metric-soft: var(--erp-color-warning-soft);
|
||||
}
|
||||
|
||||
.erp-metric-card.is-danger {
|
||||
--erp-metric-color: var(--erp-color-danger);
|
||||
--erp-metric-soft: var(--erp-color-danger-soft);
|
||||
}
|
||||
|
||||
.erp-metric-card__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--erp-space-3);
|
||||
margin-bottom: var(--erp-space-3);
|
||||
}
|
||||
|
||||
.erp-metric-card__head span {
|
||||
color: var(--erp-color-text-muted);
|
||||
font-size: var(--erp-font-size-xs);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.erp-metric-card__head .el-icon {
|
||||
color: var(--erp-metric-color);
|
||||
}
|
||||
|
||||
.erp-metric-card strong {
|
||||
display: block;
|
||||
margin-bottom: var(--erp-space-1);
|
||||
color: var(--erp-color-text);
|
||||
font-size: 25px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.erp-metric-card small {
|
||||
color: var(--erp-color-text-muted);
|
||||
font-size: var(--erp-font-size-xs);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.erp-metric-card--small,
|
||||
.erp-metric-card--outline {
|
||||
border-color: color-mix(in srgb, var(--erp-metric-color) 44%, var(--erp-color-border-soft));
|
||||
box-shadow: 0 1px 0 rgba(29, 36, 51, 0.03);
|
||||
}
|
||||
|
||||
.erp-metric-card--large,
|
||||
.erp-metric-card--quiet {
|
||||
background: linear-gradient(180deg, var(--erp-metric-soft), #ffffff 66%);
|
||||
border-color: color-mix(in srgb, var(--erp-metric-color) 18%, var(--erp-color-border-soft));
|
||||
}
|
||||
|
||||
.erp-metric-card--mini,
|
||||
.erp-metric-card--compact {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-template-areas:
|
||||
"head value"
|
||||
"delta value";
|
||||
min-height: 74px;
|
||||
align-items: center;
|
||||
padding: var(--erp-space-3);
|
||||
border-color: var(--erp-color-border-soft);
|
||||
}
|
||||
|
||||
.erp-metric-card--mini .erp-metric-card__head,
|
||||
.erp-metric-card--compact .erp-metric-card__head {
|
||||
grid-area: head;
|
||||
justify-content: flex-start;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.erp-metric-card--mini strong,
|
||||
.erp-metric-card--compact strong {
|
||||
grid-area: value;
|
||||
margin-bottom: 0;
|
||||
font-size: 22px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.erp-metric-card--mini small,
|
||||
.erp-metric-card--compact small {
|
||||
grid-area: delta;
|
||||
color: var(--erp-metric-color);
|
||||
}
|
||||
|
||||
.erp-table {
|
||||
--erp-table-header-bg-color: #f2f5fa;
|
||||
--erp-table-header-text-color: #344054;
|
||||
--erp-table-row-hover-bg-color: #f5f8ff;
|
||||
--erp-table-border-color: var(--erp-color-border-soft);
|
||||
border: 1px solid var(--erp-color-border-soft);
|
||||
border-radius: var(--erp-radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.erp-table .el-table__cell {
|
||||
padding: 7px 0;
|
||||
}
|
||||
|
||||
.erp-table th.el-table__cell {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.erp-virtual-table {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--erp-color-border-soft);
|
||||
border-radius: var(--erp-radius-md);
|
||||
}
|
||||
|
||||
.erp-dense-pagination {
|
||||
--el-pagination-font-size: var(--erp-font-size-sm);
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.erp-field-control,
|
||||
.erp-date-range,
|
||||
.erp-entity-lookup,
|
||||
.erp-tree-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.erp-choice-group {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--erp-space-2);
|
||||
}
|
||||
|
||||
.erp-side-menu {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.erp-side-menu .el-menu-item,
|
||||
.erp-side-menu .el-sub-menu__title {
|
||||
height: 38px;
|
||||
color: #465066;
|
||||
font-size: var(--erp-font-size-sm);
|
||||
}
|
||||
|
||||
.erp-side-menu .el-menu-item.is-active {
|
||||
color: var(--erp-color-primary);
|
||||
background: var(--erp-color-primary-soft);
|
||||
}
|
||||
|
||||
.erp-side-menu .el-sub-menu .el-menu-item {
|
||||
min-width: 0;
|
||||
padding-left: 44px;
|
||||
}
|
||||
|
||||
.erp-breadcrumb {
|
||||
font-size: var(--erp-font-size-sm);
|
||||
}
|
||||
|
||||
.erp-breadcrumb .el-breadcrumb__inner {
|
||||
color: var(--erp-color-text-muted);
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.erp-breadcrumb .el-breadcrumb__item:last-child .el-breadcrumb__inner {
|
||||
color: var(--erp-color-text);
|
||||
}
|
||||
|
||||
.erp-page-header {
|
||||
padding-bottom: var(--erp-space-3);
|
||||
border-bottom: 1px solid var(--erp-color-border-soft);
|
||||
}
|
||||
|
||||
.erp-tabs .el-tabs__header {
|
||||
margin-bottom: var(--erp-space-3);
|
||||
}
|
||||
|
||||
.erp-tabs .el-tabs__item {
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.erp-action-button,
|
||||
.erp-action-dropdown .el-button {
|
||||
min-width: 86px;
|
||||
}
|
||||
|
||||
.erp-status-tag,
|
||||
.erp-status-badge {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.erp-status-alert {
|
||||
border-radius: var(--erp-radius-sm);
|
||||
}
|
||||
|
||||
.erp-detail-descriptions .el-descriptions__cell {
|
||||
padding: 9px 12px;
|
||||
}
|
||||
|
||||
.erp-detail-descriptions .el-descriptions__label {
|
||||
width: 96px;
|
||||
color: #566176;
|
||||
font-weight: 700;
|
||||
background: #f5f7fb;
|
||||
}
|
||||
|
||||
.erp-entity-card {
|
||||
min-height: 112px;
|
||||
padding: var(--erp-space-4);
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--erp-color-border-soft);
|
||||
border-radius: var(--erp-radius-md);
|
||||
box-shadow: var(--erp-shadow-sm);
|
||||
}
|
||||
|
||||
.erp-entity-card header {
|
||||
margin-bottom: var(--erp-space-2);
|
||||
color: var(--erp-color-text-muted);
|
||||
font-size: var(--erp-font-size-xs);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.erp-entity-card strong {
|
||||
display: block;
|
||||
color: var(--erp-color-text);
|
||||
font-size: var(--erp-font-size-lg);
|
||||
}
|
||||
|
||||
.erp-entity-card p {
|
||||
margin: var(--erp-space-2) 0 0;
|
||||
color: var(--erp-color-text-muted);
|
||||
}
|
||||
|
||||
.erp-tree,
|
||||
.erp-tree-select {
|
||||
--el-tree-node-hover-bg-color: var(--erp-color-surface-muted);
|
||||
}
|
||||
|
||||
.erp-upload .el-upload-dragger {
|
||||
padding: var(--erp-space-5);
|
||||
border-color: var(--erp-color-border);
|
||||
border-radius: var(--erp-radius-md);
|
||||
}
|
||||
|
||||
.erp-empty-state {
|
||||
padding: var(--erp-space-5);
|
||||
background: #ffffff;
|
||||
border: 1px dashed var(--erp-color-border);
|
||||
border-radius: var(--erp-radius-md);
|
||||
}
|
||||
|
||||
.erp-progress {
|
||||
--el-color-primary: var(--erp-color-primary);
|
||||
}
|
||||
|
||||
.erp-skeleton {
|
||||
padding: var(--erp-space-4);
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--erp-color-border-soft);
|
||||
border-radius: var(--erp-radius-md);
|
||||
}
|
||||
|
||||
.erp-calendar {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--erp-color-border-soft);
|
||||
border-radius: var(--erp-radius-md);
|
||||
}
|
||||
|
||||
.erp-calendar .el-calendar__body {
|
||||
padding: var(--erp-space-3);
|
||||
}
|
||||
|
||||
.erp-calendar .el-calendar-table .el-calendar-day {
|
||||
min-height: 44px;
|
||||
height: 44px;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.erp-timeline {
|
||||
padding: var(--erp-space-2) 0 0;
|
||||
}
|
||||
|
||||
.erp-steps {
|
||||
padding: var(--erp-space-4);
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--erp-color-border-soft);
|
||||
border-radius: var(--erp-radius-md);
|
||||
}
|
||||
|
||||
.erp-collapse {
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--erp-color-border-soft);
|
||||
border-radius: var(--erp-radius-md);
|
||||
}
|
||||
|
||||
.erp-collapse .el-collapse-item__header,
|
||||
.erp-collapse .el-collapse-item__content {
|
||||
padding-right: var(--erp-space-4);
|
||||
padding-left: var(--erp-space-4);
|
||||
}
|
||||
|
||||
.erp-media-preview {
|
||||
width: 220px;
|
||||
height: 132px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--erp-color-border-soft);
|
||||
border-radius: var(--erp-radius-md);
|
||||
}
|
||||
|
||||
.erp-carousel {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--erp-color-border-soft);
|
||||
border-radius: var(--erp-radius-md);
|
||||
}
|
||||
|
||||
.erp-inline-text,
|
||||
.erp-inline-link {
|
||||
font-size: var(--erp-font-size-sm);
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.erp-space {
|
||||
padding: var(--erp-space-3);
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--erp-color-border-soft);
|
||||
border-radius: var(--erp-radius-md);
|
||||
}
|
||||
|
||||
.erp-statistic {
|
||||
min-width: 150px;
|
||||
padding: var(--erp-space-4);
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--erp-color-border-soft);
|
||||
border-radius: var(--erp-radius-md);
|
||||
}
|
||||
|
||||
.erp-switch,
|
||||
.erp-slider,
|
||||
.erp-rate,
|
||||
.erp-color-picker {
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
.erp-transfer {
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
.erp-dialog,
|
||||
.erp-drawer,
|
||||
.erp-popconfirm,
|
||||
.erp-tooltip {
|
||||
font-size: var(--erp-font-size-sm);
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.15fr 0.85fr 1.2fr 0.85fr;
|
||||
gap: var(--erp-space-4);
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.form-grid.secondary {
|
||||
grid-template-columns: 1fr 1fr 1.2fr;
|
||||
}
|
||||
|
||||
.table-search {
|
||||
width: 270px;
|
||||
}
|
||||
|
||||
.icon-cell {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: #465066;
|
||||
}
|
||||
|
||||
.risk-dot {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.risk-dot::before {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
content: "";
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.risk-low::before {
|
||||
background: var(--erp-color-success);
|
||||
}
|
||||
|
||||
.risk-mid::before {
|
||||
background: var(--erp-color-warning);
|
||||
}
|
||||
|
||||
.risk-high::before {
|
||||
background: var(--erp-color-danger);
|
||||
}
|
||||
|
||||
.component-chip-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.component-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-height: 26px;
|
||||
padding: 4px 8px;
|
||||
color: #344054;
|
||||
font-size: var(--erp-font-size-xs);
|
||||
font-weight: 700;
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--erp-color-border-soft);
|
||||
border-radius: var(--erp-radius-xs);
|
||||
}
|
||||
|
||||
.component-chip small {
|
||||
display: inline-grid;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
place-items: center;
|
||||
padding: 0 4px;
|
||||
color: var(--erp-color-text-muted);
|
||||
font-size: 11px;
|
||||
background: var(--erp-color-surface-muted);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.component-chip.priority-高 {
|
||||
color: var(--erp-color-primary-strong);
|
||||
border-color: #bdd0fb;
|
||||
background: #f4f7ff;
|
||||
}
|
||||
|
||||
.component-chip.priority-高 small {
|
||||
color: #ffffff;
|
||||
background: var(--erp-color-primary);
|
||||
}
|
||||
|
||||
.component-chip.priority-中 {
|
||||
color: #525f76;
|
||||
}
|
||||
|
||||
.component-chip.priority-低 {
|
||||
color: #7d8798;
|
||||
background: #fafbfc;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@import 'element-plus/dist/index.css';
|
||||
@import './tokens.css';
|
||||
@import './base.css';
|
||||
@import './element-overrides.css';
|
||||
@import './erp-patterns.css';
|
||||
@@ -0,0 +1,41 @@
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--erp-color-primary: #2764e7;
|
||||
--erp-color-primary-strong: #1947aa;
|
||||
--erp-color-primary-soft: #e8efff;
|
||||
--erp-color-success: #16845b;
|
||||
--erp-color-success-soft: #e6f5ee;
|
||||
--erp-color-warning: #b86b00;
|
||||
--erp-color-warning-soft: #fff4df;
|
||||
--erp-color-danger: #c73535;
|
||||
--erp-color-danger-soft: #fdecec;
|
||||
--erp-color-info: #4f5d75;
|
||||
--erp-color-text: #1d2433;
|
||||
--erp-color-text-muted: #667085;
|
||||
--erp-color-text-subtle: #8a94a6;
|
||||
--erp-color-border: #d9dee8;
|
||||
--erp-color-border-soft: #e8ebf1;
|
||||
--erp-color-surface: #ffffff;
|
||||
--erp-color-surface-muted: #f7f8fb;
|
||||
--erp-color-surface-raised: #fbfcfe;
|
||||
--erp-shadow-sm: 0 1px 2px rgba(29, 36, 51, 0.06);
|
||||
--erp-shadow-md: 0 12px 24px rgba(29, 36, 51, 0.08);
|
||||
--erp-radius-xs: 4px;
|
||||
--erp-radius-sm: 6px;
|
||||
--erp-radius-md: 8px;
|
||||
--erp-space-1: 4px;
|
||||
--erp-space-2: 8px;
|
||||
--erp-space-3: 12px;
|
||||
--erp-space-4: 16px;
|
||||
--erp-space-5: 20px;
|
||||
--erp-space-6: 24px;
|
||||
--erp-space-8: 32px;
|
||||
--erp-font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--erp-font-size-xs: 12px;
|
||||
--erp-font-size-sm: 13px;
|
||||
--erp-font-size-md: 14px;
|
||||
--erp-font-size-lg: 18px;
|
||||
--erp-font-size-xl: 24px;
|
||||
--erp-line-height-tight: 1.25;
|
||||
--erp-line-height-base: 1.5;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
})
|
||||