博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
吉特仓库管理系统-.NET打印问题总结
阅读量:6907 次
发布时间:2019-06-27

本文共 21660 字,大约阅读时间需要 72 分钟。

 

  在仓储系统的是使用过程中避免不了的是打印单据,仓库系统中包含很多单据:入库单,出库单,盘点单,调拨单,签收单等等,而且还附带着很多的条码标签的打印。本文在此记录一下一个简单的打印问题处理方式。处理问题环境如下:

在做标签打印的时候,同事说要使用OPOS指令来打印小单据标签,但是后面送过来的打印机却是斑马打印机,不支持OPOS指令打印,于是很无奈。当时自己提出过另外一种解决方案,那就是使用第三方打印软件,然后使用.NET来调用这个软件打印,这个也是本人之前一直使用的打印方式,比较有名的第三方打印软件bartender. 但是的确这个实施不方便。于是自己使用了最原始用.NET PrintDocument 来打印输出。

 

  一. 打印需求

    1. 能够打印Logo,也就是打印图片

    2. 打印固定文字,并且能够控制文字大小

    3. 打印变量信息

    4. 打印列表信息

    5. 打印条码 二维码

    6. 打印纸张大小的控制

 

    以上是个人总结出来做单据打印的一些常用功能,也是比较实用的一些要求,相信做过打印单据的同学应该都遇到过。 在以上几点的需求上有几个比较麻烦的是打印列表,相对比较复杂。对于条码以及二维码的打印其实就是打印图片,使用相应的组件来生成图片打印即可。

 

  二. GDI+绘图打印

    不用多说,这个肯定是PrintDocument 打印的重点,可能一般做Web开发的同学对打印关注的较少,这里先说WinForm 客户端程序的打印,后续讲解Web上的打印问题。在博客园中搜索了一篇不错的文章介绍GDI画图

    《》  可以查看了解一下GDI+绘图的基本知识

  

    PrintDocument 文档打印类中有一个Print()打印方法,用于触发打印,PrintPage 事件则在打印命令执行时触发,我们可以在这个事件的方法中绘图,用于实现要打印的内容。先看看一个简单的案例

private void Print(object sender, System.Drawing.Printing.PrintPageEventArgs e)        {            int top = 5;            int left = 4;            Brush bru = Brushes.Black;            Graphics g = e.Graphics;            for (int i = 0; i < listResult.Count; i++)            {                BarCodeEntity entity = listResult[i];                if (entity != null)                {                    int xMo = i % 4;                    int yMo = i / 4;                    int width = 90;                    int height = 79;                    g.DrawString("合格证", new Font("黑体", 8, FontStyle.Bold), bru, new PointF(left + width * xMo + 22, height * yMo + top));                    g.DrawString("本产品经检验合格准予出厂", new Font("黑体", 4.7f, FontStyle.Bold), bru, new PointF(left + width * xMo, height * yMo + 13 + top + 5));                    g.DrawString("检验员:" + entity.Number + "号  保质期:三年", new Font("黑体", 4.7f, FontStyle.Bold), bru, new PointF(left + width * xMo, height * yMo + 26 + top + 5));                    g.DrawString("生产日期:" + entity.Time, new Font("黑体", 4.7f, FontStyle.Bold), bru, new PointF(left + width * xMo, height * yMo + 39 + top + 5));                    float length = 7f;                    float fontWidth = entity.CompanyName.IsEmpty() ? 0 : length * (entity.CompanyName.Length * 1.0f);                    float fontSize = 4.4f;                    int marginLeft = 0;                    if (entity.CompanyName.Length > 13)                    {                        fontSize = 4.0f;                    }                    else                    {                        marginLeft = (int)((91 - fontWidth) / 2);                    }                    g.DrawString(entity.CompanyName, new Font("黑体", fontSize, FontStyle.Bold), bru, new PointF(left + width * xMo + marginLeft, height * yMo + 52 + top + 5));                }            }        }
打印绘图简单案例

     Brush bru = Brushes.Black;

     Graphics g = e.Graphics;

    以上两个是绘图的重点,定义了画笔以及绘图对象。在PrintPage事件中可以获得绘图对象,我们利用此对象来绘制图片,文字,条码,二维码等

public void DrawImage(Image image, PointF point);public void DrawImageUnscaled(Image image, Point point);public void DrawLine(Pen pen, Point pt1, Point pt2);public void DrawPie(Pen pen, Rectangle rect, float startAngle, float sweepAngle);public void DrawRectangle(Pen pen, Rectangle rect);public void DrawString(string s, Font font, Brush brush, RectangleF layoutRectangle);

    以上是常用的一些绘图方法,绘制图片,直线,圆,方框,字符串等等。可以到微软MSDN官网查看更多的绘图方法

    

    以上是一个简单的打印示例,画图方式比较原始,但是包含了不同的文字,二维码图片等。

 

  三. PrintDocument使用的缺点

    以上代码打印出来,貌似效果还不错,在小标签纸上效果也挺漂亮.但是问题来了,有一天客户提出需求说,我现在要换打标签纸了,而且文字的排版方式也要稍微做修改。从上面的一段示例代码来看,打印无非就是输出内容(文字,线框,图片等). 然后就是定义响应的坐标即可,也就是点阵类型的打印。将标签纸作为一个画布,然后计算好坐标在相应的位置输出内容即可。 如果标签纸大小变了(排版),那么意味着坐标点也要重新计算,一下子是欲哭无泪。

 

    当时他们要求使用OPOS指令,ZPL指定也就是因为可以自定义模板.但是不同的打印机支持指令性质不一样,那就只能自己定义一套模板规则来满足要求,并且摆脱不同打印机厂商的限制。

{
{Logo}}
预定凭条
保税区1店
No.150 page.1
{
{OrderCode}}
单据号:{
{OrderCode}}
提货时间:{
{DtReceive}}
提货点:{
{ReceiveAddress}}
联系人:{
{ReceiveUser}}
联系电话:{
{ReceiverPhone}}
时间:{
{DtCreate}}
-------------
序号
货号
品名
数量
单价
金额
-------------
{
{Index}}
{
{StrID}}
{
{StrName}}
{
{DCount}}
*
{
{DPrice}}
=
{
{DAmount}}
-------------
联机刷卡
人民币{
{DAmount}}
--------------
商品数:{
{DCount}}
总金额:{
{DAmount}}
{
{OrderCode}}
--------------
谢谢惠顾,欢迎再次光临
提货凭据,请妥善保管
客服热线:*******
模板定义预览

 

    为了尽快应付工作问题,在短时间仓促定义了如上模板,用于设置定义指令。以上模板主要内容是控制坐标点以及打印内容的动态体会:

    Page标签: 用于定义打印纸张, 属性:Width 纸张宽度, Heigth 纸张高度, DefaultPrinter 默认使用打印机的名称

    Line标签: 在打印过程中预定都是以行来打印,只是打印行坐标不一样(这样可以控制交叉的情况) 。 定义打印一行, Height 打印行的高度

    Image标签: 用于打印图片 Left 用于设置距离Line 左边的距离 Top 用于设置距离Line 顶部的距离,Image 必须包含到Line标签中。

    Text标签:用于打印文本信息  Left 用于设置距离Line 左边的距离 Top 用于设置距离Line 顶部的距离,FontSize 用于设置打印字体大小, Text必须包含到Line标签中。

    QRCode标签:用于打印二维码图片, Left 用于设置距离Line 左边的距离 Top 用于设置距离Line 顶部的距离

    BarCode标签:用于打印条码图片,Left 用于设置距离Line 左边的距离 Top 用于设置距离Line 顶部的距离.Width 设置条码的长度,Height 设置条码的高度

    Loop标签:用于循环打印,必须包含Line标签,也就是循环打印Line标签。Values 设置数据源的Key值

    {

{}}指令:用于替换的变量占位符

 

    

    画了一个草图理解标签中的属性Left,Top. Line都是以纸张横坐标为0的前提下打印的,也就是说Left是以坐标X=0的情况为计算标准, 而Top 是以Line的内聚为标准,如果有多个Line 则需要先计算上层所有Line的高度总和才能定义Top的Y轴的值。

 

  四. 数据源的定义

    在上面的设计中利用到了占位符,这个也是比较合理的一种做法,在打印的过程中替换占位符中的内容。 在系统启动的时候回检测模板中的所有占位符。

    数据源的定义是以Dictionary<string, object> 为基础类型,为什么什么这么做,我在占位符 比如{

{Logo}} 定义如上,我们就在Dictionary 查找LogoKey的值。然后将其中的内容值替换即可

    

    如果是Loop标签的数据源如何处理: 在Loop标签中定义了Values的属性,其属性值也就是Dictionary 中的key,查找得到之后仍然是一个List<Dictionary<string,object>>的数据值,这边便于循环和Key值得查找。

     

dic = new Dictionary
(); dic.Add("Logo", @"D:\222.jpg"); dic.Add("OrderNO", "V3454596546565"); dic.Add("Cashier", "菜霞"); dic.Add("EndPoint", "634"); dic.Add("Number", "120457"); dic.Add("CreateTime", DateTime.Now.ToString("yyyy-MM0dd HH:mm:ss")); dic.Add("Amount", "65223.00"); dic.Add("QRCode", "V3454596546565"); List
> Info = new List
>() { new Dictionary
() { { "No", "1"},{ "ProductNum", "120223"},{ "ProductName", "中华烟"},{ "Qty", "2"},{ "Price", "49"},{ "Amount", "98"} }, new Dictionary
() { { "No", "2"},{ "ProductNum", "565666"},{ "ProductName", "玻璃杯"},{ "Qty", "7"},{ "Price", "45"},{ "Amount", "45545"} }, new Dictionary
() { { "No", "3"},{ "ProductNum", "897845"},{ "ProductName", "烟灰缸"},{ "Qty", "5"},{ "Price", "2435"},{ "Amount", "67767"} }, new Dictionary
() { { "No", "4"},{ "ProductNum", "904395"},{ "ProductName", "茶几"},{ "Qty", "3"},{ "Price", "45245"},{ "Amount", "6767"} }, }; dic.Add("Qty", "5"); dic.Add("TotalAmount", "1045.00"); dic.Add("DiscountQty", "1"); dic.Add("DiscountAmount", "40.00"); dic.Add("Discount", "47.5"); dic.Add("DiscountMon", "1958.00"); dic.Add("List", Info);
数据源基本格式

 

public partial class DocumentPrintControl    {        public DocumentPrintControl() { }        public DocumentPrintControl(string printName,string filePath,Dictionary
dataSource,bool isAutoHeigth) { this.PrintName = printName; this.FilePath = filePath; this.DataSource = dataSource; this.IsAutoHeigth = isAutoHeigth; } ///
/// 打印机名称 /// public string PrintName { get; set; } ///
/// 打印模板路径 /// public string FilePath { get; set; } ///
/// 打印数据源 /// public Dictionary
DataSource { get; set; } ///
/// 是否自适应高度 /// public bool IsAutoHeigth { get; set; } ///
/// 打印Document /// private PrintDocument printDocument; ///
/// 打印对话框 /// private PrintDialog printDialog; ///
/// XML解析文档 /// private XDocument root; ///
/// 初始化 /// ///
public DocumentPrintControl Init() { this.printDialog = new PrintDialog(); this.printDocument = new PrintDocument(); this.printDialog.Document = this.printDocument; this.printDocument.PrintPage += PrintDocument_PrintPage; return this; } ///
/// 设置数据源 /// ///
///
public DocumentPrintControl SetDataSource(string fileName) { string line = string.Empty; this.DataSource = new Dictionary
(); using (StreamReader reader = new StreamReader(fileName,Encoding.Default)) { List
> list = new List
>(); while ((line = reader.ReadLine()) != null) { Dictionary
dic = new Dictionary
(); dic.Add("Line", line); list.Add(dic); } this.DataSource.Add("List", list); } return this; } ///
/// 开始命令 /// ///
public DocumentPrintControl Begin() { //打印模板 if (!File.Exists(this.FilePath)) { throw new Exception("打印模板文件不存在"); } this.root = XDocument.Load(this.FilePath); string strWidth = root.Element("Page").Attribute("Width").Value; string strHeigth = root.Element("Page").Attribute("Heigth").Value; strWidth = string.IsNullOrWhiteSpace(strWidth) ? "0" : strWidth; strHeigth = string.IsNullOrWhiteSpace(strHeigth) ? "0" : strHeigth; string DefaultPrinter = root.Element("Page").Attribute("DefaultPrinter").Value; //计算文档高度 if (this.IsAutoHeigth) { float PageHeith = 0; foreach (XElement item in root.Element("Page").Elements()) { if (item.Name == "Line") { float LineHeigth = string.IsNullOrWhiteSpace(item.Attribute("Height").Value) ? 0 : Convert.ToSingle(item.Attribute("Height").Value); PageHeith += LineHeigth; } else if (item.Name == "Loop") { string Values = item.Attribute("Values").Value; List
> listValues = this.DataSource[Values] as List
>; if (listValues != null) { XElement lineItem = item.Element("Line"); float LineHeigth = string.IsNullOrWhiteSpace(lineItem.Attribute("Height").Value) ? 0 : Convert.ToSingle(lineItem.Attribute("Height").Value); PageHeith += LineHeigth * listValues.Count(); } } } strHeigth = (PageHeith + 10).ToString(); } this.printDocument.DefaultPageSettings.PaperSize = new System.Drawing.Printing.PaperSize(string.Format("{0}*{1}", strWidth, strHeigth), Convert.ToInt32(strWidth), (int)Math.Ceiling(Convert.ToSingle(strHeigth))); this.printDocument.PrinterSettings.PrinterName = string.IsNullOrWhiteSpace(this.PrintName) ? DefaultPrinter : this.PrintName; return this; } ///
/// 触发打印 /// ///
public bool Print() { this.printDocument.Print(); return true; } ///
/// 打印触发事件 /// ///
///
private void PrintDocument_PrintPage(object sender, PrintPageEventArgs e) { PrintTemplate(sender, e); } ///
/// 打印模板 /// ///
///
///
private void PrintTemplate(object sender, PrintPageEventArgs e) { Brush bru = Brushes.Black; Graphics g = e.Graphics; float totalHeight = 0; int rowIndex = 0; foreach (XElement item in root.Element("Page").Elements()) { if (item.Name == "Line") { float LineHeigth = string.IsNullOrWhiteSpace(item.Attribute("Height").Value) ? 0 : Convert.ToSingle(item.Attribute("Height").Value); foreach (XElement child in item.Elements()) { if (child.Name == "Text") { float Left = string.IsNullOrWhiteSpace(child.Attribute("Left").Value) ? 0 : Convert.ToSingle(child.Attribute("Left").Value); float Top = string.IsNullOrWhiteSpace(child.Attribute("Top").Value) ? 0 : Convert.ToSingle(child.Attribute("Top").Value); float FontSize = string.IsNullOrWhiteSpace(child.Attribute("FontSize").Value) ? 0 : Convert.ToSingle(child.Attribute("FontSize").Value); Top = totalHeight + Top; string content = child.Value; if (content.Contains("{ { ") && content.Contains("}}")) { int beginIndex = content.IndexOf("{ { "); int endIndex = content.LastIndexOf("}}"); string key = content.Substring(beginIndex + 2, endIndex - beginIndex - 2); g.DrawString(content.Replace("{ { " + key + "}}", this.DataSource[key].ToString()), new Font("宋体", FontSize, FontStyle.Bold), bru, new PointF(Left, Top)); } else { g.DrawString(content, new Font("宋体", FontSize, FontStyle.Bold), bru, new PointF(Left, Top)); } } else if (child.Name == "Image") { float Left = string.IsNullOrWhiteSpace(child.Attribute("Left").Value) ? 0 : Convert.ToSingle(child.Attribute("Left").Value); float Top = string.IsNullOrWhiteSpace(child.Attribute("Top").Value) ? 0 : Convert.ToSingle(child.Attribute("Top").Value); Top = totalHeight + Top; string content = child.Value; if (content.Contains("{ { ") && content.Contains("}}")) { int beginIndex = content.IndexOf("{ { "); int endIndex = content.LastIndexOf("}}"); string key = content.Substring(beginIndex + 2, endIndex - beginIndex - 2); Image image = Image.FromFile(this.DataSource[key].ToString()); g.DrawImage(image, new PointF(Left, Top)); } } else if (child.Name == "QRCode") { string content = string.Empty; float Left = string.IsNullOrWhiteSpace(child.Attribute("Left").Value) ? 0 : Convert.ToSingle(child.Attribute("Left").Value); float Top = string.IsNullOrWhiteSpace(child.Attribute("Top").Value) ? 0 : Convert.ToSingle(child.Attribute("Top").Value); Top = totalHeight + Top; content = child.Value; if (content.Contains("{ { ") && content.Contains("}}")) { int beginIndex = content.IndexOf("{ { "); int endIndex = content.LastIndexOf("}}"); string key = content.Substring(beginIndex + 2, endIndex - beginIndex - 2); content = content.Replace("{ { " + key + "}}", this.DataSource[key].ToString()); } QrEncoder qrEncoder = new QrEncoder(ErrorCorrectionLevel.H); QrCode qrCode = new QrCode(); qrEncoder.TryEncode(content, out qrCode); using (MemoryStream ms = new MemoryStream()) { var renderer = new GraphicsRenderer(new FixedModuleSize(2, QuietZoneModules.Two)); renderer.WriteToStream(qrCode.Matrix, ImageFormat.Jpeg, ms); Image image = Image.FromStream(ms); g.DrawImage(image, new PointF(Left, Top)); } } else if (child.Name == "BarCode") { string content = string.Empty; float Left = string.IsNullOrWhiteSpace(child.Attribute("Left").Value) ? 0 : Convert.ToSingle(child.Attribute("Left").Value); float Top = string.IsNullOrWhiteSpace(child.Attribute("Top").Value) ? 0 : Convert.ToSingle(child.Attribute("Top").Value); float Width = string.IsNullOrWhiteSpace(child.Attribute("Width").Value) ? 0 : Convert.ToSingle(child.Attribute("Width").Value); float Height = string.IsNullOrWhiteSpace(child.Attribute("Height").Value) ? 0 : Convert.ToSingle(child.Attribute("Height").Value); Top = totalHeight + Top; content = child.Value; if (content.Contains("{ { ") && content.Contains("}}")) { int beginIndex = content.IndexOf("{ { "); int endIndex = content.LastIndexOf("}}"); string key = content.Substring(beginIndex + 2, endIndex - beginIndex - 2); content = content.Replace("{ { " + key + "}}", this.DataSource[key].ToString()); } QrCodeEncodingOptions options = new QrCodeEncodingOptions { DisableECI = true, CharacterSet = "UTF-8", Width = (int)Math.Ceiling(Width), Height = (int)Math.Ceiling(Height), }; BarcodeWriter writer = new BarcodeWriter(); writer.Format = BarcodeFormat.CODE_128; writer.Options = options; Bitmap bitmap = writer.Write(content); g.DrawImage(bitmap, new PointF(Left, Top)); } } totalHeight += LineHeigth; rowIndex++; } else if (item.Name == "Loop") { string Values = item.Attribute("Values").Value; List
> listValues = this.DataSource[Values] as List
>; if (listValues != null) { XElement lineItem = item.Element("Line"); float LineHeigth = string.IsNullOrWhiteSpace(lineItem.Attribute("Height").Value) ? 0 : Convert.ToSingle(lineItem.Attribute("Height").Value); for (int i = 0; i < listValues.Count(); i++) { Dictionary
dicRow = listValues[i]; foreach (XElement child in lineItem.Elements()) { if (child.Name == "Text") { float Left = string.IsNullOrWhiteSpace(child.Attribute("Left").Value) ? 0 : Convert.ToSingle(child.Attribute("Left").Value); float Top = string.IsNullOrWhiteSpace(child.Attribute("Top").Value) ? 0 : Convert.ToSingle(child.Attribute("Top").Value); float FontSize = string.IsNullOrWhiteSpace(child.Attribute("FontSize").Value) ? 0 : Convert.ToSingle(child.Attribute("FontSize").Value); Top = totalHeight + Top; string content = child.Value; if (content.Contains("{ { ") && content.Contains("}}")) { int beginIndex = content.IndexOf("{ { "); int endIndex = content.LastIndexOf("}}"); string key = content.Substring(beginIndex + 2, endIndex - beginIndex - 2); g.DrawString(content.Replace("{ { " + key + "}}", dicRow[key].ToString()), new Font("宋体", FontSize, FontStyle.Bold), bru, new PointF(Left, Top)); } else { g.DrawString(content, new Font("宋体", FontSize, FontStyle.Bold), bru, new PointF(Left, Top)); } } else if (child.Name == "Image") { float Left = string.IsNullOrWhiteSpace(child.Attribute("Left").Value) ? 0 : Convert.ToSingle(child.Attribute("Left").Value); float Top = string.IsNullOrWhiteSpace(child.Attribute("Top").Value) ? 0 : Convert.ToSingle(child.Attribute("Top").Value); Top = totalHeight + Top; string content = child.Value; if (content.Contains("{ { ") && content.Contains("}}")) { int beginIndex = content.IndexOf("{ { "); int endIndex = content.LastIndexOf("}}"); string key = content.Substring(beginIndex + 2, endIndex - beginIndex - 2); Image image = Image.FromFile(dicRow[key].ToString()); g.DrawImage(image, new PointF(Left, Top)); } } else if (child.Name == "QRCode") { string content = string.Empty; float Left = string.IsNullOrWhiteSpace(child.Attribute("Left").Value) ? 0 : Convert.ToSingle(child.Attribute("Left").Value); float Top = string.IsNullOrWhiteSpace(child.Attribute("Top").Value) ? 0 : Convert.ToSingle(child.Attribute("Top").Value); Top = totalHeight + Top; content = child.Value; if (content.Contains("{ { ") && content.Contains("}}")) { int beginIndex = content.IndexOf("{ { "); int endIndex = content.LastIndexOf("}}"); string key = content.Substring(beginIndex + 2, endIndex - beginIndex - 2); content = content.Replace("{ { " + key + "}}", dicRow[key].ToString()); } QrEncoder qrEncoder = new QrEncoder(ErrorCorrectionLevel.H); QrCode qrCode = new QrCode(); qrEncoder.TryEncode(content, out qrCode); using (MemoryStream ms = new MemoryStream()) { var renderer = new GraphicsRenderer(new FixedModuleSize(2, QuietZoneModules.Two)); renderer.WriteToStream(qrCode.Matrix, ImageFormat.Jpeg, ms); Image image = Image.FromStream(ms); g.DrawImage(image, new PointF(Left, Top)); } } else if (child.Name == "BarCode") { string content = string.Empty; float Left = string.IsNullOrWhiteSpace(child.Attribute("Left").Value) ? 0 : Convert.ToSingle(child.Attribute("Left").Value); float Top = string.IsNullOrWhiteSpace(child.Attribute("Top").Value) ? 0 : Convert.ToSingle(child.Attribute("Top").Value); float Width = string.IsNullOrWhiteSpace(child.Attribute("Width").Value) ? 0 : Convert.ToSingle(child.Attribute("Width").Value); float Height = string.IsNullOrWhiteSpace(child.Attribute("Height").Value) ? 0 : Convert.ToSingle(child.Attribute("Height").Value); Top = totalHeight + Top; content = child.Value; if (content.Contains("{ { ") && content.Contains("}}")) { int beginIndex = content.IndexOf("{ { "); int endIndex = content.LastIndexOf("}}"); string key = content.Substring(beginIndex + 2, endIndex - beginIndex - 2); content = content.Replace("{ { " + key + "}}", dicRow[key].ToString()); } QrCodeEncodingOptions options = new QrCodeEncodingOptions { DisableECI = true, CharacterSet = "UTF-8", Width = (int)Math.Ceiling(Width), Height = (int)Math.Ceiling(Height), }; BarcodeWriter writer = new BarcodeWriter(); writer.Format = BarcodeFormat.CODE_128; writer.Options = options; Bitmap bitmap = writer.Write(content); g.DrawImage(bitmap, new PointF(Left, Top)); } } totalHeight += LineHeigth; rowIndex++; } } } } } }
打印类设置代码

 

      

      以上是模板打印出来的效果,也避免的打印机指令的相关问题,同时可以自定义标签纸张的大小以及打印的内容边距等等问题。

 

    五. 代码管理

     由于代码还未完全整理出来,后期整理好之后会托管到GitHub上,暂时有个简单的案例,如有需求可以QQ 821865130 联系我,  群号:88718955

     吉特仓储管理系统中涉及到打印的打印功能,也尝试过各种方式的打印,后面会一一总结分享。如果对吉特仓储系统想要有一定了解可以加群:  88718955  或 142050808

     吉特仓储管理系统有有开源代码:    有兴趣的可以下载共同探讨

      

作者:
出处:
关于作者:从事仓库,生产软件方面的开发,在项目管理以及企业经营方面寻求发展之路
版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
联系方式: 个人QQ  821865130 ; 仓储技术QQ群 88718955,142050808 ;
吉特仓储管理系统 开源地址:

 

你可能感兴趣的文章
PHP7曝出三个高危0-day漏洞,还有一个仍未修复
查看>>
React Native Ubuntu简介
查看>>
透过“虚火”洞悉物联网的价值
查看>>
大数据和学生创业有什么关系
查看>>
视频点播播放器如何实现加密下载?
查看>>
Facebook将推“市场”功能:用户可相互买卖东西
查看>>
俄国防部组建信息作战部队 应对西方网络-心理攻击
查看>>
《Android应用开发攻略》——第2章 设计成功的应用程序 2.1 导言:设计成功的Android应用程序...
查看>>
法国物联网公司Sigfox 获1.6亿美元E轮融资
查看>>
Bob大叔和Jim Coplien对TDD的论战
查看>>
不以规矩不成方圆:Digital Ocean也删除了他们的数据库
查看>>
SharePoint 数据库迁移步骤
查看>>
中国电信开启2017年IP RAN设备集采:共两个标包
查看>>
解放智慧 智能家居对人到底是利还是弊
查看>>
安防物联网:海量分析技术仍是关键
查看>>
黄金法则:MySQL基准测试最佳实践
查看>>
Cisco reveals new initative
查看>>
Selenium2.0功能测试之forward与back
查看>>
微软扩大生态的又一步棋:推出 Visual Studio for Mac 预览版
查看>>
OA对于小微企业意味着什么?
查看>>