2024-04-02 18:10:48 +08:00
|
|
|
|
## 注意事项
|
2024-04-03 15:54:15 +08:00
|
|
|
|
### 不要被框架束缚思路
|
|
|
|
|
|
框架只是个代码生成器而已,前后端代码都在你手里,只要不影响其他模块,你想怎么实现怎么实现。
|
2024-04-02 18:10:48 +08:00
|
|
|
|
### 设计相关
|
|
|
|
|
|
- 一个表单只能绑定一个流程,一个流程也只能绑定一个表单,虽然框架支持单流程带多表单,但是我们在代码中屏蔽了这部分设计,如果表单内容较多,建议以子流程、tab页、分区等方式合理显示。
|
|
|
|
|
|
- 一旦某表单绑定了流程,该表单就只能以绑定流程的方式使用(展示一个流程的纯表单部分无意义,用户一定是想和流程一起查看或者审批)
|
|
|
|
|
|
- 流程草稿在单据列表里不会显示(草稿箱中可以找到),这是延续老系统的设计,不属于bug
|
|
|
|
|
|
- 因为代码生成器的特殊性,建议在设计的时候尽可能完善,或者预留某些字段,隐藏起来以便需求调整,开发者需要评估重新生成代码与自行添加字段的工作量
|
|
|
|
|
|
### 界面与用户体验
|
|
|
|
|
|
- 设计器支持响应式布局,因为设计器架构问题,并未默认打开,对于表单内字段,除了附件、多行文本框等占用宽度较大的组件外,都建议开启响应式布局
|
|
|
|
|
|
- 表单和表格需要合理调整字段宽度,响应式布局下需要使用定宽模式,一般情况下,字段的宽度取平均字长 + 2个汉字的宽度为宜,不要留太长的label,也要避免出现label换行
|
|
|
|
|
|
|
2024-05-09 09:42:56 +08:00
|
|
|
|
## Q&A
|
2024-06-12 11:38:57 +08:00
|
|
|
|
|
2024-05-09 09:42:56 +08:00
|
|
|
|
### 为什么表单所有字段都成了必填
|
|
|
|
|
|
默认情况下,绑定流程后,新建节点的所有字段都被设置为必填,需要在流程的开始节点-表单设置中去掉非必填的项。
|
|
|
|
|
|
|
2024-06-12 11:38:57 +08:00
|
|
|
|
### 表单设计和代码模版有什么区别
|
|
|
|
|
|
代码模版生成的本地文件有二开支持,表单符合UI规范,表单设计可以在线发布,像低开一样,不用写代码,但是界面不符合UI规范。
|
|
|
|
|
|
|
|
|
|
|
|
两种方法表单设计器支持内容一致,也都支持流程,差别只是UI方面。建议在设计业务模块优先使用代码模版。
|
|
|
|
|
|
|
2024-04-02 18:10:48 +08:00
|
|
|
|
## 在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' // 模块路径也要跟着改
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2024-05-09 09:42:56 +08:00
|
|
|
|
同时,因为外层封装页面需要表单加载后的元数据,需要在Form.vue中通过事件将表单数据传出。
|
2024-04-02 18:10:48 +08:00
|
|
|
|
```javascript
|
|
|
|
|
|
// 这行是原来有的
|
|
|
|
|
|
import { formProps, formEventConfigs } from './config';
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
|
emits('form-mounted', formProps); // 补上这一句
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
2024-05-27 21:42:48 +08:00
|
|
|
|
## 如何修改选项卡标题
|
|
|
|
|
|
```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
|
|
|
|
|
|
<Col v-if="getIfShow2('d7fxx515')" v-show="getIsShow2('d7f2xx515')" :span="getColWidth(schemaMap['d7fxx515'])">
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
```
|
|
|
|
|
|
这段代码上的span就是控制字段占位的,满宽度位24,1/3为8,1/4为6,以此类推,我们强烈大家使用响应式布局,以免在屏幕过窄时影响显示效果。同样,响应式布局在编辑器里支持换行、独立成行。
|
|
|
|
|
|
|
|
|
|
|
|
如果在二开里要换行,可以用div包裹,也可以加一个空div强制换行
|
|
|
|
|
|
```html
|
|
|
|
|
|
<div style="width: 100%"></div>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 如何添加自定义校验
|
|
|
|
|
|
可以在config.ts中定义,componentProps里默认有空的rules数组,可以自己添加,antd vue所有支持的校验都可以在这里使用。
|
2024-04-03 15:54:15 +08:00
|
|
|
|
```javascript
|
2024-05-27 21:42:48 +08:00
|
|
|
|
{
|
|
|
|
|
|
rules: [
|
2024-04-03 15:54:15 +08:00
|
|
|
|
{
|
2024-05-27 21:42:48 +08:00
|
|
|
|
min: 10,
|
|
|
|
|
|
max: 30,
|
|
|
|
|
|
message: '这个长度在10-30之间'
|
2024-04-03 15:54:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
]
|
2024-05-27 21:42:48 +08:00
|
|
|
|
}
|
2024-04-03 15:54:15 +08:00
|
|
|
|
```
|
2024-05-27 21:42:48 +08:00
|
|
|
|
## 如何设置字段联动
|
|
|
|
|
|
编辑器中选择Api即可配置字段联动,只要在magic-api配置参数即可,参数值可以直接选择表单或者明细表字段。
|
|
|
|
|
|
|
|
|
|
|
|
选择字典没有效果。
|
|
|
|
|
|
|
|
|
|
|
|
## 如何在表单二开中定义字段
|
|
|
|
|
|
下面的代码为一个支持多层加载的级联选择器,同时值和文字描述分开存储,不会在表单打开时加载数据:
|
2024-04-03 15:54:15 +08:00
|
|
|
|
```vue
|
2024-05-27 21:42:48 +08:00
|
|
|
|
<FormItem :labelCol="{ style: { width: '120px' } }" label="级联本体" name="jiLianBenTi7352">
|
|
|
|
|
|
<a-input v-model:value="formModel.jiLianBenTi7352" style="display: none"/>
|
|
|
|
|
|
<!-- 当一个formitem里有多个表单字段时,不需要取值的用form-item-rest包起来 -->
|
|
|
|
|
|
<a-form-item-rest>
|
|
|
|
|
|
<a-cascader v-model:value="cascadeValue" :load-data="onCascadeLoad" :options="cascadeData" placeholder="请选择" @change="onCascadeChange">
|
|
|
|
|
|
<a href="#">{{ formModel.jiLianWenZi5194 || '请选择' }}</a>
|
|
|
|
|
|
</a-cascader>
|
|
|
|
|
|
</a-form-item-rest>
|
|
|
|
|
|
</FormItem>
|
2024-04-03 15:54:15 +08:00
|
|
|
|
```
|
2024-05-27 21:42:48 +08:00
|
|
|
|
这里特别注意下,因为cascade的值为数组,而数组在数据库是不能存储的,所以这里cascadeValue为数组值,a-input里存储的为逗号分开的id。
|
2024-05-09 09:42:56 +08:00
|
|
|
|
|
2024-05-27 21:42:48 +08:00
|
|
|
|
特别注意,FormItem在本框架里获取值依靠form的validate方法,因此FormItem里必须有**antd vue的表单元素**,如果为纯手动开发的组件,建议用一个a-input储存表单值。
|
2024-05-09 09:42:56 +08:00
|
|
|
|
|
2024-05-27 21:42:48 +08:00
|
|
|
|
脚本部分
|
|
|
|
|
|
```vue
|
|
|
|
|
|
<script>
|
|
|
|
|
|
export default {
|
|
|
|
|
|
setup(props, ctx) {
|
2024-05-29 16:55:03 +08:00
|
|
|
|
const ret = SimpleFormSetup.setup(props, ctx);
|
2024-05-27 21:42:48 +08:00
|
|
|
|
const schemaMap = {};
|
|
|
|
|
|
const cascadeData = ref([]);
|
|
|
|
|
|
const cascadeValue = ref([]);
|
|
|
|
|
|
ret.getSchemas._value.forEach((schema) => {
|
|
|
|
|
|
schemaMap[schema.key] = schema;
|
|
|
|
|
|
});
|
|
|
|
|
|
return {
|
|
|
|
|
|
schemaMap,
|
|
|
|
|
|
cascadeData,
|
|
|
|
|
|
cascadeValue,
|
|
|
|
|
|
...ret
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
mounted() {
|
|
|
|
|
|
this.getCascadeData();
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
getCascadeData: function (parent = {}) {
|
|
|
|
|
|
defHttp
|
|
|
|
|
|
.get({
|
|
|
|
|
|
url: '/system/dev-demo/cascade-data',
|
|
|
|
|
|
params: {
|
|
|
|
|
|
level: parent.level
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.then((data) => {
|
|
|
|
|
|
if (!parent.level) {
|
|
|
|
|
|
this.cascadeData = data;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
parent.children = data;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
2024-05-09 09:42:56 +08:00
|
|
|
|
|
2024-05-27 21:42:48 +08:00
|
|
|
|
onCascadeChange: function (value, selectedOptions) {
|
2024-05-28 21:11:13 +08:00
|
|
|
|
// 将级联选择器的文字和值分别赋值到2个字段保存
|
2024-05-27 21:42:48 +08:00
|
|
|
|
this.formModel.jiLianWenZi5194 = selectedOptions.map((item) => item.label).join(' > ');
|
|
|
|
|
|
this.formModel.jiLianBenTi7352 = selectedOptions.map((item) => item.value).join(',');
|
|
|
|
|
|
},
|
2024-05-09 09:42:56 +08:00
|
|
|
|
|
2024-05-27 21:42:48 +08:00
|
|
|
|
onCascadeLoad: function (selectedOptions) {
|
|
|
|
|
|
const targetOption = selectedOptions[selectedOptions.length - 1];
|
|
|
|
|
|
this.getCascadeData(targetOption);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
</script>
|
2024-05-09 09:42:56 +08:00
|
|
|
|
```
|
2024-05-28 21:11:13 +08:00
|
|
|
|
|
|
|
|
|
|
## 如何在新建流程时,从其他表单带数据过来
|
|
|
|
|
|
这里的例子是从列表创建单据,其余的场景可以参考此修改。
|
|
|
|
|
|
|
|
|
|
|
|
首先在模版里加入新建单据的按钮:
|
|
|
|
|
|
```vue
|
|
|
|
|
|
<template #toolbar>
|
|
|
|
|
|
<template v-for="button in tableButtonConfig" :key="button.code">
|
|
|
|
|
|
<!-- 无关代码隐藏 -->
|
|
|
|
|
|
<a-button v-if="button.isDefault" type="primary" @click="buttonClick(button.code)">
|
|
|
|
|
|
</a-button>
|
|
|
|
|
|
<a-button v-else type="primary">
|
|
|
|
|
|
</a-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<a-button @click="goCreateFlow()"> 创建入库单 </a-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
```
|
|
|
|
|
|
你也可以通过修改buttonConfigs和actionButtons(代码里是互补关系,不在行动作actionButtons里的就在顶部动作条)实现,但是我认为没必要,自定义开发直接改模版是最简单最快的。
|
|
|
|
|
|
|
|
|
|
|
|
然后修改useTable的参数:
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const [registerTable, { reload, getSelectRows }] = useTable({
|
|
|
|
|
|
rowSelection: {
|
|
|
|
|
|
type: 'radio' //check为多选,具体选项可以参考antd vue文档
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
然后补完goCreateFlow的内容:
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
function goCreateFlow() {
|
|
|
|
|
|
const selectedRows = getSelectRows();
|
|
|
|
|
|
if (!selectedRows.length) {
|
|
|
|
|
|
return createMessage.error('请选择用于发起入库的单据');
|
|
|
|
|
|
}
|
|
|
|
|
|
// 这里当然可以把整行的数据放到localStorage,但是为了外链以及通用性,最好还是把id通过路由传过去,表单那边再查
|
|
|
|
|
|
router.push({
|
|
|
|
|
|
path: '/flow/1782662600422658049/0/createFlow',
|
|
|
|
|
|
query: {
|
|
|
|
|
|
fromIds: selectedRows.map((row) => row.id).join(',')
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
最后在目标表单的Form.vue中补充赋值操作:
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
import { useRouter } from 'vue-router';
|
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 无关代码已经隐藏,如果你是用localStorage获取数据,别忘了取值后清掉内容
|
|
|
|
|
|
const fromIds = router.currentRoute.value.query?.fromIds;
|
|
|
|
|
|
if(fromIds){
|
|
|
|
|
|
// 假装是在加载数据
|
|
|
|
|
|
setTimeout(()=>{
|
|
|
|
|
|
setFieldsValue({
|
|
|
|
|
|
yuanYin6568: fromIds
|
|
|
|
|
|
})
|
|
|
|
|
|
}, 1000)
|
|
|
|
|
|
}
|
|
|
|
|
|
emits('form-mounted', formProps);
|
|
|
|
|
|
} catch (error) {}
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|