## 注意事项 ### 不要被框架束缚思路 框架只是个代码生成器而已,前后端代码都在你手里,只要不影响其他模块,你想怎么实现怎么实现。 ### 设计相关 - 一个表单只能绑定一个流程,一个流程也只能绑定一个表单,虽然框架支持单流程带多表单,但是我们在代码中屏蔽了这部分设计,如果表单内容较多,建议以子流程、tab页、分区等方式合理显示。 - 一旦某表单绑定了流程,该表单就只能以绑定流程的方式使用(展示一个流程的纯表单部分无意义,用户一定是想和流程一起查看或者审批) - 流程草稿在单据列表里不会显示(草稿箱中可以找到),这是延续老系统的设计,不属于bug - 因为代码生成器的特殊性,建议在设计的时候尽可能完善,或者预留某些字段,隐藏起来以便需求调整,开发者需要评估重新生成代码与自行添加字段的工作量 ### 界面与用户体验 - 设计器支持响应式布局,因为设计器架构问题,并未默认打开,对于表单内字段,除了附件、多行文本框等占用宽度较大的组件外,都建议开启响应式布局 - 表单和表格需要合理调整字段宽度,响应式布局下需要使用定宽模式,一般情况下,字段的宽度取平均字长 + 2个汉字的宽度为宜,不要留太长的label,也要避免出现label换行 ## Q&A ### 为什么表单所有字段都成了必填 默认情况下,绑定流程后,新建节点的所有字段都被设置为必填,需要在流程的开始节点-表单设置中去掉非必填的项。 ### 如何在提交时也提交隐藏字段的值 修改Form.vue里的validate方法,将最后的values改为 ```javascript async function validate() { let values = []; try { values = await systemFormRef.value?.validate(); // 省略的代码 } finally { } return systemFormRef.value?.getFieldsValue(); } ``` 原理是validate方法在校验的同时,会返回**校验成功**字段的值,对于隐藏字段,因为跳过了校验,所以值也不会返回。而getFieldsValue方法会返回整个formModel。 ### 为什么我的页面绑定流程后也没有相关按钮 - 如果是可以重做,需要用**代码模版**(界面优先和数据优先都可以),而不是代码生成。 - 需要排查菜单接口有没有返回流程定义ID,即system/menu/tree接口,对应菜单项里必须有schemaId这项 ### 表单设计和代码模版有什么区别 代码模版生成的本地文件有二开支持,表单符合UI规范,表单设计可以在线发布,像低开一样,不用写代码,但是界面不符合UI规范。 两种方法表单设计器支持内容一致,也都支持流程,差别只是UI方面。建议在设计业务模块优先使用代码模版。 ### 表单的封装层次是什么 ``` formCreatePage/approveFlowPage/createFlow(二开封装页面,提供标题栏和路由功能) ↓ FormInformation(最外层表单封装,用于区分是低开模式还是源码模式) ↓ Form.vue(业务表单,生成的代码,低开没有这层) ↓ SimpleForm/SimpleFormSetup(主体表单封装) ↓ SimpleFormItem(表单字段) ↓ 表单组件 / SubFormV2(嵌套明细表) ``` ### 表单提供了什么封装函数 在生成的Form.vue中,可以通过systemFormRef.value调用对外提供的函数,这些函数在SimpleForm.vue里可以找到,可以参考formApi或者defineExpose提供的函数,有用的函数如: - setFieldsValue 设定字段的值 - getFieldsValue 获取表单的值,也就是formModel的非响应式版本 - validateFields 手动触发校验 ### 如何定义onChange/blur事件 所有的事件都在字段的events中,如change、blur,没有on,主表单的函数定义为 ```javascript function change(schema, formModel, formApi, { formData }) { } ``` 明细表的函数参数定义为 ```javascript function change({ column, row, rowIndex, formModel }) { } ``` 这两个定义的formModel都可以取到全部表单的值,也可以赋值做修改。 ### 如何在Tab页中打开表单/流程 如果你需要自己编程实现Tab页跳转,或者升级旧版框架的页面,可以参考下面步骤: ```typescript const { currentRoute } = useRouter(); // 获取表单的绑定的流程 schemaId通过菜单中的路由参数给出 const schemaIdComputedRef = computed(() => currentRoute.value.meta.schemaId as string); //处理新增逻辑 function handleAdd(){ if (schemaIdComputedRef.value) { router.push({ path: '/flow/' + schemaIdComputedRef.value + '/0/createFlow' }); } else { router.push({ path: '/form/bizoutapply/0/createForm', query: { formPath: 'dev/bizoutapply' //这里是表单的所在目录 } }); } } // 处理行的双击逻辑,其他地方可以类似处理 function dbClickRow(record) { const workflowData = record.workflowData || {}; const { processId, taskIds, schemaId } = workflowData; if (schemaId && taskIds) { // 待办 router.push({ path: '/flow/' + schemaId + '/' + processId + '/approveFlow', query: { taskId: taskIds[0] } }); } else if (schemaId && !taskIds) { // 已审批的单子 router.push({ path: '/flow/' + schemaId + '/' + processId + '/approveFlow', query: { readonly: 1, taskId: '' } }); } else { // 非流程 router.push({ path: '/form/overtimeapply/' + record.id + '/viewForm', query: { formPath: 'dev/overtimeapply' // 模块路径也要跟着改 } }); } } ``` 同时,因为外层封装页面需要表单加载后的元数据,需要在Form.vue中通过事件将表单数据传出。 ```javascript // 这行是原来有的 import { formProps, formEventConfigs } from './config'; onMounted(async () => { emits('form-mounted', formProps); // 补上这一句 }); ``` ### 如何修改选项卡标题 ```javascript import { useMultipleTabStore } from '/@/store/modules/multipleTab'; import { useRouter } from 'vue-router'; const tabStore = useMultipleTabStore(); const router = useRouter(); const currentRoute = router.currentRoute.value; const fullPath = currentRoute.fullPath; tabStore.changeTitle(fullPath, `选项卡标题`); // 顺便tabStore也支持关闭选项卡 tabStore.closeTab(currentRoute, router); ``` ### 如何在表单二开中自定义布局 ```vue