Files
geg-gas-web/docs/表单二开/4_表单tab页拆分+改为进度条样式.md
2024-06-12 18:35:13 +08:00

8.5 KiB
Raw Blame History

二开实例Tab页拆分+样式调整+校验

首先我们新建一个具有若干文本框的例子这里只讲思路因此不设计其他字段也不做除了必填的其他校验我们的要求是Tab页拆分的内容可以随便切换填写但是步骤表示的必须先写当前步骤才能填写后续内容

代码因为篇幅限制,去掉了和思路无关的部分。

<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文件也就是代码自动生成的表单。

// 校验form 通过返回表单数据
async function validate() {
    let values = [];
    try {
        /* 这里就是我们的方法这个返回值很有迷惑性values在校验失败的时候是false成功时候为key-value的对象不是数组 */
        values = await /*systemFormRef.value?.validate()*/ customValidate();
        //添加隐藏组件
        // 无关代码省略
    } finally {
    }
    return values;
}

然后手动实现校验过程:

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;
}