Files
geg-gas-web/docs/dev_readme.md

13 KiB
Raw Blame History

注意事项

不要被框架束缚思路

框架只是个代码生成器而已,前后端代码都在你手里,只要不影响其他模块,你想怎么实现怎么实现。

设计相关

  • 一个表单只能绑定一个流程一个流程也只能绑定一个表单虽然框架支持单流程带多表单但是我们在代码中屏蔽了这部分设计如果表单内容较多建议以子流程、tab页、分区等方式合理显示。
  • 一旦某表单绑定了流程,该表单就只能以绑定流程的方式使用(展示一个流程的纯表单部分无意义,用户一定是想和流程一起查看或者审批)
  • 流程草稿在单据列表里不会显示草稿箱中可以找到这是延续老系统的设计不属于bug
  • 因为代码生成器的特殊性,建议在设计的时候尽可能完善,或者预留某些字段,隐藏起来以便需求调整,开发者需要评估重新生成代码与自行添加字段的工作量

界面与用户体验

  • 设计器支持响应式布局,因为设计器架构问题,并未默认打开,对于表单内字段,除了附件、多行文本框等占用宽度较大的组件外,都建议开启响应式布局
  • 表单和表格需要合理调整字段宽度,响应式布局下需要使用定宽模式,一般情况下,字段的宽度取平均字长 + 2个汉字的宽度为宜不要留太长的label也要避免出现label换行

Q&A

为什么表单所有字段都成了必填

默认情况下,绑定流程后,新建节点的所有字段都被设置为必填,需要在流程的开始节点-表单设置中去掉非必填的项。

在Tab页中打开表单/流程

如果你需要自己编程实现Tab页跳转或者升级旧版框架的页面可以参考下面步骤

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中通过事件将表单数据传出。

// 这行是原来有的
import { formProps, formEventConfigs } from './config';

onMounted(async () => {
    emits('form-mounted', formProps); // 补上这一句
});

如何修改选项卡标题

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

如何进行表单二开

首先打开项目dev_tools/formprops.js将其中的内容替换为模块config注意nodejs不加参数必须用module.exports不能用export default写法。

然后执行template_extend.js会在同目录生成template.txt这个文件就是将config文件在第一层展开的模版内容。

接下来找到要二开的表单在components目录里定义新建一个vue文件然后用下面的内容初始化这个文件实际上就是SimpleForm的继承我们要用自己的模版覆盖掉原来的循环。由于不是每个字段都有dataIndex如布局类、标题使用数组展开在二次修改后会比较混乱此处定义了映射表可以通过key找到组件同时简化了原来的两个函数。

<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">
        <Row>
            <!-- 把刚才template.txt的内容放到这里 -->
        </Row>

        <div :style="{ textAlign: getProps.buttonLocation }">
            <slot name="buttonBefore"></slot>
            <a-button v-if="getProps.showSubmitButton" type="primary" @click="handleSubmit">
                {{ t('提交') }}
            </a-button>
            <a-button v-if="getProps.showResetButton" style="margin-left: 10px" @click="handleReset">
                {{ t('重置') }}
            </a-button>
            <slot name="buttonAfter"></slot>
        </div>
    </Form>
</div>
</template>

<script>
// 注意这里继承的是SimpleFormSetup使用sciprt 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';

const FormItem = Form.Item;

export default {
    components: { Form, Col, SimpleFormItem, Row, FormItem },
    mixins: [SimpleFormSetup],
    setup(props, ctx) {
        const ret = SimpleForm.setup(props, ctx);
        const schemaMap = {};
        ret.getSchemas._value.forEach((schema) => {
            schemaMap[schema.key] = schema;
        });
        return {
            schemaMap,
            ...ret
        };
    },
    methods: {
        getIfShow2: function (key) {
            return this.getIfShow(this.schemaMap[key], this.formModel[this.schemaMap[key].field]);
        },

        getIsShow2: function (key) {
            return this.getIsShow(this.schemaMap[key], this.formModel[this.schemaMap[key].field]);
        }
    }
};
</script>

找到components目录下的Form.vue用我们自己的表单替换掉原来的SimpleForm假设我们的文件为CascadeDemoForm.vue

<template>
    <CascadedemoForm
        ref="systemFormRef"
        :formProps="data.formDataProps"
        :formModel="{}"
        :isWorkFlow="props.fromPage!=FromPageType.MENU"
    />
</template>

展开后的文件和原有表单一致每个字段都已经拆开因此可以自由使用v-if/v-show等进行显隐控制以及在字段之间插入内容、做自动计算等。

该展开方法不能用Grid、Tab等布局嵌套如果需要建议自行写代码套容器不要用编辑器自带的布局组件。

如何在表单二开中自定义布局

<Col v-if="getIfShow2('d7fxx515')" v-show="getIsShow2('d7f2xx515')" :span="getColWidth(schemaMap['d7fxx515'])">
</Col>

这段代码上的span就是控制字段占位的满宽度位241/3为81/4为6以此类推我们强烈大家使用响应式布局以免在屏幕过窄时影响显示效果。同样响应式布局在编辑器里支持换行、独立成行。

如果在二开里要换行可以用div包裹也可以加一个空div强制换行

<div style="width: 100%"></div>

如何添加自定义校验

可以在config.ts中定义componentProps里默认有空的rules数组可以自己添加antd vue所有支持的校验都可以在这里使用。

{
    rules: [
        {
            min: 10,
            max: 30,
            message: '这个长度在10-30之间'
        }
    ]
}

如何设置字段联动

编辑器中选择Api即可配置字段联动只要在magic-api配置参数即可参数值可以直接选择表单或者明细表字段。

选择字典没有效果。

如何在表单二开中定义字段

下面的代码为一个支持多层加载的级联选择器,同时值和文字描述分开存储,不会在表单打开时加载数据:

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

这里特别注意下因为cascade的值为数组而数组在数据库是不能存储的所以这里cascadeValue为数组值a-input里存储的为逗号分开的id。

特别注意FormItem在本框架里获取值依靠form的validate方法因此FormItem里必须有antd vue的表单元素如果为纯手动开发的组件建议用一个a-input储存表单值。

脚本部分

<script>
export default {
    setup(props, ctx) {
        const ret = SimpleFormSetup.setup(props, ctx);
        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;
                    }
                });
        },

        onCascadeChange: function (value, selectedOptions) {
            // 将级联选择器的文字和值分别赋值到2个字段保存
            this.formModel.jiLianWenZi5194 = selectedOptions.map((item) => item.label).join(' > ');
            this.formModel.jiLianBenTi7352 = selectedOptions.map((item) => item.value).join(',');
        },

        onCascadeLoad: function (selectedOptions) {
            const targetOption = selectedOptions[selectedOptions.length - 1];
            this.getCascadeData(targetOption);
        }
    }
};
</script>

如何在新建流程时,从其他表单带数据过来

这里的例子是从列表创建单据,其余的场景可以参考此修改。

首先在模版里加入新建单据的按钮:

 <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的参数

const [registerTable, { reload, getSelectRows }] = useTable({
    rowSelection: {
        type: 'radio' //check为多选具体选项可以参考antd vue文档
    }
});

然后补完goCreateFlow的内容

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中补充赋值操作

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