在此文开篇之处先特别申明,此文在有些人的眼中会有广告的嫌疑,但是本人不想将其作为一个广告宣传的文章,在此提到软件内容部分请大家予以谅解和包含,作为时间不算短的程序员给大家分享一些自己开发吉特仓储管理软件相关的经验和坑,当然还有一些自己从中获利的方式,不能说给大家指条明路吧,算是作为程序开发人的相互经验交流。
此文本来想写在国庆假期之前的,但是那段时间公司事情刚好很忙,所以没有来得及写此文。当时要搞Solr搜索引擎,因为自己不熟悉java程序所以在弄得过程中有些费力,而且自己本身也不是一心苦心专研程序技术的人,但我绝对是一位苦心专研程序的人,虽然最终也没有什么大的成果。去年公司没有干之后,一直到今年现在我一直在想为何我们公司会做不下去,就连一个小公司都做不下去,其实我们也很努力,我们也很拼命,创业之初一周基本上四天会睡在办公室,每日每夜的做项目,那个时候是有客户的,但是到了最后发现每个项目都不赚钱,为什么时间太长了,不能快速解决客户问题[这只是其中一个很小的原因],当时确定了公司的方向就是要做仓储物流制造方面的软件产品。虽然我知道这个行业竞争很大,很多人说现在的市场有很多成熟的产品,这个不说我也自然明白,每个人都有自己的想法,也有自己针对的市场方向,我们在此不多说。
一. 深入一个行业
最开始做开发的时候涉及到过好多行业,汽车零配件,金融单位,游戏行业,房地产行业,一直做着感觉最终除了会写代码好像啥也不会,一个行业的套路你都还没有弄清楚感觉你就落伍了(主要是干个几年感觉工资跟不上了,只能跳槽增加工资咯,其实我跳槽没有一次是加过工资的,因为我胆小,只要认为自己过得去就行了,最重要的是觉得自己干的值)。不是每个技术员都可以开公司的,但是很多技术员都给别人做过私活,感觉工作之余赚点小钱还是很不错的,在此之前我也干过感觉挺牛逼的.其实后来我也做的少,我也去问过很多人他们基本一致的答案:私活真的是赚的辛苦钱,每天没日没夜的,真不如上班。 这个的确是如此的,我已人格保证, 所以现在刚出道的程序员们如果手上没有什么货的话,真的不要轻易去接什么私活。
1. 你跳槽那么多次,多少年之后你发现你比别人强在哪里?
这个问题好容易戳人的心窝子,特别是做过外包的同学感触肯定很深,今天这个项目需要我那我不顾一切扑上去,下一个项目需要我*扑上去。国庆回家的时候,火车上三个湖南小伙,听聊天得知是干程序的,其中一人说我做过某某军工企业的什么软件,我做过某某银行的什么软件,我还做了某个***企业什么的软件, 其实我只是听听,我基本知道这个小伙是干外包项目的,然后还有一个携程的小伙,他们聊了很多我一直不说话,但是他们都谈到了一个问题就是这些年下来感觉除了会写代码,好像就干不了其他的,如果换了一个行业或者一个项目组又好像从头开始了。
其实这就是问题的所在,因为你在短期内不断的在切换你不熟知的领域,你还没有足够的时间来深入了解这个行业的特性,也就使得你不能够在这个行业成为专家,所以你最终的结果就是可替代性很强,程序员现在大把大把的,上海地铁上随便抓一大把,替换你分分钟的事情,就说携程招人和离职的速度来看就足以说明这个问题。在此之前我也有过类似的感受,感觉到头来啥啥都不是,我等天之愚钝之人不可能做到技术牛逼之人,发挥不了自己的智商。那就只能在某个小的领域专心做事,做精做强。
之前我特别羡慕一个朋友,他是做销售的,其实他所做的事情远不是一个销售人员能够达到的了,他之前是做货架,自动门等销售的,一直做了十来年吧,后面我经常跟着他一起去跑客户,我说让他带我我见世面,到客户那边之后他所做的事情远不是销售给客户介绍产品,推荐价格。到了客户仓库他会给客户做专业的仓库规划测量,他随身带着卷尺,测距仪,黄胶带等等一系列东西,俨然就是一个工程师的范,事情做得非常的专业到位,但是我见他很少直接给客户推荐某个产品,然后还会给客户给出规划的图纸设计图。当时我很是惊呆,这个销售的确做的到位,后面我问他是不是学这个行业的,他说他从来没有学过这些,只是在这个行业一直做就慢慢的专业了。再后来我有认识了另外一个做同样销售的销售员,他也就是一个普通的销售员了,我跟他去看客户现场他也就只知道拿着产品说明书给客户介绍产品了,差距自然不说,后面这位就没有干这一行了,我问他为什么,他说这个行业赚不到钱。那个朋友现在上海儿女双全,有车有房,相信比一般的程序员要好很多的。后面他说自己做货架销售,在他们公司做了一个业绩全国第一,好像是这样的。总之给我的感觉他做这个事"专业",他也给过我一个建议:“好好的干一行”
2. 你为什么成为不了专家?
前不久上海嘉定那边一个工厂的客户老板打电话我,说你好像很久没有到我们公司来坐坐了,你啥时候有空到我们公司来坐坐啊,我们聊聊人生。其实这位老板年长我很多,为人很精明。我问他是不是软件出问题了,他说不是的,他说像找你聊聊仓库生产方面的事情。当他电话我的时候我第一个反应就是软件出问题了,后面聊下来他觉得流程比软件更重要吧,他是想让我帮他们想想如何优化生产流程以及仓库作业。其实我不太想搭理这个客户,具体原因不说了.但是客户能够给我打电话我还是挺高兴的,其实他大可以去找别人或者其他的供应商来处理这个事情。他说事情我一点就通,他们说的生产仓库问题我很快就能够知道他们说的什么问题,并且给出合理的建议,他们觉得我很专业。 其实我不能自诩自己是专业,当时决定做了这个方面的事情之后,我的确花了很大的精力来了解这个行业的知识,包括现场实地考察。
什么是专家, 我个人觉得就是在这个行业有资深经验, 专 就是专业, 家 不好怎么解释,反正画家,书法家等比较有威望吧,最关键的是能够在关键时刻关键点起到关键作用, 当然不能说我们国家某某部门某某专家了,这种话不要多说。专家能够刻画到点子上,戳中要点一针见血,找出良方,这就是专家。专家除了苦学那就是经验积累,遇到的问题越多后面你能够解决问题的几率就越大,久而久之你就是专家了。其实现在很多做技术的今天学.NET,明天又是Java,再来说PHP(拍黄片)是世界上最好的语言, 久而久之其实他也就是一个普通写代码的,干了10年还是只能写浅显的代码。
二. 项目开发你为何这么慢
我自己一句有一句调侃自己的话:"每个人都说时间就是金钱,你的时间真的值钱么,如果在特定的时间内你没有创造价值那么你的时间就不值钱" 。 好像有点过分,但对于我自己来说是有道理的
1. 为何一个简单的进销存你都要做三个月
这是我当年遇到的很现实的问题,先不说价钱问题。别人一个月就可以上线运行,为何你一个简单的进销存要做三个月,我们仔细罗列这些功能:(1) 系统设置 (2) 用户管理 (3) 部门管理 (4) 角色管理 (4)产品管理 (6) 价格管理 (7) 客户管理 (8) 供应商管理 .....我实在不想说下去了,简单的进销存确实不止这些功能,在怎么简单你得让客户流程能够跑起来才行啊。
再从技术方面考虑: 这得有多少张表啊,数据库操作好多代码啊,还要设计前段UI,做的交互性好还要大量的JS,想想这些工作量3个月真的一点不多。唉,为什么我就没有一套可以复用的东西呢?
对的,因为你没有可以复用的东西,所以你慢,你项目进度不能快也就在情理之中了
2.怎么就没有公共组件
等你确定好做一个行业软件的开发之后,技术体系基本确定之后,开发还是慢啊。我要弹出框,系统规划中没有这个组件,我要从网上找。 好多个页面要选择用户信息,弹出页面,第一个页面凑合着做吧,忍了,又来一个这样的页面需求,又忍了, 越来越多,忍无可忍,这种业务组件你为何不封装成公共的呢。
; (function ($) { $.fn.ProductDialog = function (options) { var defaultOption = { data: {}, Mult: true, EventName: "dblclick", callBack: undefined }; defaultOption = $.extend(defaultOption, options); var DataServer={ Server: function () { var config = (function () { var URL_GetList = "/Storage/ProductAjax/GetList"; return { URL_GetList: URL_GetList }; })(); //数据操作服务 var dataServer = (function ($, config) { //查询分页列表 var GetList=function(data,callback){ $.gitAjax({ url: config.URL_GetList, data: data, type: "post", dataType: "json", success: function (result) { if(callback!=undefined && typeof callback=="function"){ callback(result); } } }); } return { GetList: GetList } })($, config); return dataServer; }, PageClick:function(PageIndex,PageSize){ $.jBox.tip("正在努力加载数据...","loading"); var Server=DataServer.Server(); var search={}; search["PageIndex"]=PageIndex; search["PageSize"]=PageSize; Server.GetList(search,function(result){ DataServer.SetTable(result); $.jBox.closeTip(); }); }, SetTable:function(result){ current.find("#tabInfo").DataTable({ destroy: true, data:result.Result, paging:false, searching:false, scrollX: false, bAutoWidth:false, bInfo:false, ordering:false, columns: [ { data: 'SnNum' ,render:function(data, type, full, meta){ return "<input type='checkbox' name='product_item' value='"+data+"' data-full='"+JSON.stringify(full)+"'/>"; }}, { data: 'BarCode'}, { data: 'ProductName',createdCell:function(td, cellData, rowData, row, col){ if(!git.IsEmpty(cellData) && cellData.length>10){ $(td).popover({container:"body",content:cellData,trigger:"hover",placement:"bottom",delay:{show:500}}); } },render:function(data,type,full,meat){ return git.GetStrSub(data,10); }}, { data: 'Size',render:function(data,type,full,meta){ return git.GetStrSub(data,10); },createdCell:function(td, cellData, rowData, row, col){ if(!git.IsEmpty(cellData) && cellData.length>10){ $(td).popover({container:"body",content:cellData,trigger:"hover",placement:"bottom",delay:{show:500}}); } }}, { data: 'CateName'}, { data: 'UnitName'}, { data: 'MaxNum'}, { data: 'MinNum'}, { data: 'AvgPrice'}, { data: 'Display',render:function(data,type,full,meta){ return git.GetStrSub(data,10); },createdCell:function(td, cellData, rowData, row, col){ if(!git.IsEmpty(cellData) && cellData.length>10){ $(td).popover({container:"body",content:cellData,trigger:"hover",placement:"bottom",delay:{show:500}}); } }} ], aoColumnDefs:[ { "sWidth": "15px", "aTargets": [0] } ], oLanguage:{ sEmptyTable:"没有查询到任何数据" } }); var pageInfo=result.PageInfo; if(pageInfo!=undefined){ current.find("#myMinPager").minpager({ pagenumber: pageInfo.PageIndex, recordCount: pageInfo.RowCount, pageSize: pageInfo.PageSize, buttonClickCallback: DataServer.PageClick }); } DataServer.BindEvent(); }, BindEvent:function(){ if(defaultOption.Mult){ current.find("#tabInfo").find("input[name='item_all']").click(function(event) { var flag=$(this).attr("checked"); if(flag){ current.find("#tabInfo").find("input[name='product_item']").attr("checked",true); }else{ current.find("#tabInfo").find("input[name='product_item']").attr("checked",false); } }); } else{ current.find("#tabInfo").find("input[name='item_all']").hide(); current.find("#tabInfo").find("input[name='product_item']").click(function(event) { current.find("#tabInfo").find("input[name='product_item']").attr('checked', false); $(this).attr("checked",true); }); } //搜索 current.find(".search").find('button[data-command="Search"]').click(function(event) { DataServer.PageClick(1,10); }); }, GetSelect:function(){ var list=[]; current.find("#tabInfo").find("input[name='product_item']").each(function(i,item){ var flag=$(item).attr("checked"); if(flag){ var value=$(item).attr("data-full"); list.push(JSON.parse(value)); } }); return list; } } var submit = function (v, h, f) { if (v == 1) { var list=DataServer.GetSelect(); if(list==undefined || list.length==0){ $.jBox.tip("请选择产品","warn"); return false; } if (defaultOption.callBack != undefined && typeof (defaultOption.callBack) == "function") { if(defaultOption.Mult){ defaultOption.callBack(list); }else{ defaultOption.callBack(list[0]); } } } }; $(this).bind(defaultOption.EventName, function () { $.jBox.open("get:/Storage/Product/Dialog", "选择产品", 850, 600, { buttons: { "选择": 1, "关闭": 2 }, submit: submit, loaded: function (h) { current=h; DataServer.PageClick(1,10); } }); }); }; })(jQuery);
类似于这种的组件,在项目中有大量使用,如果能够做到全局公共并且方便使用这就是极佳的。最终形成组件也是有发展历程的: 开始只是一个简单的额对话框显示表格并且选中产品,慢慢的又有更多的需求,比如要能够搜索,要能够分页,要支持多选以及单选等等,双几行能够自动选择等一系列问题。其实你做好了公共组件在后期的开发中也就省事了。
3. 代码的套路
10个人就有10种代码风格,这种相互改代码会让人崩溃,人员的离职,没有文档和注释的说明,字段和数据库的对应不起来,各种让人吐的问题。不要太多的花样,定义好套路,所有的代码必须遵循这种套路你会快很多,最关键的是找bug的速度回快很多。
/** * *财务管理-应收实收 * **/ var FinanceBillManager = { Server: function () { var config = (function () { var URL_Add = "/Finance/BillAjax/AddRec"; var URL_GetList = "/Finance/BillManagerAjax/GetList"; var URL_GetDetail = "/Finance/BillManagerAjax/GetDetail"; var URL_Delete = "/Finance/BillManagerAjax/Delete"; var URL_Cancel = "/Finance/BillManagerAjax/Cancel"; var URL_Audite = "/Finance/BillManagerAjax/Audite"; var URL_ToExcel = "/Finance/BillManagerAjax/ToExcel"; var URL_AddPay = "/Finance/BillAjax/AddPay"; var URL_PayDetail = "/Finance/BillAjax/AddRec"; return { URL_GetList: URL_GetList, URL_Add: URL_Add, URL_Delete: URL_Delete, URL_Cancel: URL_Cancel, URL_Audite: URL_Audite, URL_ToExcel: URL_ToExcel, URL_AddPay: URL_AddPay, URL_PayDetail: URL_PayDetail, }; })(); //数据操作服务 var dataServer = (function ($, config) { //查询分页列表 var Add=function(data,callback){ $.gitAjax({ url: config.URL_Add, data: data, type: "post", dataType: "json", success: function (result) { if(callback!=undefined && typeof callback=="function"){ callback(result); } } }); } var GetList=function(data,callback){ $.gitAjax({ url: config.URL_GetList, data: data, type: "post", dataType: "json", success: function (result) { if(callback!=undefined && typeof callback=="function"){ callback(result); } } }); } var Delete=function(data,callback){ $.gitAjax({ url: config.URL_Delete, data: data, type: "post", dataType: "json", success: function (result) { if(callback!=undefined && typeof callback=="function"){ callback(result); } } }); } var Cancel=function(data,callback){ $.gitAjax({ url: config.URL_Cancel, data: data, type: "post", dataType: "json", success: function (result) { if(callback!=undefined && typeof callback=="function"){ callback(result); } } }); } var Audite=function(data,callback){ $.gitAjax({ url: config.URL_Audite, data: data, type: "post", dataType: "json", success: function (result) { if(callback!=undefined && typeof callback=="function"){ callback(result); } } }); } var ToExcel=function(data,callback){ $.gitAjax({ url: config.URL_ToExcel, data: data, type: "post", dataType: "json", success: function (result) { if(callback!=undefined && typeof callback=="function"){ callback(result); } } }); } var AddPay=function(data,callback){ $.gitAjax({ url: config.URL_AddPay, data: data, type: "post", dataType: "json", success: function (result) { if(callback!=undefined && typeof callback=="function"){ callback(result); } } }); } return { Add: Add, GetList: GetList, Delete: Delete, Cancel: Cancel, Audite: Audite, ToExcel: ToExcel, AddPay: AddPay, } })($, config); return dataServer; }, PageClick:function(PageIndex,PageSize){ $.jBox.tip("正在努力加载数据...","loading"); var Server=FinanceBillManager.Server(); var search=FinanceBillManager.GetSearch(); search["PageIndex"]=PageIndex; search["PageSize"]=PageSize; Server.GetList(search,function(result){ FinanceBillManager.SetTable(result); $.jBox.closeTip(); }); }, Refresh:function(){ var PageSize=$("#mypager").pager("GetPageSize"); var PageIndex=$("#mypager").pager("GetCurrent"); $.jBox.tip("正在努力加载数据...","loading"); var Server=FinanceBillManager.Server(); var search=FinanceBillManager.GetSearch(); search["PageIndex"]=PageIndex; search["PageSize"]=PageSize; Server.GetList(search,function(result){ FinanceBillManager.SetTable(result); $.jBox.closeTip(); }); }, SetTable:function(result){ $("#tabList").DataTable({ destroy: true, data:result.Result, paging:false, searching:false, scrollX: false, bAutoWidth:false, bInfo:false, ordering:false, columns: [ { data: 'SnNum' ,render:function(data, type, full, meta){ return "<input type='checkbox' name='bill_item' value='"+data+"' data-full='"+JSON.stringify(full)+"'/>"; }}, { data: 'BillNum'}, { data: 'Title'}, { data: 'CateName'}, { data: 'FromName'}, { data: 'Amount',render:function(data,type,full,meta){ return git.ToDecimal(data,2); }}, { data: 'RealPayAmount',render:function(data,type,full,meta){ return git.ToDecimal(data,2); }}, { data: 'LeavAmount',render:function(data,type,full,meta){ if(parseFloat(data)>0){ return '<span class="label label-warning">'+git.ToDecimal(data,2)+'</span>'; } else{ return "0.00"; } }}, { data: 'Status',render:function(data,type,full,meta){ if(data==EFinanceStatusJson.Pass){ return '<span class="label label-success">'+git.GetEnumDesc(EFinanceStatus,data)+'</span>'; }else if(data==EFinanceStatusJson.NotPass){ return '<span class="label label-warning">'+git.GetEnumDesc(EFinanceStatus,data)+'</span>'; }else{ return git.GetEnumDesc(EFinanceStatus,data); } }}, { data: 'CreateTime',render:function(data,type,full,meta){ return git.JsonToDateTime(data); }}, { data:"ID",render:function(data,type,full,meta){ var html=""; if(full.Status==EFinanceStatusJson.Wait || full.Status==EFinanceStatusJson.NotPass){ html+='<a class="edit" href="javascript:void(0)">编辑</a> '; } if(full.Status==EFinanceStatusJson.Wait || full.Status==EFinanceStatusJson.NotPass){ html+='<a class="audite" href="javascript:void(0)">审核</a> '; } html+='<a class="view" href="javascript:void(0)">查看</a> '; if(full.Status==EFinanceStatusJson.Wait || full.Status==EFinanceStatusJson.NotPass){ html+='<a class="delete" href="javascript:void(0)">删除</a> '; } if(full.Status==EFinanceStatusJson.Pass || full.Status==EFinanceStatusJson.PayPart){ html+='<a class="pay" href="javascript:void(0)">收款</a> '; } return html; }} ], aoColumnDefs:[ { "sWidth": "15px", "aTargets": [0] } ], oLanguage:{ sEmptyTable:"没有查询到任何数据" } }); var pageInfo=result.PageInfo; if(pageInfo!=undefined){ $("#mypager").pager({ pagenumber: pageInfo.PageIndex, recordCount: pageInfo.RowCount, pageSize: pageInfo.PageSize, buttonClickCallback: FinanceBillManager.PageClick }); } //绑定编辑 删除事件 FinanceBillManager.BindEvent(); }, BindEvent:function(){ $("#tabList").find("tbody td").popover({ container:"body", }); $("#tabList").find("a.edit").click(function(){ var SN=$(this).parent().parent().find("input[name='bill_item']").val(); FinanceBillManager.Detail(SN,"Edit"); }); //审核 $("#tabList").find("a.audite").click(function(){ var SN=$(this).parent().parent().find("input[name='bill_item']").val(); FinanceBillManager.Detail(SN,"Audite"); }); //查看 $("#tabList").find("a.view").click(function(){ var SN=$(this).parent().parent().find("input[name='bill_item']").val(); FinanceBillManager.Detail(SN,"View"); }); $("#tabList").find("a.delete").click(function(){ var SN=$(this).parent().parent().find("input[name='bill_item']").val(); var submit=function(v,h,f){ if(v=="ok"){ var list=[]; list.push(SN); var param={}; param["list"]=JSON.stringify(list); var Server=FinanceBillManager.Server(); Server.Delete(param,function(result){ $.jBox.tip(result.Message,"success"); var pageSize=$("#mypager").pager("GetPageSize"); FinanceBillManager.PageClick(1,pageSize); }); } } $.jBox.confirm("确定要删除吗?", "提示", submit); }); //付款 $("#tabList").find("a.pay").click(function(){ var SN=$(this).parent().parent().find("input[name='bill_item']").val(); FinanceBillManager.Pay("",SN,"Add"); }); }, SelectAll:function(item){ var flag=$(item).attr("checked"); if(flag){ $("#tabList").find("input[name='bill_item']").attr("checked",true); }else{ $("#tabList").find("input[name='bill_item']").attr("checked",false); } }, GetSelect:function(){ var list=[]; $("#tabList").find("input[name='bill_item']").each(function(i,item){ var flag=$(item).attr("checked"); if(flag){ var value=$(item).val(); list.push(value); } }); return list; }, GetSearch:function(){ var searchBar=$("div[data-condition='search']"); var BillNum=searchBar.find("input[name='BillNum']").val(); var CateNum=searchBar.find("select[name='CateNum']").val(); var FromName=searchBar.find("input[name='FromName']").val(); var Title=searchBar.find("input[name='Title']").val(); var ContractNum=searchBar.find("input[name='ContractNum']").val(); var Status=$("div[data-group='Status']").find("button.disabled").attr("value"); var BeginTime=searchBar.find("input[name='BeginTime']").val(); var EndTime=searchBar.find("input[name='EndTime']").val(); var search={}; search["BillNum"]=BillNum; search["CateNum"]=CateNum; search["FromName"]=FromName; search["Title"]=Title; search["ContractNum"]=ContractNum; search["Status"]=Status; search["BeginTime"]=BeginTime; search["EndTime"]=EndTime; return search; }, Detail:function(SnNum,Command){ var currentSnNum=SnNum; var submit=function(v,h,f){ if(v=="1"){ var BillNum=h.find('input[name="BillNum"]').val(); var SnNum=h.find('input[name="SnNum"]').val(); var CateNum=h.find('select[name="CateNum"]').val(); var FromName=h.find('input[name="FromName"]').val(); var ToName=h.find('input[name="ToName"]').val(); var Amount=h.find('input[name="Amount"]').val(); var PrePayCount=h.find('input[name="PrePayCount"]').val(); var Remark=h.find('input[name="Remark"]').val(); var Title=h.find('input[name="Title"]').val(); var Remark=h.find('input[name="Remark"]').val(); var LastTime=h.find('input[name="LastTime"]').val(); var Title=h.find('input[name="Title"]').val(); var param={}; param["BillNum"]=BillNum; param["SnNum"]=SnNum; param["CateNum"]=CateNum; param["FromName"]=FromName; param["ToName"]=ToName; param["Amount"]=Amount; param["PrePayCount"]=PrePayCount; param["Status"]=1; //审核通过 param["Remark"]=Remark; param["LastTime"]=LastTime; param["Title"]=Title; var Server=FinanceBillManager.Server(); Server.Add(param,function(result){ $.jBox.tip(result.Message,"success"); var pageSize=$("#mypager").pager("GetPageSize"); FinanceBillManager.PageClick(1,pageSize); }); }else if(v=="2"){ }else if(v=="3"){ var param={}; param["SnNum"]=currentSnNum; param["Status"]=2; //审核通过 var Server=FinanceBillManager.Server(); Server.Audite(param,function(result){ $.jBox.tip(result.Message,"success"); var pageSize=$("#mypager").pager("GetPageSize"); FinanceBillManager.PageClick(1,pageSize); }); }else if(v=="4"){ var param={}; param["SnNum"]=currentSnNum; param["Status"]=3; //审核通过 var Server=FinanceBillManager.Server(); Server.Audite(param,function(result){ $.jBox.tip(result.Message,"success"); var pageSize=$("#mypager").pager("GetPageSize"); FinanceBillManager.PageClick(1,pageSize); }); } } // 1确定 2 关闭 3 审核通过 4 审核不通过 if(Command=="View"){ $.jBox.open("get:/Finance/Bill/AddRec?SnNum="+SnNum, "查看应收", 670, 350, { buttons: {"关闭":2 } ,submit:submit,loaded:function(h){ h.find('input,select').attr('disabled',true); }}); }else if(Command=="Audite"){ $.jBox.open("get:/Finance/Bill/AddRec?SnNum="+SnNum, "审核应收", 670, 350, { buttons: {"审核通过": 3, "审核不通过": 4,"关闭":2 } ,submit:submit,loaded:function(h){ h.find('input,select').attr('disabled',true); }}); }else if(Command=="Add"){ $.jBox.open("get:/Finance/Bill/AddRec", "新增应收", 670, 350, { buttons: {"确定": 1, "关闭":2 } ,submit:submit,loaded:function(h){ }}); }else if(Command=="Edit"){ $.jBox.open("get:/Finance/Bill/AddRec?SnNum="+SnNum, "编辑应收", 670, 350, { buttons: {"确定": 1, "关闭":2 } ,submit:submit,loaded:function(h){ }}); } }, Pay:function(SnNum,BillSnNum,Command){ var currentSnNum=SnNum; var submit=function(v,h,f){ if(v=="1"){ var PayNum=h.find('input[name="PayNum"]').val(); var SnNum=h.find('input[name="SnNum"]').val(); var PayType=h.find('select[name="PayType"]').val(); var BankName=h.find('input[name="BankName"]').val(); var Amount=h.find('input[name="Amount"]').val(); var PayTime=h.find('input[name="PayTime"]').val(); var Remark=h.find('input[name="Remark"]').val(); var BillSnNum=h.find('input[name="BillSnNum"]').val(); var param={}; param["PayNum"]=PayNum; param["SnNum"]=SnNum; param["PayType"]=PayType; param["BankName"]=BankName; param["Amount"]=Amount; param["PayTime"]=PayTime; param["Remark"]=Remark; param["BillSnNum"]=BillSnNum; var Server=FinanceBillManager.Server(); Server.AddPay(param,function(result){ $.jBox.tip(result.Message,"success"); var pageSize=$("#mypager").pager("GetPageSize"); FinanceBillManager.PageClick(1,pageSize); }); }else if(v=="2"){ } } // 1确定 2 关闭 if(Command=="Add"){ $.jBox.open("get:/Finance/Bill/AddPay?BillSnNum="+BillSnNum, "新增实收", 670, 300, { buttons: {"确定": 1, "关闭":2 } ,submit:submit,loaded:function(h){ }}); }else if(Command=="Edit"){ $.jBox.open("get:/Finance/Bill/AddPay?BillSnNum="+BillSnNum+"&SnNum="+SnNum, "编辑实收", 670, 300, { buttons: {"确定": 1, "关闭":2 } ,submit:submit,loaded:function(h){ }}); } }, ToolBar:function(){ //工具栏按钮点击事件 $("div.toolbar").find("a.btn").click(function(){ var command=$(this).attr("data-command"); if(command=="Add"){ FinanceBillManager.Detail(undefined,"Add"); }else if(command=="Edit"){ var list=FinanceBillManager.GetSelect(); if(list.length==0){ $.jBox.tip("请选择要编辑的项","warn"); return false; } var SN=list[0]; FinanceBillManager.Detail(SN,"Edit"); }else if(command=="Delete"){ var submit=function(v,h,f){ if(v=="ok"){ var list=FinanceBillManager.GetSelect(); if(list.length==0){ $.jBox.tip("请选择要删除的项","warn"); return false; } var param={}; param["list"]=JSON.stringify(list); var Server=FinanceBillManager.Server(); Server.Delete(param,function(result){ $.jBox.tip(result.Message,"success"); var pageSize=$("#mypager").pager("GetPageSize"); FinanceBillManager.PageClick(1,pageSize); }); } } $.jBox.confirm("确定要删除吗?", "提示", submit); }else if(command=="Excel"){ var Server=FinanceBillManager.Server(); var search=FinanceBillManager.GetSearch(); Server.ToExcel(search,function(result){ if(result.Code==1000){ var path = unescape(result.Message); window.location.href = path; }else{ $.jBox.info(result.Message, "提示"); } }); }else if(command=="Refresh"){ FinanceBillManager.Refresh(); } }); //搜索 高级搜索按钮 var searchBar=$("div[data-condition='search']"); searchBar.find("a[data-command='search']").click(function(){ FinanceBillManager.PageClick(1,10); }); searchBar.find("button[data-command='Advanced']").click(function(){ var display=$("div.formsearch").css("display"); if(display=="block"){ $("div.formsearch").hide(300); }else{ $("div.formsearch").show(300); } }); searchBar.find("button[data-command='Clear']").click(function(){ //清空搜索条件 searchBar.find("input[name='BillNum']").val(""); searchBar.find("select[name='CateNum']").val(""); searchBar.find("input[name='FromName']").val(""); searchBar.find("input[name='Title']").val(""); searchBar.find("input[name='ContractNum']").val(""); searchBar.find("input[name='BeginTime']").val(""); searchBar.find("input[name='EndTime']").val(""); }); //状态按钮处理 $("div[data-group='Status']").find("button").click(function(){ $("div[data-group='Status']").find("button").removeClass("disabled"); $(this).addClass("disabled"); FinanceBillManager.PageClick(1,10); }); //加载默认数据 FinanceBillManager.PageClick(1,10); } }
在吉特仓储管理系统中,经过多次的改版之后,最终确定了JS的代码风格:
(1) 不一定要遵循世间的套路什么驼峰命名,帕斯卡命名,定好符合自己的规则
(2) 前后端数据传输Key名称尽量和Model或者数据库字段一致,虽然违反了一定传统的命名规则
(3) 每个模块定义一个对象,所有的操作方法都封装到对象中
(4) 尽量做到UI分离,多使用事件驱动
ITopClient client = new TopClientDefault(); string list = WebUtil.GetFormValue<string>("list"); string CompanyID = this.CompanyID; Dictionary<string, string> dic = new Dictionary<string, string>(); dic.Add("CompanyID", CompanyID); dic.Add("List", list); string result = client.Execute(DepartApiName.DepartApiName_Delete, dic); return Content(result);
调用API使用而代码示例
(1) 所有的代码都遵循如上操作方式,不得标新立异再做一套
(2) 请求API全部是使用POST的方式 (根据自己的情况来,有些人说这种做法不好,我觉得挺好)
(3) 统一的数据返回格式JSON,也便于做拦截处理,代码要结构化
代码套路多,这里不多说 有兴趣去GitHub上下载开源版本的代码下来观摩
三. 八九月我激动过度
从未如此为项目激动,想着有一天软件能够被客户所能够接受,八九月的确有点出乎我的意料之外,吉特仓储管理系统已经不温不火的做了四年了,改版也不知道多少,虽然也有些客户在使用,但那终究不是我满意的结果,不是对客户不满意而是我在这些客户身上没有获取真正的价值,因为很多项目都是靠时间堆积起来的。八月九月两个月客户咨询的客户有点多,最终选择了几个客户合作, 项目金额超10W。 对于我现在一个兼职开发者(为生活所迫)来说的确是一件可喜可贺的事情,不要拿这个跟某某公司来对比,我也从来不想超过哪个公司,我只想认真的做好一个自己想做的软件。
10W+ 这远超过我工资收入,当然忙我也找了一些小伙伴帮忙,但是现在的确要比之前的处理速度要快很多。
8月初有个山西的客户联系我了,让我帮其弄一个仓库系统,说是给他们县城的小型加工厂使用,也就是云仓储。其实正中我下怀, 费用也就2W 我没有多要价,也想着拿新版本仓储系统试水, 20来天项目上线使用,客户爽快付款90%,听他说之前有个团队给他们弄了几个月还不是他想要的。
1. 项目大底开发速度高了很多
这几个客户开发效率自己明显推进速度高了很多,除了前期自己积累了大量的公共组建,现有成熟的软件之外,也积累了大量的客户犯错案例
2. 正确的引导客户
大多数客户其实不清楚自己要做什么东西的,如果对方是程序员我估计你会很沟通。首先给客户一个演示版本,让客户看是不是他想要的,如果不是那就不用谈了,避免浪费双方的时间
3. 合理的建议
在某种情况下你要决定的相信你的专业能力要比客户强,给客户实际的可见的演示软件,还要给客户说具体的案例,特别是案例中会出现的问题以及解决方式,合理的处理建议会让客户相信你的专业度
4. 多为客户考虑
多为客户考虑,有时候客户考虑的功能很少,你要合理的给客户新增一些业务功能,同时也要删减一些不必要的需求,你的目的就是为他解决这个问题
5. 不要一味的谈技术
可能客户会处于礼貌的听你说你的技术如何的牛逼,但是客户绝大时候是听不懂在说什么的。 我会问客户的仓库规划情况,销售情况,打印情况,作业包装情况等,侧面找方式阐述你的解决方案
四. 软件版权保护
1. 软件著作权登记证书
不得不说有些人真的很可恶,吉特仓储管理系统(GitWMS)之前有一个版本开源之后,自己下载闷声去做客户就不说什么了,但是有些人却公开出售源码,之前还和某平台闹过一次。 这里不多说了,其实当时已经在申请软件著作权登记,这个登记证书终于下来了。
虽然很多人说这个东西没卵用,有时候这个东西是必须的,最起码在给别人说这个东西是我自己的时候有底气一点,看代码别人不承认是你的东西啊。
2. 如何看待开源问题
我从内心里面承认开源给我带来了很多的让我意向不到的好处,但是绝大部分来下载此代码的人都是程序员或者软件公司,他们问我出售价格以及使用技术,其实我更想听到合作意向,但是这种声音少之又少,同时还有更多的人或者企业拿到之后默默的去修改代码,去掉版权信息然后去做自己的客户,做自己的私单,然后有问题了就来像我咨询。其实我内心是高兴和纠结的,还有人说我是花了10块钱买的代码,留的联系方式是你的说可以提供技术支持,10块钱一顿饭都不够,而且钱不在我口袋。
(1). 开源的目的
我真正开源的目的是想让更多人了解我的项目,供大家一个可以参考学习的案例,最初我也没有想过有人可以拿着去卖钱或者直接修改给客户去使用,就算申明不可以给客户使用有些人还是会默默的修改不露声色的去给客户使用,就好比说不能兜售源码但是还是有人默默的在兜售源码。
(2). 你为什么不全部开源
我开源了所有的业务代码,当然我也可以负全部责任的告诉你,新版本的是没有开源的(不要问我为什么),以及底层的一些操作基本没有公开源代码,我相信代码已经流露在外面了。 公开的代码是2.0版本的代码,其中删除了一些非仓库业务的功能,其余功能模块代码完全公开了,保证是一个可以正常运行,流程可以走通的,数据库结构全部公开的程序,没有任何的保留。
总是有人问我质问我,你这些都开源了为什么还有底层的一些东西不开源,你就得将所有的代码全部开源,要不然你开源干嘛。对于这种质问我很无奈, 打个不恰当的比喻,.NET现在也要开源了,为什么他不开源他依托的Windows系统, 我想我也是这么想的。希望大家能够读懂我的意思,有时候想做一个好事反而遭来辱骂声其实心里很不爽。
3. 开源带来更多的分享者以及潜在客户
其实我并不是那么在乎有人拿着我东西去做其他的客户,只希望他们能够保留我的版权标题等基本信息,但是很多人做不到。自从开源之后我的确有很多开发者对此表示了兴趣,仔细我的人也很多,其实认真去看的人不多。每个人心里都有这一个心理,开源的东西就是好东西,但是有多少人真正去看过开源的代码,更有甚者那些优秀的开源项目你下载之后当作安心石的代码你能够真的看得懂么。"如果开源你看不懂,那和不开源有什么区别" ,开源只是为那些懂的人而开源的。 当然也有很多人下载代码之后给我提了好多的意见,这些意见都是非常宝贵的,我在此非常的感谢这些人,分享各位的想法和经验,我才能做得更好。
五. 学习的参考对象
在博客园也算混了很久了,其中牛人比较多。至于哪些人是牛人我不多说,在开发吉特仓储管理系统的时候,我也参考过他们的模式。
吉日嘎拉: http://www.cnblogs.com/jirigala/ 当年他所开发的 通用权限管理系统
魏琼东: http://www.cnblogs.com/eastjade/ AgileEAS.NET SOA 中间件平台,医疗系统开发
路过秋天: http://www.cnblogs.com/cyq1162/ QBlog 博客
李天平: http://www.cnblogs.com/ltp/category/44293.html 代码生成器
麦舒: http://www.cnblogs.com/ansiboy/category/322450.html ALinq 当年也算雄霸一方
当然还有更多的优秀作品, 我参考过他们的很多运营模式。其中吉日嘎啦和魏琼东的相对是偏重业务行业性的, 最起码给我的感觉是这样的,我没有深入的去了解过他们的代码。其实我个人的侧重点也是如此,我更想希望侧重于偏重行业性质的软件开发。
之前收入一直很低,其实老婆给我了很大的支持,我也非常害怕她说我为什么不找个正经的工作去干,天天折腾这些东西。我给她玩笑的说,我说我用吉特仓储管理系统可以赚到100W, 然后我也就笑笑,她也就笑笑。我说知道你们不会相信我的,我说赚不到100W我赚50W也可以,再不行30W也可以。
我一直坚信自己能够做到,当然过去的几年我也曾一度怀疑自己,虽然现在这个也很遥远,但是我希望自己依旧坚持。
参考了很多产品,大体上可以分为两种: (1) 以技术型为主,也就是技术开发框架或者组件, 麦舒的就是典型的案例 (2) 以业务型为主,老魏的医疗系统绝对的偏向行业性质
且不论这两者之间各有什么优缺点,但坚信一点只要你坚持去做持续改进都能够做出好东西来。
此文有点啰嗦,有不正之处望请谅解,有心在业余时间发展个人仓储系统,如果对此感兴趣的朋友可以下载开源版本的代码研究并和本人交流,希望能够给到更多更好的建议。
作者:情缘
出处:http://www.cnblogs.com/qingyuan/
关于作者:从事仓库,生产软件方面的开发,在项目管理以及企业经营方面寻求发展之路
版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
联系方式: 个人QQ 821865130 ; 仓储技术QQ群 88718955,142050808 ;
吉特仓储管理系统 开源地址: https://github.com/hechenqingyuan/gitwms