docs: 补充二开文档
This commit is contained in:
@ -20,6 +20,27 @@
|
|||||||
|
|
||||||
两种方法表单设计器支持内容一致,也都支持流程,差别只是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事件
|
### 如何定义onChange/blur事件
|
||||||
|
|
||||||
所有的事件都在字段的events中,如change、blur,没有on,主表单的函数定义为
|
所有的事件都在字段的events中,如change、blur,没有on,主表单的函数定义为
|
||||||
|
|||||||
@ -1 +1,208 @@
|
|||||||
# 二开实例:Tab页拆分+样式调整+校验
|
# 二开实例:Tab页拆分+样式调整+校验
|
||||||
|
|
||||||
|
首先,我们新建一个具有若干文本框的例子,这里只讲思路,因此不设计其他字段,也不做除了必填的其他校验,我们的要求是,Tab页拆分的内容可以随便切换填写,但是**步骤表示的必须先写当前步骤才能填写后续内容**。
|
||||||
|
|
||||||
|
代码因为篇幅限制,去掉了和思路无关的部分。
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div ref="formWrap">
|
||||||
|
<Form ref="formRef" :label-col="getProps?.labelCol" :labelAlign="getProps?.labelAlign" :layout="getProps?.layout" :model="formModel" :wrapper-col="getProps?.wrapperCol" @keypress.enter="handleEnterPress">
|
||||||
|
<a-tabs v-model:activeKey="currTab">
|
||||||
|
<a-tab-pane key="0" tab="分组1">
|
||||||
|
<!-- 这里注意Row和Col是配套使用,因为我们按选项开拆开了字段,所以每个选项卡里都有一个Row -->
|
||||||
|
<Row>
|
||||||
|
<!-- 文本框1 后面的字段省略 -->
|
||||||
|
<Col v-if="getIfShow2('efda5xx23')" v-show="getIsShow2('efdxx67e23')" :span="getColWidth(schemaMap['efdxx67e23'])">
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="1" tab="分组2">
|
||||||
|
<Row><!-- 第二个分组的字段 --></Row>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="2" tab="分组2">
|
||||||
|
<Row><!-- 第三个分组的字段 --></Row>
|
||||||
|
<div class="step-wrap">
|
||||||
|
<a-steps :current="currStep">
|
||||||
|
<!-- 注意这个版本的antd vue不能使用items赋值数组,只能手动循环 -->
|
||||||
|
<a-step v-for="(item, index) in steps" :title="item.title" @click="onClickStep(index)"></a-step>
|
||||||
|
</a-steps>
|
||||||
|
</div>
|
||||||
|
<Row>
|
||||||
|
<template v-if="currStep == 0">
|
||||||
|
<!-- 步骤1_1 其余步骤字段省略 -->
|
||||||
|
<Col v-if="getIfShow2('810xxd32')" v-show="getIsShow2('8100xx699d32')" :span="getColWidth(schemaMap['8xx2699d32'])">
|
||||||
|
</Col>
|
||||||
|
</template>
|
||||||
|
<template v-if="currStep == 1"><!-- 步骤2字段 --></template>
|
||||||
|
<template v-if="currStep == 2"><!-- 步骤3字段 --></template>
|
||||||
|
</Row>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
<!-- 无关内容省略 -->
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.step-wrap {
|
||||||
|
width: 80%;
|
||||||
|
margin: 0 auto 16px auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
// 注意,这里继承的是SimpleFormSetup,使用script setup写法的组件无法继承,必须使用特别的版本
|
||||||
|
import SimpleFormSetup from '/@/components/SimpleForm/src/SimpleFormSetup.vue';
|
||||||
|
import { Col, Form, Row } from 'ant-design-vue';
|
||||||
|
import SimpleFormItem from '/@/components/SimpleForm/src/components/SimpleFormItem.vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { CheckCircleOutlined } from '@ant-design/icons-vue';
|
||||||
|
|
||||||
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { CheckCircleOutlined, Form, Col, SimpleFormItem, Row, FormItem },
|
||||||
|
mixins: [SimpleFormSetup],
|
||||||
|
setup(props, ctx) {
|
||||||
|
const ret = SimpleFormSetup.setup(props, ctx);
|
||||||
|
const currStep = ref(0);
|
||||||
|
const currTab = ref('0');
|
||||||
|
const expose = ctx.expose; // 这个是选项的setup写法,不能用defineExpose
|
||||||
|
|
||||||
|
function onClickTab(index){
|
||||||
|
currTab.value = index + '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClickStep(index){
|
||||||
|
currStep.value = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
const steps = ref([
|
||||||
|
{
|
||||||
|
title: '步骤1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '步骤2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '步骤3'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
expose({
|
||||||
|
...ret.formApi, // 特别注意我们需要继承父类的expose,这个写法只对本例子有效
|
||||||
|
onClickStep,
|
||||||
|
onClickTab
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...ret,
|
||||||
|
currStep,
|
||||||
|
steps,
|
||||||
|
currTab,
|
||||||
|
onClickStep,
|
||||||
|
onClickTab
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 这里需要增加一个计算属性 否则流程关联时字段读写状态会失效
|
||||||
|
schemaMap() {
|
||||||
|
const schemaMap = {};
|
||||||
|
this.getSchemas.forEach((schema) => {
|
||||||
|
schemaMap[schema.key] = schema;
|
||||||
|
});
|
||||||
|
return schemaMap;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
//无关内容省略
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
可以看出,在进行二开拆分后,使用v-if、v-show配合响应式变量定义,可以轻松拆分选项卡和步骤条。这里特别注意的是,子类需要继承父类的expose方法,而正常情况下该需求无法实现,这里可以实现的原因是父类的formApi实际上就是expose的全部内容,因此我们在对外暴露的时候,只需要合并formApi就可以,对于其他组件,该方法不成立。
|
||||||
|
|
||||||
|
除了父类的内容外,我们还对外提供了切换选项卡和步骤的方法,以便校验错误的时候跳转到对应选项卡,方便修改。
|
||||||
|
|
||||||
|
接下来要手动修改原有的验证方法,打开components/Form.vue文件,也就是代码自动生成的表单。
|
||||||
|
```javascript
|
||||||
|
// 校验form 通过返回表单数据
|
||||||
|
async function validate() {
|
||||||
|
let values = [];
|
||||||
|
try {
|
||||||
|
/* 这里就是我们的方法,这个返回值很有迷惑性,values在校验失败的时候是false,成功时候为key-value的对象,不是数组 */
|
||||||
|
values = await /*systemFormRef.value?.validate()*/ customValidate();
|
||||||
|
//添加隐藏组件
|
||||||
|
// 无关代码省略
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
然后手动实现校验过程:
|
||||||
|
```javascript
|
||||||
|
import { useMessage } from '/@/hooks/web/useMessage';
|
||||||
|
const { createMessage } = useMessage();
|
||||||
|
|
||||||
|
function validateField(schema, formValues) {
|
||||||
|
// 这里只考虑了必填校验 如果需要其他校验自己完成
|
||||||
|
if (!schema?.componentProps?.required) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return formValues[schema.field];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function customValidate() {
|
||||||
|
const schemas = data.formDataProps.schemas;
|
||||||
|
// 组件在选项卡的第几页,实际上每个选项卡要校验的不止一个字段
|
||||||
|
const pageMapping = {
|
||||||
|
wenBenKuang17845: 0,
|
||||||
|
wenBenKuang46888: 1,
|
||||||
|
wenBenKuang73214: 2
|
||||||
|
};
|
||||||
|
// 组件在步骤的第几页
|
||||||
|
const stepMapping = {
|
||||||
|
buZhou119202: 0,
|
||||||
|
buZhou212978: 1,
|
||||||
|
buZhou316637: 2
|
||||||
|
};
|
||||||
|
const stepFlag = [1, 1, 1];
|
||||||
|
const formValues = systemFormRef.value.getFieldsValue(); // 这个函数可以取到formModel
|
||||||
|
let pageField = null;
|
||||||
|
// 这种遍历只针对主表有效,如果用了子表还要对子表校验
|
||||||
|
for (let i = 0; i < schemas.length; i++) {
|
||||||
|
let schema = schemas[i];
|
||||||
|
if (validateField(schema, formValues)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const field = schema.field;
|
||||||
|
// 记下来第一个未通过的字段,后面都不需要校验了,因为要跳转过去
|
||||||
|
if (pageMapping[field] !== undefined && pageField === null) {
|
||||||
|
pageField = pageMapping[field];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// 记下来哪些步骤页面没填完
|
||||||
|
if (stepMapping[field] !== undefined) {
|
||||||
|
stepFlag[stepMapping[field]] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pageField !== null) {
|
||||||
|
systemFormRef.value.onClickTab(pageField);
|
||||||
|
// 手动触发一次校验函数,让红框显示出来
|
||||||
|
systemFormRef.value.validate();
|
||||||
|
return Promise.reject(false);
|
||||||
|
}
|
||||||
|
const strStepFlag = stepFlag.join('');
|
||||||
|
// 拼接后的步骤标志1表示填完,0表示没填完,如果出现01的序列,如101,表示步骤1填了,步骤2没填,步骤3填了
|
||||||
|
// 按照我们假定的场景,只有1填完才能填2
|
||||||
|
const valResult = strStepFlag.indexOf('01');
|
||||||
|
if (valResult > -1) {
|
||||||
|
// 假设序列为101,那返回值为1,也就是步骤2没填
|
||||||
|
createMessage.warn(`步骤${valResult + 1}完成后才能填写后面内容`);
|
||||||
|
systemFormRef.value.onClickTab(2); // 我们知道哪页有步骤条,所以可以写死
|
||||||
|
systemFormRef.value.onClickStep(valResult);
|
||||||
|
return Promise.reject(false);
|
||||||
|
}
|
||||||
|
return formValues;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user