1. 项目背景与需求分析
在基于Vue2的企业级后台管理系统开发中,物料参照组件是一个高频使用的功能模块。Ruoyi-UI作为一套成熟的前端解决方案,其内置组件库已经覆盖了大部分基础需求。但在实际业务场景中,我们经常遇到以下痛点:
- 原生弹窗组件功能单一,无法满足复杂表单与表格联动的需求
- 参照选择逻辑需要重复编写,不同业务模块间存在大量相似代码
- 大数据量下表格渲染性能不足,操作流畅度下降
- 多层级数据展示缺乏统一解决方案
Vxe-table作为一款功能强大的Vue表格组件,其配套的vxe-modal弹窗组件提供了丰富的API和扩展能力。本次封装正是基于vxe-modal的特性,结合Ruoyi-UI的设计规范,打造一个可复用的高级物料参照组件。
2. 技术选型与架构设计
2.1 核心依赖分析
javascript复制// package.json关键依赖
{
"dependencies": {
"vue": "^2.6.14",
"vxe-table": "3.x",
"element-ui": "^2.15.9",
"ruoyi-ui": "^1.0.0"
}
}
选择vxe-modal而非Element UI的Dialog组件主要基于以下考量:
- 内置虚拟滚动支持,万级数据流畅渲染
- 完善的表格集成方案,支持复杂表头与单元格自定义
- 丰富的API体系,便于功能扩展
- 与vxe-table的无缝配合,减少兼容性问题
2.2 组件架构设计
组件采用复合式设计模式,核心结构如下:
code复制MaterialReference
├── index.vue // 主入口
├── SearchPanel.vue // 搜索区
├── TablePanel.vue // 表格区
└── hooks
├── usePagination.js // 分页逻辑
└── useSelection.js // 选中逻辑
3. 核心功能实现
3.1 弹窗基础配置
javascript复制// 弹窗基础配置
this.modal = this.$vxe.modal({
title: '物料参照',
width: '80%',
height: '70%',
showFooter: true,
content: () => import('./index.vue'),
props: {
// 传入的配置参数
config: this.config
},
methods: {
confirm: this.handleConfirm
}
})
关键配置说明:
content使用动态导入实现按需加载props传递配置对象实现动态化methods暴露确认回调接口
3.2 高性能表格实现
vue复制<template>
<vxe-table
ref="xTable"
border
resizable
show-overflow
keep-source
:data="tableData"
:loading="loading"
:checkbox-config="{ highlight: true }"
:scroll-y="{ enabled: true, gt: 50 }"
@checkbox-change="handleSelectionChange"
>
<!-- 自定义列配置 -->
</vxe-table>
</template>
性能优化要点:
keep-source保持数据源引用避免重复渲染scroll-y启用虚拟滚动- 分页加载配合后端排序/过滤
3.3 多层级数据展示方案
对于树形物料数据,采用递归组件实现:
vue复制<template>
<vxe-table-column field="name" title="物料名称">
<template #default="{ row }">
<template v-if="row.children">
<i class="el-icon-folder"></i>
<span @click="toggleExpand(row)">{{ row.name }}</span>
<div v-show="row.expanded" class="children-table">
<child-table :data="row.children" />
</div>
</template>
<template v-else>
<i class="el-icon-document"></i>
{{ row.name }}
</template>
</template>
</vxe-table-column>
</template>
4. 与Ruoyi-UI的深度集成
4.1 样式规范统一
创建styles/mixin.scss:
scss复制@mixin ry-modal-theme {
.vxe-modal--wrapper {
.vxe-modal--box {
border-radius: $--border-radius-base;
box-shadow: $--box-shadow-light;
}
.vxe-modal--header {
background-color: $--color-primary;
color: #fff;
}
}
}
4.2 权限控制集成
javascript复制// 在created钩子中检查权限
created() {
if (!this.hasPermi(this.config.permission)) {
this.$modal.msgError('无访问权限')
this.$nextTick(() => this.modal.close())
}
}
4.3 全局注册方案
在main.js中注册为全局组件:
javascript复制import MaterialReference from '@/components/MaterialReference'
Vue.component('MaterialReference', MaterialReference)
// 或挂载到Vue原型
Vue.prototype.$materialReference = function(config) {
return new Promise(resolve => {
const instance = new Vue({
render: h => h(MaterialReference, {
props: { config },
on: { confirm: resolve }
})
})
instance.$mount()
document.body.appendChild(instance.$el)
})
}
5. 高级功能扩展
5.1 自定义搜索面板
通过插槽机制实现灵活配置:
vue复制<template>
<vxe-modal>
<template #search>
<el-form :model="searchForm" inline>
<el-form-item label="物料编码">
<el-input v-model="searchForm.code" />
</el-form-item>
<!-- 自定义表单项 -->
<slot name="search-extra" />
</el-form>
</template>
</vxe-modal>
</template>
5.2 批量操作支持
javascript复制// 批量导出功能实现
handleBatchExport() {
const { fullData } = this.$refs.xTable.getTableData()
this.$export.excel({
columns: this.exportColumns,
data: fullData,
filename: '物料数据'
})
}
5.3 历史记录功能
javascript复制// 使用localStorage存储选择历史
saveSelectionHistory(items) {
const history = JSON.parse(localStorage.getItem('materialHistory') || '[]')
const newHistory = [...new Set([...items, ...history])].slice(0, 10)
localStorage.setItem('materialHistory', JSON.stringify(newHistory))
}
6. 性能优化实践
6.1 虚拟滚动配置
javascript复制// 优化百万级数据渲染
this.tableConfig = {
scrollY: {
enabled: true,
oSize: 20, // 每次渲染的行数
gt: 100 // 超过100行启用虚拟滚动
},
scrollX: {
enabled: true,
gt: 20 // 超过20列启用横向虚拟滚动
}
}
6.2 数据分片加载
javascript复制async loadData(params) {
this.loading = true
try {
const { data } = await api.fetchMaterial({
...params,
pageSize: 200 // 每次加载200条
})
this.tableData = this.chunkData(data, 50) // 分片渲染
} finally {
this.loading = false
}
}
6.3 内存管理优化
javascript复制beforeDestroy() {
// 清除大对象引用
this.tableData = null
this.$refs.xTable.reloadData([])
}
7. 实际应用案例
7.1 采购订单场景
javascript复制this.$materialReference({
title: '采购物料选择',
columns: [
{ field: 'code', title: '物料编码' },
{ field: 'spec', title: '规格型号' }
],
filters: {
warehouse: 'A001'
}
}).then(selectedItems => {
this.form.materials = selectedItems
})
7.2 生产BOM维护
vue复制<material-reference
ref="materialRef"
:config="bomConfig"
@confirm="handleBomConfirm"
>
<template #search-extra>
<el-form-item label="工艺路线">
<el-select v-model="searchForm.route">
<el-option label="机加工" value="01" />
<el-option label="装配" value="02" />
</el-select>
</el-form-item>
</template>
</material-reference>
8. 常见问题解决方案
8.1 样式冲突处理
创建styles/override.scss:
scss复制/* 解决vxe-table与element-ui样式冲突 */
.vxe-table {
.el-checkbox__inner {
width: 16px;
height: 16px;
&::after {
top: 2px;
left: 5px;
}
}
}
8.2 多实例通信方案
使用EventBus处理多弹窗交互:
javascript复制// utils/eventBus.js
import Vue from 'vue'
export default new Vue()
// 组件内使用
eventBus.$on('material-selected', (data) => {
this.handleExternalSelection(data)
})
8.3 移动端适配策略
javascript复制// 根据设备动态调整弹窗大小
computed: {
modalSize() {
return this.$store.state.app.isMobile
? { width: '100%', height: '100%' }
: { width: '80%', height: '70%' }
}
}
9. 单元测试要点
9.1 基础功能测试用例
javascript复制describe('MaterialReference', () => {
it('should render correct columns', () => {
const wrapper = mount(Component, {
propsData: {
config: {
columns: [{ field: 'code', title: '编码' }]
}
}
})
expect(wrapper.find('.vxe-header--column').text()).toContain('编码')
})
})
9.2 性能测试方案
javascript复制test('should handle 10000 rows data', async () => {
const data = generateTestData(10000)
const wrapper = mount(Component)
wrapper.vm.loadData(data)
await wrapper.vm.$nextTick()
expect(wrapper.vm.$refs.xTable.getTableData().fullData.length).toBe(10000)
})
10. 扩展开发建议
10.1 对接工作流引擎
javascript复制// 与Activiti集成示例
async loadWorkflowMaterials(processDefinitionId) {
const { data } = await api.getProcessVariables(processDefinitionId)
this.tableData = data.materials.map(item => ({
...item,
_disabled: !item.available
}))
}
10.2 三维可视化集成
vue复制<template>
<div class="preview-container">
<vxe-table />
<model-preview
v-if="show3D"
:model="currentModel"
class="preview-viewer"
/>
</div>
</template>
10.3 离线模式支持
javascript复制// 使用IndexedDB存储离线数据
async initOfflineDB() {
this.db = new Dexie('MaterialDB')
this.db.version(1).stores({
materials: '++id,code,name'
})
await this.db.materials.bulkPut(offlineData)
}
在实现过程中有几个关键经验值得分享:1) vxe-modal的zIndex管理需要特别注意,在多弹窗场景下建议使用统一的zIndex管理器;2) 表格列宽自适应最好配合防抖函数使用,避免频繁重排导致的性能问题;3) 对于超大数据集,建议采用Web Worker进行后台数据处理
