MS OpenXML SDK 2.0在Excel中指定位置填入数据和插入图片

简介: 简介 这是公司另外一个项目组遇到的技术问题。即如何用OpenXML在Excel指定位置填入数据和插入图片。笔者知道此事之后,帮他们想了一下办法。 用户的需求是这样的:有一个Excel的文档模版,里面已经有了一个表格框架。

简介

这是公司另外一个项目组遇到的技术问题。即如何用OpenXML在Excel指定位置填入数据和插入图片。笔者知道此事之后,帮他们想了一下办法。

用户的需求是这样的:有一个Excel的文档模版,里面已经有了一个表格框架。需要在某些单元格填入数字或者文字。并在某些单元格插入图片。并且不破坏原有的表格内容。这些功能必须在asp.net应用程序中实现,并且不用Office interop的组件。他们之前已经用了Office interop对象来操作Excel,这样就要求服务器上安装Office,因为之前的方法占用内存大,不稳定,所以用户希望他们另外想办法。用户和他们都知道OpenXML可以读写Excel文件,而且很轻量的,占用资源小,但是他们不知道如何用OpenXML完成这个任务,卡壳好多天了。

imageimage

需求的理解可以见上图,我们需要做的是:

1. 在黄色背景的单元格填入数据

2. 在指定位置插入图片(如上图中的折线图)

上图左图是未填入数据之前的样子。上图右图是完成后的最终结果图。

实现

他们在网路上找了一些参考资料和参考代码,但是其中的对象模型比较复杂,一时半会没有弄明白。笔者聆听他们的需求后,再看了他们的已有代码。调试了一阵,发现这些参考代码对实现他们的需求没有什么用。需要针对他们的需求来改代码才行。

此工程必须引用C:\Program Files (x86)\Open XML SDK\V2.0\lib\DocumentFormat.OpenXml.dll,C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client\WindowsBase.dll

以下是核心代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Drawing.Spreadsheet;
using System.Text.RegularExpressions;
using System.Data;
namespace ExcelWriter
{
    public class OpenXMLExcel : IDisposable
    {
        #region Constrution and dispose
       
        SpreadsheetDocument spreadSheet;
        public WorksheetPart CurrentWorksheetPart { get; set; }
        SharedStringTablePart shareStringPart;
        public OpenXMLExcel(Stream stream, bool bCreate)
        {
            if (bCreate == true)
            {
                spreadSheet = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook);
                WorkbookPart workbookPart = spreadSheet.AddWorkbookPart();
                workbookPart.Workbook = new Workbook();

                WorksheetPart worksheetPart = InsertWorksheet(spreadSheet.WorkbookPart);

                if (spreadSheet.WorkbookPart.GetPartsOfType().Count() > 0)
                {
                    shareStringPart = spreadSheet.WorkbookPart.GetPartsOfType().First();
                }
                else
                {
                    shareStringPart = spreadSheet.WorkbookPart.AddNewPart();
                }

                shareStringPart.SharedStringTable = new SharedStringTable();
                shareStringPart.SharedStringTable.Count = 1;
                shareStringPart.SharedStringTable.UniqueCount = 1;

                CurrentWorksheetPart = worksheetPart;
            }
            else
            {
                spreadSheet = SpreadsheetDocument.Open(stream, true);
                shareStringPart = spreadSheet.WorkbookPart.GetPartsOfType().First();
                CurrentWorksheetPart = spreadSheet.WorkbookPart.WorksheetParts.First();
            }
        }
        public void Dispose()
        {
            spreadSheet.Close();
            spreadSheet.Dispose();
        }
        
        #endregion
        #region Public interface
        public void WriteData(int x, int y, string strContent)
        {
            DataTable dt = new DataTable();
            dt.Columns.Add("Column1");
            dt.Rows.Add(strContent);
            WriteDataIntoWorkSheet(x, y, dt);
        }

        public void WriteDataIntoWorkSheet(int startx, int starty, DataTable dt)
        {
            //if (startx < 1)
            //    startx = 1;
            //if (starty < 1)
            //    starty = 1;

            WorksheetPart worksheetPart = CurrentWorksheetPart;
            //starty -= 1;
            int j = 0;
            foreach (DataRow dr in dt.Rows)
            {
                j++;
                for (int i = 0; i < dt.Columns.Count; i++)
                {
                    string name = GetColumnName(i + starty);
                    string text = Convert.IsDBNull(dr[i]) ? "" : dr[i].ToString();
                    int index = InsertSharedStringItem(text, shareStringPart);
                    Cell cell = InsertCellInWorksheet(name, Convert.ToUInt32(j + startx), worksheetPart);

                    cell.CellValue = new CellValue(index.ToString());
                    cell.DataType = new EnumValue(CellValues.SharedString);
                    worksheetPart.Worksheet.Save();
                }
            }
        }
        public void WriteDataIntoWorkSheet(int startx, int starty, string[][] data)
        {
            WorksheetPart worksheetPart = CurrentWorksheetPart;
            starty -= 1;
            int i = 0;
            foreach (string[] row in data)
            {
                int j = 0;
                foreach (string text in row)
                {
                    string name = GetColumnName(j + starty);
                    int index = InsertSharedStringItem(text, shareStringPart);
                    Cell cell = InsertCellInWorksheet(name, Convert.ToUInt32(i + startx), worksheetPart);

                    cell.CellValue = new CellValue(index.ToString());
                    cell.DataType = new EnumValue(CellValues.SharedString);
                    worksheetPart.Worksheet.Save();
                    j++;
                }
                i++;
            }
        }
        public void AddNewWorksheet()
        {
            WorkbookPart workbookPart = spreadSheet.WorkbookPart;

            WorksheetPart newWorksheetPart = workbookPart.AddNewPart();
            newWorksheetPart.Worksheet = new Worksheet(new SheetData());
            newWorksheetPart.Worksheet.Save();
            CurrentWorksheetPart = newWorksheetPart;
            //workbookPart.SharedStringTablePart.SharedStringTable.Count = workbookPart.SharedStringTablePart.SharedStringTable.Count + 1;
            //workbookPart.SharedStringTablePart.SharedStringTable.UniqueCount = workbookPart.SharedStringTablePart.SharedStringTable.UniqueCount + 1;
            string relationshipId = workbookPart.GetIdOfPart(newWorksheetPart);
            Sheets sheets = workbookPart.Workbook.GetFirstChild();
            uint sheetId = 1;
            if (sheets.Elements().Count() > 0)
            {
                sheetId = sheets.Elements().Select(s => s.SheetId.Value).Max() + 1;
            }

            string sheetName = "Sheet" + sheetId;

            // Append the new worksheet and associate it with the workbook.
            Sheet sheet = new Sheet() { Id = relationshipId, SheetId = sheetId, Name = sheetName };
            sheets.Append(sheet);
            workbookPart.Workbook.Save();
        }

        public void InsertImage(long x, long y, long? width, long? height, string sImagePath)
        {
            try
            {
                WorksheetPart wsp = CurrentWorksheetPart;
                DrawingsPart dp;
                ImagePart imgp;
                WorksheetDrawing wsd;

                ImagePartType ipt;
                switch (sImagePath.Substring(sImagePath.LastIndexOf('.') + 1).ToLower())
                {
                    case "png":
                        ipt = ImagePartType.Png;
                        break;
                    case "jpg":
                    case "jpeg":
                        ipt = ImagePartType.Jpeg;
                        break;
                    case "gif":
                        ipt = ImagePartType.Gif;
                        break;
                    default:
                        return;
                }

                if (wsp.DrawingsPart == null)
                {
                    //----- no drawing part exists, add a new one

                    dp = wsp.AddNewPart();
                    imgp = dp.AddImagePart(ipt, wsp.GetIdOfPart(dp));
                    wsd = new WorksheetDrawing();
                }
                else
                {
                    //----- use existing drawing part

                    dp = wsp.DrawingsPart;
                    imgp = dp.AddImagePart(ipt);
                    dp.CreateRelationshipToPart(imgp);
                    wsd = dp.WorksheetDrawing;
                }

                using (FileStream fs = new FileStream(sImagePath, FileMode.Open))
                {
                    imgp.FeedData(fs);
                }

                int imageNumber = dp.ImageParts.Count();
                if (imageNumber == 1)
                {
                    Drawing drawing = new Drawing();
                    drawing.Id = dp.GetIdOfPart(imgp);
                    CurrentWorksheetPart.Worksheet.Append(drawing);
                }

                NonVisualDrawingProperties nvdp = new NonVisualDrawingProperties();
                nvdp.Id = new UInt32Value((uint)(1024 + imageNumber));
                nvdp.Name = "Picture " + imageNumber.ToString();
                nvdp.Description = "";
                DocumentFormat.OpenXml.Drawing.PictureLocks picLocks = new DocumentFormat.OpenXml.Drawing.PictureLocks();
                picLocks.NoChangeAspect = true;
                picLocks.NoChangeArrowheads = true;
                NonVisualPictureDrawingProperties nvpdp = new NonVisualPictureDrawingProperties();
                nvpdp.PictureLocks = picLocks;
                NonVisualPictureProperties nvpp = new NonVisualPictureProperties();
                nvpp.NonVisualDrawingProperties = nvdp;
                nvpp.NonVisualPictureDrawingProperties = nvpdp;

                DocumentFormat.OpenXml.Drawing.Stretch stretch = new DocumentFormat.OpenXml.Drawing.Stretch();
                stretch.FillRectangle = new DocumentFormat.OpenXml.Drawing.FillRectangle();

                BlipFill blipFill = new BlipFill();
                DocumentFormat.OpenXml.Drawing.Blip blip = new DocumentFormat.OpenXml.Drawing.Blip();
                blip.Embed = dp.GetIdOfPart(imgp);
                blip.CompressionState = DocumentFormat.OpenXml.Drawing.BlipCompressionValues.Print;
                blipFill.Blip = blip;
                blipFill.SourceRectangle = new DocumentFormat.OpenXml.Drawing.SourceRectangle();
                blipFill.Append(stretch);

                DocumentFormat.OpenXml.Drawing.Transform2D t2d = new DocumentFormat.OpenXml.Drawing.Transform2D();
                DocumentFormat.OpenXml.Drawing.Offset offset = new DocumentFormat.OpenXml.Drawing.Offset();
                offset.X = 0;
                offset.Y = 0;
                t2d.Offset = offset;
                Bitmap bm = new Bitmap(sImagePath);

                DocumentFormat.OpenXml.Drawing.Extents extents = new DocumentFormat.OpenXml.Drawing.Extents();

                if (width == null)
                    extents.Cx = (long)bm.Width * (long)((float)914400 / bm.HorizontalResolution);
                else
                    extents.Cx = width * (long)((float)914400 / bm.HorizontalResolution);

                if (height == null)
                    extents.Cy = (long)bm.Height * (long)((float)914400 / bm.VerticalResolution);
                else
                    extents.Cy = height * (long)((float)914400 / bm.VerticalResolution);

                bm.Dispose();
                t2d.Extents = extents;
                ShapeProperties sp = new ShapeProperties();
                sp.BlackWhiteMode = DocumentFormat.OpenXml.Drawing.BlackWhiteModeValues.Auto;
                sp.Transform2D = t2d;
                DocumentFormat.OpenXml.Drawing.PresetGeometry prstGeom = new DocumentFormat.OpenXml.Drawing.PresetGeometry();
                prstGeom.Preset = DocumentFormat.OpenXml.Drawing.ShapeTypeValues.Rectangle;
                prstGeom.AdjustValueList = new DocumentFormat.OpenXml.Drawing.AdjustValueList();
                sp.Append(prstGeom);
                sp.Append(new DocumentFormat.OpenXml.Drawing.NoFill());

                DocumentFormat.OpenXml.Drawing.Spreadsheet.Picture picture = new DocumentFormat.OpenXml.Drawing.Spreadsheet.Picture();
                picture.NonVisualPictureProperties = nvpp;
                picture.BlipFill = blipFill;
                picture.ShapeProperties = sp;

                Position pos = new Position();
                pos.X = x * 914400 / 72;
                pos.Y = y * 914400 / 72;
                Extent ext = new Extent();
                ext.Cx = extents.Cx;
                ext.Cy = extents.Cy;
                AbsoluteAnchor anchor = new AbsoluteAnchor();
                anchor.Position = pos;
                anchor.Extent = ext;
                anchor.Append(picture);
                anchor.Append(new ClientData());
                wsd.Append(anchor);
                wsd.Save(dp);
            }
            catch (Exception ex)
            {
                throw ex; // or do something more interesting if you want
            }
        }

        public void InsertImage(long x, long y, string sImagePath)
        {
            InsertImage(x, y, null, null, sImagePath);
        }
        
        #endregion
        #region private static OpenXml methods

        private static int InsertSharedStringItem(string text, SharedStringTablePart shareStringPart)
        {
            // If the part does not contain a SharedStringTable, create one.
            if (shareStringPart.SharedStringTable == null)
            {
                shareStringPart.SharedStringTable = new SharedStringTable();
                shareStringPart.SharedStringTable.Count = 1;
                shareStringPart.SharedStringTable.UniqueCount = 1;
            }
            int i = 0;
            // Iterate through all the items in the SharedStringTable. If the text already exists, return its index.
            foreach (SharedStringItem item in shareStringPart.SharedStringTable.Elements())
            {
                if (item.InnerText == text)
                {
                    return i;
                }
                i++;
            }

            // The text does not exist in the part. Create the SharedStringItem and return its index.
            shareStringPart.SharedStringTable.AppendChild(new SharedStringItem(new DocumentFormat.OpenXml.Spreadsheet.Text(text)));
            shareStringPart.SharedStringTable.Save();

            return i;
        }

        private static Cell InsertCellInWorksheet(string columnName, uint rowIndex, WorksheetPart worksheetPart)
        {
            Worksheet worksheet = worksheetPart.Worksheet;
            SheetData sheetData = worksheet.GetFirstChild();
            string cellReference = columnName + rowIndex;

            // If the worksheet does not contain a row with the specified row index, insert one.
            Row row;
            if (sheetData.Elements().Where(r => r.RowIndex == rowIndex).Count() != 0)
            {
                row = sheetData.Elements().Where(r => r.RowIndex == rowIndex).First();
            }
            else
            {
                row = new Row() { RowIndex = rowIndex };
                sheetData.Append(row);
            }

            // If there is not a cell with the specified column name, insert one.  
            if (row.Elements().Where(c => c.CellReference.Value == columnName + rowIndex).Count() > 0)
            {
                return row.Elements().Where(c => c.CellReference.Value == cellReference).First();
            }
            else
            {
                // Cells must be in sequential order according to CellReference. Determine where to insert the new cell.
                Cell refCell = null;
                foreach (Cell cell in row.Elements())
                {
                    if (string.Compare(cell.CellReference.Value, cellReference, true) > 0)
                    {
                        refCell = cell;
                        break;
                    }
                }

                Cell newCell = new Cell() { CellReference = cellReference };
                row.InsertBefore(newCell, refCell);

                worksheet.Save();
                return newCell;
            }
        }

        private static WorksheetPart InsertWorksheet(WorkbookPart workbookPart)
        {
            // Add a new worksheet part to the workbook.
            WorksheetPart newWorksheetPart = workbookPart.AddNewPart();
            newWorksheetPart.Worksheet = new Worksheet(new SheetData());
            newWorksheetPart.Worksheet.Save();

            workbookPart.Workbook.AppendChild(new Sheets());

            Sheets sheets = workbookPart.Workbook.GetFirstChild();
            string relationshipId = workbookPart.GetIdOfPart(newWorksheetPart);

            // Get a unique ID for the new sheet.
            uint sheetId = 1;
            if (sheets.Elements().Count() > 0)
            {
                sheetId = sheets.Elements().Select(s => s.SheetId.Value).Max() + 1;
            }

            string sheetName = "Sheet" + sheetId;

            // Append the new worksheet and associate it with the workbook.
            Sheet sheet = new Sheet() { Id = relationshipId, SheetId = sheetId, Name = sheetName };
            sheets.Append(sheet);

            workbookPart.Workbook.Save();
            return newWorksheetPart;
        }

        // Given a Worksheet and a cell name, verifies that the specified cell exists.
        // If it does not exist, creates a new cell. 
        private static void CreateSpreadsheetCellIfNotExist(Worksheet worksheet, string cellName)
        {
            string columnName = GetColumnName(cellName);
            uint rowIndex = GetRowIndex(cellName);

            IEnumerable rows = worksheet.Descendants().Where(r => r.RowIndex.Value == rowIndex);

            // If the Worksheet does not contain the specified row, create the specified row.
            // Create the specified cell in that row, and insert the row into the Worksheet.
            if (rows.Count() == 0)
            {
                Row row = new Row() { RowIndex = new UInt32Value(rowIndex) };
                Cell cell = new Cell() { CellReference = new StringValue(cellName) };
                row.Append(cell);
                worksheet.Descendants().First().Append(row);
                worksheet.Save();
            }
            else
            {
                Row row = rows.First();

                IEnumerable cells = row.Elements().Where(c => c.CellReference.Value == cellName);

                // If the row does not contain the specified cell, create the specified cell.
                if (cells.Count() == 0)
                {
                    Cell cell = new Cell() { CellReference = new StringValue(cellName) };
                    row.Append(cell);
                    worksheet.Save();
                }
            }

        }
        // Given a cell name, parses the specified cell to get the column name.
        private static string GetColumnName(string cellName)
        {
            // Create a regular expression to match the column name portion of the cell name.
            Regex regex = new Regex("[A-Za-z]+");
            Match match = regex.Match(cellName);

            return match.Value;
        }

        // Given a cell name, parses the specified cell to get the row index.
        private static uint GetRowIndex(string cellName)
        {
            // Create a regular expression to match the row index portion the cell name.
            Regex regex = new Regex(@"\d+");
            Match match = regex.Match(cellName);

            return uint.Parse(match.Value);
        }
        
        #endregion
        #region Utility methods
        private static string GetColumnName(int index)
        {
            string name = "";
            char[] columnNames = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
            int num = index;
            do
            {
                int i = num % 26;
                name = columnNames[i] + name;
                num = num / 26 - 1;
            } while (num > -1);
            if (string.IsNullOrEmpty(name))
                name = "A";
            return name;
        } 
        #endregion
    }

}

 

要使用上述代码,就可以用如下的一段代码:

FileStream file = null;
            OpenXMLExcel openXMLExcel = null;
            try
            {
                file = new FileStream(@"C:\book1.xlsx", FileMode.Open);
                openXMLExcel = new OpenXMLExcel(file, false);
                openXMLExcel.WriteData(1, 1, "数学");
                openXMLExcel.WriteData(3, 1, "物理");
                openXMLExcel.InsertImage(1, 70, @"c:\TempPic.jpg");
                openXMLExcel.Dispose();
                file.Close();
            }
            catch
            {
                if (openXMLExcel != null)
                {
                    openXMLExcel.Dispose();
                }
                if (file != null)
                {
                    file.Close();
                }
            }
            finally
            {
            }

 

FileMode.Open是为了读入已有的Excel文件的字节流。

new OpenXMLExcel(file, false)中的false是为了进行读取操作, 此参数如果为true的话,就会建立一个新的Excel Sheet。

openXMLExcel.WriteData(1, 1, "数学"); 是为了将“数学”两字写入黄色背景的单元格,前一个1是横向坐标参数,表示第1行,注意这是以单元格为单位的坐标,这个行号从0开始。后一个1是纵向组表参数,表示第一列,注意这个列号也是从0开始。即最左上的单元格的坐标是(0,0)。

openXMLExcel.InsertImage(1, 70, @"c:\TempPic.jpg"); 是为了将c:\TempPic.jpg插入到坐标为1, 70的坐标处。注意这是以象素Pixel为单位的坐标。坐标原点在左上角。

OpenXML局限性

此OpenXML不支持Excel 2003或者更早的文件格式。只支持Excel 2007及Excel 2010的文件格式。可能是因为从Excel 2007起,这些文件都用xml表示了。xlsx其实是一个zip文件,解开此zip文件,我们可以看到很多描述excel内容的xml文件。

Enjoy coding life!

目录
相关文章
|
4月前
|
Python
如何根据Excel某列数据为依据分成一个新的工作表
在处理Excel数据时,我们常需要根据列值将数据分到不同的工作表或文件中。本文通过Python和VBA两种方法实现该操作:使用Python的`pandas`库按年级拆分为多个文件,再通过VBA宏按班级生成新的工作表,帮助高效整理复杂数据。
|
4月前
|
数据采集 数据可视化 数据挖掘
用 Excel+Power Query 做电商数据分析:从 “每天加班整理数据” 到 “一键生成报表” 的配置教程
在电商运营中,数据是增长的关键驱动力。然而,传统的手工数据处理方式效率低下,耗费大量时间且易出错。本文介绍如何利用 Excel 中的 Power Query 工具,自动化完成电商数据的采集、清洗与分析,大幅提升数据处理效率。通过某美妆电商的实战案例,详细拆解从多平台数据整合到可视化报表生成的全流程,帮助电商从业者摆脱繁琐操作,聚焦业务增长,实现数据驱动的高效运营。
|
6月前
|
存储 安全 大数据
网安工程师必看!AiPy解决fscan扫描数据整理难题—多种信息快速分拣+Excel结构化存储方案
作为一名安全测试工程师,分析fscan扫描结果曾是繁琐的手动活:从海量日志中提取开放端口、漏洞信息和主机数据,耗时又易错。但现在,借助AiPy开发的GUI解析工具,只需喝杯奶茶的时间,即可将[PORT]、[SERVICE]、[VULN]、[HOST]等关键信息智能分类,并生成三份清晰的Excel报表。告别手动整理,大幅提升效率!在安全行业,工具党正碾压手动党。掌握AiPy,把时间留给真正的攻防实战!官网链接:https://www.aipyaipy.com,解锁更多用法!
|
4月前
|
Python
将Excel特定某列数据删除
将Excel特定某列数据删除
|
11月前
|
数据采集 数据可视化 数据挖掘
利用Python自动化处理Excel数据:从基础到进阶####
本文旨在为读者提供一个全面的指南,通过Python编程语言实现Excel数据的自动化处理。无论你是初学者还是有经验的开发者,本文都将帮助你掌握Pandas和openpyxl这两个强大的库,从而提升数据处理的效率和准确性。我们将从环境设置开始,逐步深入到数据读取、清洗、分析和可视化等各个环节,最终实现一个实际的自动化项目案例。 ####
1976 10
|
存储 人工智能 开发工具
AI助理化繁为简,速取代码参数——使用python SDK 处理OSS存储的图片
只需要通过向AI助理提问的方式输入您的需求,即可瞬间获得核心流程代码及参数,缩短学习路径、提升开发效率。
1721 5
AI助理化繁为简,速取代码参数——使用python SDK 处理OSS存储的图片
|
数据采集 存储 JavaScript
自动化数据处理:使用Selenium与Excel打造的数据爬取管道
本文介绍了一种使用Selenium和Excel结合代理IP技术从WIPO品牌数据库(branddb.wipo.int)自动化爬取专利信息的方法。通过Selenium模拟用户操作,处理JavaScript动态加载页面,利用代理IP避免IP封禁,确保数据爬取稳定性和隐私性。爬取的数据将存储在Excel中,便于后续分析。此外,文章还详细介绍了Selenium的基本设置、代理IP配置及使用技巧,并探讨了未来可能采用的更多防反爬策略,以提升爬虫效率和稳定性。
697 4
|
9月前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
602 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
9月前
|
分布式计算 Hadoop 大数据
从Excel到Hadoop:数据规模的进化之路
从Excel到Hadoop:数据规模的进化之路
192 10
|
关系型数据库 MySQL Shell
不通过navicat工具怎么把查询数据导出到excel表中
不通过navicat工具怎么把查询数据导出到excel表中
161 0