外观
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) | 改内核要三思 |
定制三分法(决定代码放哪):
- 业务校验/保存钩子 →
extension/*.jsx(addBefore的明细在formData.detailData,是数组) - 改列/表单/查询布局 → 页面 vue 里
reactive(viewOptions())解构后运行时 patch - 自定义弹窗/选单组件 → 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 后;删主表字段必须晚于 vueonInit里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、summaryFormatter | summary.html |
| 单元格合并 | <view-grid :spanMethod="fn">,返回 {rowspan,colspan},第二参是全页 rows | merge.html |
| 默认排序 | 列上 sort/order | sort.html |
| 表头筛选 | 列配置 filterData = true(可搜索 + Excel 式多选面板,2026-10-31 已增强 VolTable:搜索框过滤候选 + 复选多选 + 确定/清空;候选值取自当前页数据、纯前端过滤只筛当前页;改动在框架文件 VolTable.vue,官方升级会被覆盖;V2 表格无此功能) | headFilter.html |
| 快捷查询 | gridRef.queryFields = [...] | queryFields.html |
| 自定义列渲染 | 列 formatter 或 jsx render | formatter.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下发,存 Vuexstore.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. 高频踩坑清单(一行一条,出问题先对表)
- options.js 是生成物,改了会被覆盖 → 全部用 onInit/onInited 运行时 patch
- jsx 里碰
editFormOptions/columns→ 白屏;改配置去页面 vue editFormFields未声明的字段赋值不进提交 → 先if (x === undefined) x = ''colSize是百分比不是像素parentCall只在 extension modelBody 组件生效- 换表格数据触发的空
selection-change会清勾选 →switching标志位 - 本地数据更新表格用
splice,别换引用 - 审核按钮不显示 → 权限链六层 + 后端菜单缓存(见第 8 节)
- 改了按钮/菜单权限必须重新登录(前端登录时拉取缓存)
- 按钮点击无反应先查浏览器 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/ |