SNAPSHOT W7 已部署稳定态 — 凯迪ERP+OA一体化平台 (MET 73.3%)
恢复点(restore point)。别人改崩后可 git reset --hard 回到此提交。 == 此快照内容 == - 后端 oa-backend: 734 控制器 / 711 实体 (Spring Boot 3.2.5 + SQLite, 端口8091) - 前端 modern-ui/app: Vue3+Vite, 约700页 (构建产物已在 oa-backend/src/main/resources/static) - 数据库 oa-backend/data/oa.db: 含全部演示数据 (强制入库, 6.6MB) - 交接文档 go.md + go-code-reference/endpoints/entities/database.md - 多代理建设脚本 .claude/wf-*.js == 状态 == - 对 凯迪科技ERP_20260507.xlsx 合规 MET ~73.3% (PARTIAL 75: 34可建+6种子/bug+35外部硬天花板) - 安全: 5轮红队+5轮复检, default-deny分级鉴权, 连续零可利用 - W3~W7 累计补完436缺口; W8末轮(40缺口)为半成品(源码树可编译但未集成) - 运行: cd oa-backend; java -jar build/libs/oa-backend-0.1.0.jar --server.port=8091; admin/123456 == 排除(gitignore, 可再生) == node_modules / oa-backend/build / .jdks / *.log / Backup-ERP-* / 弃用的OFBiz核心(只保留modern-ui) 完整文件夹备份见同目录 Backup-ERP-20260615-191517/ (含上述全部, 仅缺 node_modules) 时间戳: 20260615-191517 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,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,
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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')
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user