最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 电子流合同系统开发

    正文概述 掘金(热心码农陈某)   2021-02-24   598

    项目背景

    由于部门业务迅速发展,与合作商的合同数量增长速度快,以往通过人工流程对合同进行录入、查找的管理模式逐步跟不上合同所需的处理速度,因此将线下无序繁杂的合同升级为规范的线上合同的需求日益凸显,基于该业务诉求诞生出该合同系统。

    合同系统的诞生极大地解决了录入、管理合同时耗费大量人力精力的问题,在减少业务同学工作量的同时,也更好地服务与合作伙伴,减少合同数据填写出错、遗漏等问题;

    技术选型

    技术选型原因
    前端JS框架Vue2.0社区热度高,组件种类多,技术栈成熟,类HTML模板熟悉成本低,路由(vue-router)、状态管理器(vuex)等高实用性组件由Vue核心团队维护,是官方推荐组件UI库Iview提供基于Vue开发的UI组件库,技术相对成熟,组件种类多,当前基础建设系统使用iview进行开发(*主要原因),避免引入其他UI库导致代码杂乱css开发工具SCSS提高css编写效率,使用函数式编程编写css代码具有较高的扩展性及可读性,维护成本较低

    合同项目部分功能页面截图

    合同人工录入-基础信息:

    电子流合同系统开发

    合同人工录入-详细信息

    电子流合同系统开发

    合同查询

    电子流合同系统开发

    合同查询-作品列表信息

    电子流合同系统开发

    合同审核

    电子流合同系统开发

    开发目录划分及其意义?

    一个项目如果有一个合理的项目目录结构,能让开发者及之后维护的人对项目熟悉了解的成本大大降低

    电子流合同系统开发

    如上图所示,总目录ContractV2下创建了多个不同的文件夹对应功能及说明如下所示:

    组件化、模块化开发的优势及如何定义?

    在新的项目需求拿到手时,不要急于立马进行开发,先好好梳理一遍项目结构、流程、交互,对功能点进行归纳分类,与产品、需求方反复沟通,明确项目业务流程是否通顺、功能点是否具有复用性,一方面是为了能在开发时,对项目功能划分具有大局性思维,另一方面是为了在开发时避免做无用功,导致对不必封装的组件、模块进行了封装,对需要封装的组件模块却直接用业务代码实现了,导致后期要重构代码。

    优点缺点
    组件化、模块化开发维护成本低
    可扩展性高
    耦合程度低,删改简便
    复用性强
    改造成本中
    学习成本低
    基于状态码或业务逻辑进行扩展时须兼容多种应用场景
    项目进行前期不便于确定是否需要模块化、组件化开发
    定制化程度低
    组件间通讯不方便
    直接写业务逻辑实现实现速度快
    功能封装程度低令单功能模块冗余代码少
    适用于高定制化开发
    维护成本高
    可扩展性低
    复用性低
    改造成本中
    学习成本中
    业务、组件间代码耦合程度高,不便于删改

    基于iview的tabs组件进行的二次组件化封装代码及实例使用代码:


    组件化封装 start

    <style lang="scss" scoped>
        .contract-tabs {
            margin-top: 20px;
            /deep/.can-not-close+.ivu-icon-ios-close {
                display: none;
            }
            /deep/.ivu-tabs-bar {
                margin-bottom: 0;
            }
            .ivu-page {
                text-align: right;
            }
        }
    <style>
    
    <template>
        <Tabs ref="tabs" type="card" :value="curTab" :animated="false" closable @on-tab-remove="handleTabRemove" :before-remove="removeBefore" class="contract-tabs">
            <template v-for="(tab,index) in tabColumns">
                <TabPane v-if="tab.show" :label="tab['closeable'] ? tab.label : (h) => tab.renderLabel(h, tab)" :class="{'disable-close': !tab.closeable}" :key="randomKey(index)" :name="tab.name">
                    <Table v-if="tab.type == 'table'" :loading="tab.isLoading" height="630" border :columns="tab.columns" :data="tab.data"></Table>
                    <Page :current="tab.page.currentPage" :total="tab.page.total" size="small" show-total @on-change="(page)=>{tab.page.onChange(page,index)}" show-elevator :page-size="tab.page.page_size" v-if="tab.page"></Page>
                </TabPane>
            </template>
        </Tabs>
    </template>
    
    import Vue from 'vue';
    import {Tabs} from 'iview';
    
    //Tab原属性
    const tabsNativeProps = Tabs.props,
        tabsNativePropNames = Object.keys(tabsNativeProps);
    
    //Tab属性监听
    const originProxyWatcher = tabsNativePropNames.reduce((watch, name) => {
        watch[name] = function (val) {
            Vue.set(this.wrapProps, name, val);
        };
        return watch;
    }, {});
    
    //Tab原方法
    const tabNativeMethods = Tabs.methods,
        tabNativeMethodsName = Object.keys(tabNativeMethods);
    
    //Tab原方法代理
    const originMethodsProxy = tabNativeMethodsName.reduce((proxy, name) => {
        proxy[name] = function (...args) {
            const $tabs = this.refs['tabs'];
            $tabs[name].apply($tabs, args);
        };
        return proxy;
    }, {});
    
    
    export default {
        pageAuthor: 'janwardchen',
        components: {Tabs},
        props: Object.assign({
            'curTab': {
                type: String,
                default: 'tabIndex1',
            },
            'tabPaneColumns': {
                type: Array,
                default: () => {
                    return []
                }
            },
        }, tabsNativeProps),
        watch: Object.assign({}, originProxyWatcher),
        methods: Object.assign({
            renderLabel: (createElement, params) => {
                let {label='',closeable,show=true} = params;
                if (closeable == false || closeable == 'false') {
                    return createElement('span',{attrs: {class: 'can-not-close'}}, label);
                }
                if(!show) {
                    return null;
                }
            },
            removeBefore(idx) {
                const {closeable} = this.tabPaneColumns[idx];
                if (closeable == false || closeable == 'false') {
                    return new Promise((resolve, reject) => {});
                }
            },
            handleTabRemove(name, arg) {
                this.$emit("removeTab",name);
            },
            randomKey(idx) {
                const _t = new Date(),
                    _key = idx.toString() + _t.getTime().toString();
                return _key;
            }
        }, originMethodsProxy),
        computed: {
            tabColumns() {
                let _columns = this.tabPaneColumns || [];
                const self = this;
                if (_columns) {
                    _columns.forEach((item, idx) => {
                        item['renderLabel'] = self.renderLabel;
                        if(item.page && JSON.stringify(item.page) != '{}') {
                            let {currentPage=1,total=0,page_size=20,onChange=()=>{return false;}} = item.page;
                            item.page.currentPage = Number(currentPage);
                            item.page.total = Number(total);
                            item.page.page_size = Number(page_size);
                            item.page.onChange = onChange;
                        }
                        if(item.show==undefined) {
                            item.show = true;
                        }
                    });
                }
                return _columns;
            }
        }
    }
    

    组件化封装end


    组件实例示例:

    <template>
        <ContractTabs :tab-pane-columns="tabPaneColumns" :cur-tab="curTab" @removeTab="removeTab"></ContractTabs>
    </template>
    
    <script>
        import ContractTabs from '~/components/contract-tabs.vue';
        export default {
            components: {
                ContractTabs,
            },
            data() {
                return {
                    tabPaneColumns: [
                        {label: '补签合同',type: 'table',columns: [
                                {title: '合同号 ', minWidth: 170, key: 'pact_no',align: 'center',},
                                {title: '协议列表 ',minWidth: 140,key: 'is_signed_protocol',align: 'center',render: this.renderColumn(),},
                                {title: '作品列表 minWidth: 200,key: 'works_list',align: 'center',render: this.renderColumn(),},
                                {title: '我方主体 ', minWidth: 110, key: 'ou',align: 'center'},
                                {title: '供应商名称 ', minWidth: 175, key: 'cp_name', align: 'center',},
                                {title: '生效时间 ', minWidth: 180, key: 'effect_time', align: 'center',},
                                {title: '合同状态 ', minWidth: 130, key: 'state', align: 'center',},
                                {title: '经办人 ', minWidth: 100, key: 'head_person', align: 'center',},
                                {title: '录入人 ', minWidth: 100, key: 'head_person', align: 'center',},
                                {title: '审核人 ', minWidth: 100, key: 'head_person', align: 'center',},
                                {title: '合同详情 ',minWidth: 110,key: 'show_dfixed: 'right',render: this.renderColumn(),},
                                {title: '操作 ',minWidth: 145,key: 'sign_protocol_list',align: 'center',fixed: 'right',render: this.renderColumn(),}
                            ],
                            isLoading: true,
                            page: {currentPage: 1, total: 0, page_size: 20, onChange: this.fetchIndexData},
                            data: [],
                            closeable: false,
                            name: 'tabIndex1'
                        },
                    ],
                }
            },
            methods: {
                //渲染table列
                renderColumn() {
                    .....
                }
            }
        }
    </script>
    

    如上图代码所示,在tabPaneColumns中只需把对应的tab数据处理好,传入点击按钮的处理事件,余下的处理交由ContractTabs就好了,这种形式的处理在删减该逻辑时,直接把<ContractTabs> <ContractTabs>删除,就能移除该功能,不会影响到其他模块的代码。

    组件示例图:

    电子流合同系统开发

    此外,对于功能的模块化处理也做了一定的封装,如下贴出一些示例:

    /**
     * 执行ajax请求
     * @param url 请求接口地址
     * @param data 请求参数
     * @param cb 请求成功后执行的回调
     * @param type 请求方式为post或get  选填
     * @param ignore 回调是否忽略res.status判断
     */
    async fetchData(url, data, cb, type, ignore) {
        let method = type || 'get';
        const res = await this.ajax({url, data, method});
        if (ignore) {
            cb(res);
            return false
        } else {
            if (res.status == 2) {
                cb(res);
                return false;
            } else if (res.status == 1) {
                this.$Message.warning(res.msg);
                return false;
            } else {
                console.log(res.status);
                this.$Message.warning(res.msg);
                return false;
            }
        }
    },
    /**
    * 对form表单数据进行统一提交前格式化处理,确保数据通用性
    * @param data 待处理的数据,处理完后回吐数据
    */
    formatFormData(data) {
        let formData = {};
        if (JSON.stringify(data) != '{}') {
            let ObjArr = Object.keys(data);
            ObjArr.forEach((keys) => {
                data[keys].forEach((item) => {
                    formData[item.key] = item.value;
                });
            });
        }
        return formData;
    },
    

    如上,这些组件、功能模块都需要在多个地方复用,如果不做任何封装处理,每一个需要用到的地方都直接手动重新编写一次,代码的重复量可想而知是很多的,而且在改动的时候,也很可能会出现改漏改错的情况,这是程序员的噩梦啊。。

    合同项目开发总结

    • 项目功能项多,页面多,接口多,各接口的作用、数据、结构多样,因此在开发时,后台每完成一个接口交接到前端时,都必须进行整理,记录接口的入参,作用,调用方式、调用地方、返回的数据各字段作用,否则当接口的量多了,查找或复盘的时候容易一头雾水。
    • 前期对需求必须有足够的了解,多与产品、需求方沟通,确认各功能交互、数据校验严格程度,避免过多进行组件化封装,也避免因缺少沟通了解导致多次改造调整功能、组件、模块。
    • 这次项目中没有使用当前大火的TypeScript,这在开发组件和调试接口时,出现了很多因数据格式、类型而引发的报错问题,需要慢慢的一步一步定位解决,在调试耗时上没有控制好。

    起源地下载网 » 电子流合同系统开发

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元