项目背景
由于部门业务迅速发展,与合作商的合同数量增长速度快,以往通过人工流程对合同进行录入、查找的管理模式逐步跟不上合同所需的处理速度,因此将线下无序繁杂的合同升级为规范的线上合同的需求日益凸显,基于该业务诉求诞生出该合同系统。
合同系统的诞生极大地解决了录入、管理合同时耗费大量人力精力的问题,在减少业务同学工作量的同时,也更好地服务与合作伙伴,减少合同数据填写出错、遗漏等问题;
技术选型
技术选型 | 原因 | 前端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介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!