Skip to content

VOL Pro 前端开发指南 ​

适用:web.vite/(Vue 3 + vite + Element Plus + Vuex4)。 所有写法均经源码验证;标注 ⚠ 的是实战踩过的坑。官方文档站 http://doc.volcore.xyz(页面需加 .html 后缀)。 ⚠ 表格类文档路径在 /docs/web/table/,不在 /docs/table/(那是 VolTable 组件参数页)。


1. 目录结构与"生成物/手写区"边界(最重要的一节) ​

目录性质规则
src/views/**/{表名}.vue生成物重新生成会覆盖。只放运行时 patch(adjustMainOptions)与轻交互
src/views/**/{表名}/options.js生成物绝对不改。所有调整在 vue 的 onInit/onInited 里运行时改
src/extension/**/{表名}.jsx手写区校验/业务钩子/自定义组件全在这,生成器永不覆盖
src/router/viewGird.js手写静态路由每个页面一行 {path, name, paramater},无动态注册
src/components/basic/框架组件(ViewGrid/VolForm/VolTable/VolBox)改内核要三思

定制三分法(决定代码放哪):

  1. 业务校验/保存钩子 → extension/*.jsx(addBefore 的明细在 formData.detailData,是数组)
  2. 改列/表单/查询布局 → 页面 vue 里 reactive(viewOptions()) 解构后运行时 patch
  3. 自定义弹窗/选单组件 → extension 的 components: { modelBody: XxxVue }(挂编辑弹窗 body)

⚠ 白屏坑:extension jsx 的 this 是 ViewGrid 实例,但 setup 没暴露 editFormOptions/columns——jsx 里碰它们直接 TypeError → onBeforeMount 中断 → 整页空白无报错。改配置只能在页面 vue(能解构出引用)里做。

2. 生命周期与钩子顺序(ViewGrid.vue onBeforeMount 实测) ​

jsx.onInit → vue.onInit(props) → 框架初始化(initViewColumns/initDicKeys/getButtons) → jsx.onInited → vue.onInited
  • 在 vue onInit 里 columns.push({AuditStatus列}):来得及被框架初始化用到,但晚于 setup 阶段的同步判断(如 initReadonly)
  • 事件钩子(addBefore/modelOpenAfter 等)执行顺序:jsx 先、vue 后;删主表字段必须晚于 vue onInit 里 getFormOption 的访问
  • 70+ 个钩子总表:📄 /docs/view-grid/methods.html(searchBefore/addBefore/updateBefore/delBefore/auditBefore/modelOpenBefore/rowClick/spanMethod…)

3. 页面 vue 里最常用的 patch 写法 ​

js
// views/erp/erp/ERP_PurchaseIn.vue —— 模板可直接照抄的骨架
import { reactive, viewOptions, getColumns } from './ERP_PurchaseIn/options.js'
const { columns, editFormOptions, searchFormOptions, editFormFields, searchFormFields, table, detail } = reactive(viewOptions())

// vue3 写法:onInit 里拿 gridRef 后
const gridRef = useViewGrid()          // 框架 hook(生成的 vue 里有现成代码)
onInit(() => {
  // 快捷查询字段(生成器须先勾"查询行")
  gridRef.queryFields = ['ReceiptNo', 'PurchaseOrderNo']
  // 弹窗尺寸:视口 80%(框架对超 80% 宽高会强制压缩,直接按 80% 设)
  const vw = window.innerWidth * 0.8, vh = window.innerHeight * 0.8
  gridRef.boxOptions.width = Math.round(vw)
  gridRef.boxOptions.height = Math.round(vh)
  gridRef.detailOptions.height = Math.max(300, Math.round(vh - 320))   // 明细表撑满弹窗
  // 合计:明细列 detail.columns / 主列 columns 上配
  columns.forEach(x => { if (x.field == 'Amount') x.summary = true })  // 'avg' 求平均
})

Excel 式录入(2026-11-02 框架增强,VolTable.vue,升级需重做见记忆 project_framework_mods)——有编辑列的表格自动生效:

  • 从 Excel 粘贴多行多列:明细表上直接 Ctrl+V,TSV 按"可见列"从第一列对齐(readonly 列占位跳过);首格匹配列名视为表头自动丢弃;字典列按显示文本反查 key;数字/日期自动转类型
  • 复制行:gridRef.getTable().copyRows()(默认勾选行,支持多行);第 2 参传要清空的字段数组(主键/单据关联),清掉才按新增提交:copyRows(null, ['Id', 'PurchaseOrderDetailId'])
  • 列批量填充:gridRef.getTable().fillColumn(field, { mode: 'fixed'|'inc', value, step })——rows 默认勾选行(未勾选=全部行);inc=从 value 起每行 +step(替代 Excel 下拉填充)
  • 演示:采购入库明细表(ERP_PurchaseIn.vue 有"复制行/批量填充"按钮 + 填充对话框可抄)

⚠ colSize 是百分比宽度(一行基准 12/100 均可,生成器默认一行 5 个各 20)。删字段后每行会缺角,用 splice 整行重排:

js
const pick = (rows, field) => { let h; rows.forEach(r => { const f = r.find(x => x.field == field); if (f) h = f }); return h }
const row1 = ['ReceiptNo','SupplierId','WarehouseId','ArrivalDate'].map(f => { const c = pick(editFormOptions, f); if (c) c.colSize = 25; return c }).filter(Boolean)
editFormOptions.splice(0, editFormOptions.length, row1, row2)

4. 表单字段操作(⚠ 最隐蔽的坑) ​

js
const opt = gridRef.getFormOption('SupplierId')   // 编辑表单字段配置引用
opt.type = 'selectTable'; opt.url = 'api/...'; ...// 就地改,响应式生效
opt.readonly = true; opt.hidden = true             // 只读/隐藏

⚠ editFormFields 的隐藏字段必须先声明再赋值,否则(selectTable/自定义组件)回写的值不进提交数据:

js
if (editFormFields.Creator === undefined) editFormFields.Creator = ''
editFormFields.Creator = 'admin'   // 现在才会进 formData

表单其他能力:只读/默认值/必填 /docs/web/form/formReadonly.html、自定义校验 customValidator、字段联动 editSelectChange、实时计算 formCalc、三级明细 subdetails、分组 group

5. 选单(上下游单据引用)标准范式 ​

js
// 编辑弹窗内"选择采购订单",完整配置项(范例:views/erp/erp/ERP_InventoryIo/purchaseInSetup.js)
const opt = gridRef.getFormOption('字段名')
Object.assign(opt, {
  type: 'selectTable',
  url: 'api/ERP_PurchaseOrder/getOrderDetail',   // 返回 {rows, total}
  columns: [{ field:'OrderNo', title:'订单号', width:120 }, ...],
  onSelect: (rows) => { /* rows → 明细合并;计算一律后端做,前端只回填 */ },
  loadBefore: (param, cb) => { param.value = [{ name:'SupplierId', value: editFormFields.SupplierId }]; cb(param) },
  single: false, height: 400, selectWidth: 900, pagination: true
})
  • 计算放后端;返回结构直接 push 进 gridRef.getTable().rowData
  • 文档:/docs/web/general/selectDetailData.html(弹窗选单)、selectTable.html

6. 表格能力速查 ​

需求写法文档(/docs/web/table/)
合计/平均x.summary = true、numberLength、summaryFormattersummary.html
单元格合并<view-grid :spanMethod="fn">,返回 {rowspan,colspan},第二参是全页 rowsmerge.html
默认排序列上 sort/ordersort.html
表头筛选列配置 filterData = true(可搜索 + Excel 式多选面板,2026-10-31 已增强 VolTable:搜索框过滤候选 + 复选多选 + 确定/清空;候选值取自当前页数据、纯前端过滤只筛当前页;改动在框架文件 VolTable.vue,官方升级会被覆盖;V2 表格无此功能)headFilter.html
快捷查询gridRef.queryFields = [...]queryFields.html
自定义列渲染列 formatter 或 jsx renderformatter.html、customCell.html
行点击/样式rowClick、rowColor、tagColor同名
在线编辑editTable同名
大数据量VolTable V2 + 虚拟滚动/docs/web/general/tableV2.html
明细实时计算detailCalc同名

7. 自定义弹窗组件融入框架(多单合并选单实战总结) ​

  • 弹窗用全局注册的 vol-box(有 #footer 插槽),别用 el-dialog(风格脱节)
  • 表格 import @/components/basic/VolTable.vue::ck(多选)、:selectable、:column-index="false"、:pagination-hide、:load-key="false";本地数据必须 splice 就地更新,不能整体换数组引用(框架约定,源码注释明文)
  • 组件挂在 extension 的 components: { modelBody: ... } → 与页面通信走官方通道:
js
this.$emit('parentCall', ($grid) => {        // 框架回传 gridRef
  $grid.getTable().rowData.splice(...)       // 操作明细
  $grid.editFormFields.Xxx = value
})

⚠ parentCall 只在 extension 的 modelBody 组件里有效(ViewGrid 用 <component :is> 渲染并监听 @parentCall);放进页面 vue 的 <template #modelBody> 插槽会发错对象。两侧都要用时,用一个共享 ref 模块桥接(如 poPickerBus.js)。

  • 自定义按钮放 gridRef.detailOptions.buttons(与"新增行/删除行"同款渲染,审核态自动隐藏),别在 modelBody 里自造一排按钮

⚠ 跨页勾选池:表格数据整体替换会触发一次 selection-change 传空数组——在该事件里无条件清理勾选池会误删之前勾的行。用 switching 标志位挡掉换数据期间的事件,换完用 toggleRowSelection 手动恢复。框架的 reserveSelection 只为分页翻页设计(isPageLoad 标志),手动换 table-data 用不上。

8. 按钮权限与扩展按钮 ​

  • 工具栏按钮 = api/buttons.js 静态模板(Search/Add/Update/Delete/Audit/AntiAudit/Import/Export/Print…)∩ 用户权限(登录时 api/menu/getTreeMenu 下发,存 Vuex store.permission)
  • 按钮不显示排查链(由浅入深):① 菜单配置里该按钮权限勾了吗 → ② 角色授权里有吗(非 admin 用户看 Sys_RoleAuth)→ ③ 改过库的话后端菜单缓存刷新了吗(界面保存一次菜单或重启后端)→ ④ 重新登录了吗
  • 页面自定义按钮:vue 里 extend.buttons.view/box = [{ name, icon, value, onClick }],可设 Index 插入指定位置;接口级权限由后端 [ApiActionPermission] 把守
  • 审核按钮显示只看权限;AuditStatus 列影响的是审核弹窗/流程列展示

9. 请求、字典、缓存、打印 ​

js
proxy.http.post('api/xxx/yyy', param, true)      // 第3参: 是否显示 loading
proxy.http.get('api/xxx', {}, false)
  • 接口地址写死 src/api/http.js(开发 http://localhost:9100/);跨域走后端 CORS,不走 vite 代理(vite.config.ts 里全注释)
  • 字典:options 列配 dataKey(字典编号)+ dataType: 'number/string',自动绑定;手动刷新 initDicKeys
  • ⚠ 自定义接口传参遵守后端指南第 6 节(对象传参 + camelCase),裸标量必 415
  • 打印:hiprint 可视化模板存 Sys_PrintOptions.html;文档 /docs/print/(套打/连续打印需本地客户端)
  • excel 导入导出下载:/docs/web/general/http.html

10. 高频踩坑清单(一行一条,出问题先对表) ​

  1. options.js 是生成物,改了会被覆盖 → 全部用 onInit/onInited 运行时 patch
  2. jsx 里碰 editFormOptions/columns → 白屏;改配置去页面 vue
  3. editFormFields 未声明的字段赋值不进提交 → 先 if (x === undefined) x = ''
  4. colSize 是百分比不是像素
  5. parentCall 只在 extension modelBody 组件生效
  6. 换表格数据触发的空 selection-change 会清勾选 → switching 标志位
  7. 本地数据更新表格用 splice,别换引用
  8. 审核按钮不显示 → 权限链六层 + 后端菜单缓存(见第 8 节)
  9. 改了按钮/菜单权限必须重新登录(前端登录时拉取缓存)
  10. 按钮点击无反应先查浏览器 Network:401=token 过期、415=传参裸标量、"服务器处理异常"=后端异常看 backend.log

11. 官方文档索引(前端) ​

分区路径
ViewGrid 属性/方法/70+ 钩子/docs/view-grid/properties.html、methods.html、methods/xxx.html
通用(选单/弹窗/大表格/插槽/打开编辑)/docs/web/general/(selectTable、selectDetailData、box、slot、tableV2、openEdit、http…)
表格/docs/web/table/(summary、merge、formatter、queryFields、editTable…)
表单/docs/web/form/(formReadonly、customValidator、formCalc、subdetails…)
打印/docs/print/