Unity使用Aspose.Words创建表格和UI截图一起插入到Word中并保存到本地的一种解决方案

实现思路

1. UI截图

这是实现的是针对某一特定的UI截图,实际上是使用了通过Unity中的API:Texture2D.ReadPixels来读取屏幕区域像素,然后将图片数据转为二进制数据,再保存到本地。

2. 使用Aspose.Words创建表格

Aspose.Words是一款先进的类库,可以直接在各个应用程序中执行各种文档处理任务。Aspose.Words支持DOC,OOXML,RTF,HTML,OpenDocument, PDF, XPS, EPUB和其他格式。使用Aspose.Words,不使用Microsoft Word和WPS。也可以生成,更改,转换,渲染和打印文档。
这里应用到Unity中,将文本数据、图片和使用Aspose.Words创建的表格插入到模板Word中。使用数据分离的方法,首先定义表格数据类TableData,在这个类中,定义你的表名、表头、表格内容等等,然后有一个方法是你要从其他模块里将获取到的表格数据保存到这个类TableData的数据结构中,再根据这些数据创建表格数据的xml节点信息并保存下来,另外一个脚本里写解析这个xml文档的方法,根据解析出来的数据,利用Aspose.Words类库里的API创建表格并插入各种数据。最后使用FileBrowser插件打开本地资源浏览器并保存新的Word文档。

详细实现

使用Aspose.Words将创建的表格和其他文本数据、图片插入到Word中
  • 首先明确你要创建的表格长啥样?这决定了你的TableData类要怎么写。
    比如我需要创建如下两个表格:
    在这里插入图片描述
    在这里插入图片描述
    那就要归纳出来我的数据结构应该包含:表格名称、表头、表格内容、表尾这些信息,明确了这些信息,那我构造出来的TableData应该是这样的:
    在这里插入图片描述
  • 获取表格数据保存到TableData类数据结构中
    你的表名是什么,表头、表格内容等这些数据都是需要提前获取的,这决定了你要生成多少行表格数据,我这里只是演示demo,所以直接写好测试数据:
    在这里插入图片描述
  • 将表格数据转为xml的形式保存下来
    完整代码:
WriteDataToXML.cs将对象序列化为xml数据
using System.Collections.Generic;
using UnityEngine;
using System.Xml;
using System.Linq;
/// <summary>
/// 将数据写入XML文档
/// </summary>
public class WriteDataToXML : MonoBehaviour
{
    /// <summary>
    /// 文档所在目录
    /// </summary>
    string wordXMLPath;
    /// <summary>
    /// 写入到 xml中的数据
    /// </summary>
    List<TableData> tableDatas=new List<TableData>();
    string[] table1HeaderName = { "序号", "姓名","语文", "数学", "英语"};
    string[] table2HeaderName = { "序号", "姓名", "性别", "总分"};
    /// <summary>
    /// 表格中每一行的数据,序号在创建表格的时候直接写入
    /// 初始化一个二维数组,存储表格数据
    /// </summary>
    string[,] tableRowDatas = 
    { 
        { "周一","男", "90","90", "60","240" },
        { "卓二","女", "100","90","70" ,"260" },
        { "张三","女", "80","60", "65" ,"205"},
        { "李四","男", "90","60", "80" ,"230"},
        { "王五","男", "70","80", "80","230" }
    };
    string[] textDatas = { "初一(5)班", "300" };
    private void Start()
    {
        wordXMLPath = Application.dataPath + "/../data/Word";
    }
    /// <summary>
    /// 保存Word
    /// </summary>
    public void SaveWord()
    {
        CreateTableData(table1HeaderName,1);
        CreateTableData(table2HeaderName, 2);
        CreateXmlData();
        SaveWordToWindow.WriteDataToWord a = new SaveWordToWindow.WriteDataToWord();
        a.CreateNewWord(wordXMLPath);
    }
    /// <summary>
    /// 创建表格数据
    /// 前提是拿到数据来源
    /// </summary>
    /// <param name="datas">表头数据</param>
    /// <param name="tableId">表格的ID</param>
    private void CreateTableData(string[] datas,int tableId)
    {
        TableData TData = new TableData();
        for (int i = 0; i < datas.Length; i++)
        {
            TData.TableHeaderList.Add(datas[i]);//添加一个表头
        }
        if (tableId==1)//获取表格1数据
        {
            TData.tableName = "班级个人单科成绩表";
            for (int i = 0; i < tableRowDatas.GetLength(0); i++)
            {
                //表格每一行的数据,从左到右
                List<string> rowDatas = new List<string>();
                rowDatas.Add(tableRowDatas[i, 0]);//即姓名
                rowDatas.Add(tableRowDatas[i, 2]);//即语文成绩
                rowDatas.Add(tableRowDatas[i, 3]);//即数学成绩
                rowDatas.Add(tableRowDatas[i, 4]);//即英语成绩
                TData.TBodyList.Add(rowDatas);
            }
        }
        else if(tableId==2)//获取表格2数据
        {
            TData.tableName = "班级总分表";
            TData.TableTail = "最高分";
            List<int> array=new List<int>();
            for (int i = 0; i < tableRowDatas.GetLength(0); i++)
            {
                List<string> rowDatas = new List<string>();
                rowDatas.Add(tableRowDatas[i, 0]);//即姓名
                rowDatas.Add(tableRowDatas[i, 1]);//即性别
                rowDatas.Add(tableRowDatas[i, 5]);//即总分
                TData.TBodyList.Add(rowDatas);
                array.Add(int.Parse(tableRowDatas[i, 5]));
            }
            TData.FooterStr = array.Max().ToString();//使用了Linq的Max()方法找到最大值
        }
        tableDatas.Add(TData);//将一个表格数据添加到表格列表
    }

    /// <summary>
    /// 创建表格数据的xml节点信息并保存
    /// </summary>
    private void CreateXmlData()
    {
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.AppendChild(xmlDoc.CreateXmlDeclaration("1.0", "UTF-8", null));//创建xml文件声明
        XmlElement rootNode = xmlDoc.CreateElement("Root");//创建根节点
        xmlDoc.AppendChild(rootNode);//将根节点添加到xml中

        XmlElement wordNode = xmlDoc.CreateElement("WordName");
        wordNode.InnerText = "班级成绩报告.doc";
        rootNode.AppendChild(wordNode);

        //根据表格数据创建表格的xml数据
        for (int i = 0; i < tableDatas.Count; i++)
        {
            //创建一个表格的xml信息
            TableData tableData = tableDatas[i];
            XmlElement tableNode = xmlDoc.CreateElement("Table");
            tableNode.SetAttribute("LabelName", string.Format("table{0}", i + 1));
            rootNode.AppendChild(tableNode);
            //创建表名节点
            XmlElement TNameNode = xmlDoc.CreateElement("TableName");
            TNameNode.InnerText = tableData.tableName;
            tableNode.AppendChild(TNameNode);
            //创建表头节点
            XmlElement headerNode = xmlDoc.CreateElement("HeaderName");
            tableNode.AppendChild(headerNode);
            for (int j = 0; j < tableData.TableHeaderList.Count; j++)
            {
                XmlElement itemNode = xmlDoc.CreateElement("Item");
                itemNode.InnerText = tableData.TableHeaderList[j];
                headerNode.AppendChild(itemNode);
            }
            //创建表格内容的节点
            XmlElement dataNode = xmlDoc.CreateElement("Data");
            tableNode.AppendChild(dataNode);
            for (int lineIdx = 0; lineIdx < tableData.TBodyList.Count; lineIdx++)
            {
                //一行对应一个Line节点
                XmlElement lineNode = xmlDoc.CreateElement("Line");
                dataNode.AppendChild(lineNode);
                for (int cell = 0; cell < tableData.TableHeaderList.Count-1; cell++)
                {
                    //行内一个单元格对应一个Item节点
                    XmlElement itemNode = xmlDoc.CreateElement("Item");
                    itemNode.InnerText = tableData.TBodyList[lineIdx][cell];
                    lineNode.AppendChild(itemNode);
                }
            }
            //创建表尾节点
            XmlElement tailNode = xmlDoc.CreateElement("TailName");
            tailNode.SetAttribute("LabelName", tableData.TableTail);
            tailNode.InnerText = tableData.FooterStr;
            tableNode.AppendChild(tailNode);
        }
        //创建其他文本数据节点
        XmlElement textNode = xmlDoc.CreateElement("Text");
        for (int i = 0; i < textDatas.Length; i++)
        {
            XmlElement itemNode = xmlDoc.CreateElement("Item");
            itemNode.SetAttribute("LabelName", string.Format("text{0}",i));
            itemNode.InnerText = textDatas[i];
            textNode.AppendChild(itemNode);
        }
        rootNode.AppendChild(textNode);

        //创建图片信息节点
        XmlElement pictureNode = xmlDoc.CreateElement("Picture");
        pictureNode.SetAttribute("LabelName", "image");
        pictureNode.InnerText = "picture";
        rootNode.AppendChild(pictureNode);

        string docPath = wordXMLPath + "/WordData.xml";
        xmlDoc.Save(docPath);//将所以表格数据保存到本地WordData.xml文件中
    }
}
/// <summary>
/// 表格数据类
/// </summary>
public class TableData
{
    /// <summary>
    /// 表名
    /// </summary>
    public string tableName;
    /// <summary>
    /// 表头数据
    /// </summary>
    public List<string> TableHeaderList=new List<string>();
    /// <summary>
    /// 表格每一行的内容
    /// </summary>
    public List<List<string>> TBodyList=new List<List<string>>();
    /// <summary>
    /// 表尾
    /// </summary>
    public string TableTail { get; set; }
    /// <summary>
    /// 表尾内容
    /// </summary>
    public string FooterStr;
}

将脚本挂载到物体上,在保存报告的按钮上添加监听SaveWord方法:
在这里插入图片描述
运行可以看到生成的WordData.xml内容:
在这里插入图片描述
在这里插入图片描述

WriteDataToWord.cs解析xml画出表格插入到word文件中
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Xml;
using Crosstales.FB;
using Aspose.Words;

namespace SaveWordToWindow
{
    public class WriteDataToWord
    {
        /// <summary>
        /// 模板Word文件名
        /// </summary>
        public string WordName;
        /// <summary>
        /// 插入到word中的文本数据信息
        /// </summary>
        public List<OtherData> textDatas = new List<OtherData>();
        /// <summary>
        /// 插入到word中的图片数据信息
        /// </summary>
        public List<OtherData> pictureDatas = new List<OtherData>();
        /// <summary>
        /// 插入到word中的表格数据信息
        /// </summary>
        public List<Table> tableDatas = new List<Table>();

        public string filePath = "";
        /// <summary>
        /// 生成一个新的Word文档,并可选择本地路径保存
        /// </summary>
        public void CreateNewWord(string path)
        {
            filePath = path;
            LoadingWordDataXML(path);
            InsertDataToWord();
        }


        /// <summary>
        /// 解析需要插入到Word中的数据信息节点  
        /// </summary>
        /// <param name="path"></param>
        private void LoadingWordDataXML(string path)
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(path+ "/WordData.xml");
            XmlNode node = doc.SelectSingleNode("Root");
            foreach (XmlNode item in node.ChildNodes)
            {
                switch (item.Name)
                {
                    case "WordName":
                        WordName = item.InnerText;
                        break;
                    case "Table":
                        ReadTableData(item);
                        break;
                    case "Text":
                        ReadTextData(item);
                        break;
                    case "Picture":
                        ReadPictureData(item);
                        break;
                }
            }
        }
        /// <summary>
        /// 读取xml中的表格数据节点
        /// </summary>
        /// <param name="node"></param>
        private void ReadTableData(XmlNode node)
        {
            Table table = new Table();
            table.TableTagName = node.Attributes["LabelName"].InnerText;
            table.TableName = node.SelectSingleNode("TableName").InnerText;
            foreach (XmlNode item in node.ChildNodes)
            {
                switch (item.Name)
                {
                    case "HeaderName":
                        table.TableHeader = new List<string>();
                        for (int i = 0; i < item.ChildNodes.Count; i++)
                        {
                            table.TableHeader.Add(item.ChildNodes[i].InnerText);
                        }
                        break;
                    case "Data":
                        table.TableData = new List<List<string>>();
                        for (int i = 0; i < item.ChildNodes.Count; i++)
                        {
                            List<string> rowData = new List<string>();
                            for (int j = 0; j < item.ChildNodes[i].ChildNodes.Count; j++)
                            {
                                rowData.Add(item.ChildNodes[i].ChildNodes[j].InnerText);
                            }
                            table.TableData.Add(rowData);
                        }
                        break;
                    case "TailName":
                        table.TableTail = item.Attributes["LabelName"].InnerText;
                        table.TableTailData = item.InnerText;
                        break;
                }
            }
            tableDatas.Add(table);
        }
        /// <summary>
        /// 读取xml中的图片数据节点
        /// </summary>
        /// <param name="node"></param>
        private void ReadPictureData(XmlNode node)
        {
            OtherData pData = new OtherData();
            pData.labelName = node.Attributes["LabelName"].InnerText;
            pData.Data = node.InnerText;
            pictureDatas.Add(pData);
        }
        /// <summary>
        /// 读取xml中的文本数据节点
        /// </summary>
        /// <param name="node"></param>
        private void ReadTextData(XmlNode node)
        {
            foreach (XmlNode item in node.ChildNodes)
            {
                OtherData textData = new OtherData();
                textData.labelName = item.Attributes["LabelName"].InnerText;
                textData.Data = item.InnerText;
                textDatas.Add(textData);
            }
        }
        /// <summary>
        /// 将数据插入到Wordw文档中
        /// </summary>
        private void InsertDataToWord()
        {
            //以WordName的word文件为模板
            Document doc = new Document(filePath + "\\" + WordName);
            //使用DocumentBuilder将各种数据插入到Word中
            DocumentBuilder docBuilder = new DocumentBuilder(doc);

            //插入文本数据
            for (int i = 0; i < textDatas.Count; i++)
            {
                try
                {
                    //获取到文档中textDatas[i].labelName标签
                    Bookmark bookMark = doc.Range.Bookmarks[textDatas[i].labelName];
                    if (bookMark != null)
                    {
                        bookMark.Text = "";//清除标示
                                           //移动到书签 textDatas[i].labelName处
                        if (docBuilder.MoveToBookmark(textDatas[i].labelName))
                        {
                            docBuilder.Write(textDatas[i].Data);//插入数据
                        }
                    }
                }
                catch (System.Exception e)
                {
                    Debug.Log("插入文本数据异常" + e.Message);
                }
            }

            //插入图片
            for (int i = 0; i < pictureDatas.Count; i++)
            {
                try
                {
                    Bookmark bookmark = doc.Range.Bookmarks[pictureDatas[i].labelName];
                    if (bookmark != null)
                    {
                        bookmark.Text = "";
                        if (docBuilder.MoveToBookmark(pictureDatas[i].labelName))
                        {
            string imgPath = string.Format("{0}\\{1}.png", filePath, pictureDatas[i].Data);
                            docBuilder.InsertImage(imgPath);
                        }
                    }
                }
                catch (System.Exception e)
                {
                    Debug.Log("插入图片异常" + e.Message);
                }
            }

            //生成表格并插入
            for (int i = 0; i < tableDatas.Count; i++)
            {
                try
                {
                    Table tableData = tableDatas[i];
                    //用来设置表格中的单元格宽度
                    int[] lens = { 10, 20, 20, 20, 20 };
                    Bookmark bookmark = doc.Range.Bookmarks[tableData.TableTagName];
                    if (bookmark != null)
                    {
                        bookmark.Text = "";
                        if (docBuilder.MoveToBookmark(tableData.TableTagName))
                        {
                            //开始创建表格
                            docBuilder.StartTable();
                            //生成表格表名
                            for (int iName = 0; iName < tableData.TableHeader.Count; iName++)
                            {
                                docBuilder.InsertCell();//插入单元格
                                docBuilder.CellFormat.Width = lens[iName];//设置单元格宽度
          docBuilder.ParagraphFormat.Alignment = ParagraphAlignment.Center;//设置单元格水平居中对齐
                                if (iName == 0)
                                {
                                    docBuilder.Write(tableData.TableName);//写入内容
 docBuilder.CellFormat.HorizontalMerge = Aspose.Words.Tables.CellMerge.First;//横向合并第一个单元格
                                }
                                else
                                {
                                //与前一个单元格合并,无需再写入内容
 docBuilder.CellFormat.HorizontalMerge = Aspose.Words.Tables.CellMerge.Previous;
                                }
                            }
                            //结束一行,并开始新的一行
                            docBuilder.EndRow();
                            //生成表头
                            for (int iHead = 0; iHead < tableData.TableHeader.Count; iHead++)
                            {
                                docBuilder.InsertCell();
                                //这里不单元格合并,所以需设置为None,否则会与前面的单元格合并
                     docBuilder.CellFormat.HorizontalMerge = Aspose.Words.Tables.CellMerge.None;
                                docBuilder.ParagraphFormat.Alignment = ParagraphAlignment.Center;
                                docBuilder.Write(tableData.TableHeader[iHead]);
                                docBuilder.CellFormat.Width = lens[iHead];
                            }
                            docBuilder.EndRow();
                            //生成表格内容
                            for (int iRow = 0; iRow < tableData.TableData.Count; iRow++)
                            {
                                //生成表示序号的单元格
                                docBuilder.InsertCell();
                                docBuilder.ParagraphFormat.Alignment = ParagraphAlignment.Center;
                                docBuilder.Write(string.Format("{0}", iRow + 1));
                                docBuilder.CellFormat.Width = lens[0];
                                List<string> cells = tableData.TableData[iRow];
                                //生成每一行的每一个单元格的内容
                                for (int iCell = 0; iCell < cells.Count; iCell++)
                                {
                                    docBuilder.InsertCell();
                            docBuilder.ParagraphFormat.Alignment = ParagraphAlignment.Center;
                                    docBuilder.Write(cells[iCell]);
                                    docBuilder.CellFormat.Width = lens[iCell + 1];
                                }
                                docBuilder.EndRow();
                            }
                            //判断是否有表尾
                            if (!string.IsNullOrEmpty(tableData.TableTail))
                            {
                                //生成表尾,表尾的第一个单元格与倒数第二个单元格需要合并,写入表尾名称
                                //最后一个单元格无需合并,写入表尾数据
                                for (int iTail = 0; iTail < tableData.TableHeader.Count; iTail++)
                                {
                                    docBuilder.InsertCell();
                                    docBuilder.CellFormat.Width = lens[iTail];
                    docBuilder.ParagraphFormat.Alignment = ParagraphAlignment.Center;
                                    if (iTail == 0)
                                    {
                                        docBuilder.Write(tableData.TableTail);
                                        //横向合并的第一个单元格
                  docBuilder.CellFormat.HorizontalMerge = Aspose.Words.Tables.CellMerge.First;
                                    }
                             else if (iTail > 0 && iTail < tableData.TableHeader.Count - 1)
                                    {
                                    //与前一个单元格合并
                  docBuilder.CellFormat.HorizontalMerge = Aspose.Words.Tables.CellMerge.Previous;
                                    }
                                    else
                                    {
                                     docBuilder.Write(tableData.TableTailData);
                                    //最后一个单元格无需合并
                     docBuilder.CellFormat.HorizontalMerge = Aspose.Words.Tables.CellMerge.None;
                                    }
                                }
                            }
                            docBuilder.EndRow();
                            //结束当前表格
                            docBuilder.EndTable();
                        }
                    }
                }
                catch (System.Exception e)
                {
                    Debug.Log("插入表格异常" + e.Message);
                }
            }


            //另存为一个更新后的Word文档,并可以选择路径保存
            string extensions = "doc";
            //这里用到了FileBrowser插件,用于打开本地资源浏览器保存文件
            doc.Save(FileBrowser.SaveFile("保存Word", "", "MySaveFile", extensions));
        }
    }
    /// <summary>
    /// 其他数据
    /// </summary>
    public class OtherData
    {
        /// <summary>
        /// 标签名
        /// </summary>
        public string labelName;
        /// <summary>
        /// 数据信息
        /// </summary>
        public string Data;
    }

    /// <summary>
    /// 表格数据
    /// </summary>
    public class Table
    {
        /// <summary>
        /// 表格名
        /// </summary>
        public string TableName;
        /// <summary>
        /// 表格标签
        /// </summary>
        public string TableTagName;
        /// <summary>
        /// 表头
        /// </summary>
        public List<string> TableHeader;
        /// <summary>
        /// 表格内容
        /// </summary>
        public List<List<string>> TableData;
        /// <summary>
        /// 表尾
        /// </summary>
        public string TableTail;
        /// <summary>
        /// 表尾内容
        /// </summary>
        public string TableTailData;

    }
}

注释写的很明白了,首先解析在xml中的表格信息、文本信息、图片信息,分别保存到Table类和OtherData类的数据结构中去,最后根据这些数据创建一个个单元格,写入内容,插入文本和图片到Word中,并可另存到本地。

  • 模板Word中的标签要与xml文档中生成的一致
    添加标签:在这里插入图片描述

运行结果:

在这里插入图片描述

代码对应生成的数据:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

使用Texture2D.ReadPixels进行指定UI界面截图

ScreenShotByUI.cs

using UnityEngine;
using System.Collections;
public class ScreenShotByUI : MonoBehaviour
{
    /// <summary>
    /// 截图的UI
    /// </summary>
    public RectTransform ScreenShotUI;
    //保存的图片路径
    string picturePath;
    /// <summary>
    /// 点击开始截图方法
    /// </summary>
    public void ScreenShotClick()
    {
        picturePath = Application.dataPath + "/../data/Word/picture.png";
        IEnumerator coroutine = ScreenShot(ScreenShotUI, picturePath);
        StartCoroutine(coroutine);
    }
    /// <summary>
    /// 异步读取UI像素转为字节流数据并保存
    /// </summary>
    /// <param name="ScreenShotUI"></param>
    /// <param name="mFileName">保存截图的路径名</param>
    /// <returns></returns>
    public IEnumerator ScreenShot(RectTransform ScreenShotUI, string mFileName)
    {
        //等待帧画面渲染结束
        yield return new WaitForEndOfFrame();

        int uiWidth = (int)(ScreenShotUI.rect.width);
        int uiHeight = (int)(ScreenShotUI.rect.height);

        Texture2D tex = new Texture2D(uiWidth, uiHeight, TextureFormat.RGB24, false);

        //左下角为原点(0, 0)
        float uiLeftX = ScreenShotUI.transform.position.x + ScreenShotUI.rect.xMin;
        float uiLeftY = ScreenShotUI.transform.position.y + ScreenShotUI.rect.yMin;

        //从屏幕读取像素, leftBtmX/leftBtnY 是读取的初始位置,width、height是读取像素的宽度和高度
        //可以根据需求更改宽高
        tex.ReadPixels(new Rect(uiLeftX, uiLeftY, uiWidth, uiHeight), 0, 0);
        //执行读取操作
        tex.Apply();
        //将纹理图片转为字节流数据
        byte[] bytes = tex.EncodeToPNG();
        //将字节流数据保存到本地
        System.IO.File.WriteAllBytes(mFileName, bytes);
    }
}

将脚本拖拽到游戏物体上,将要截图的UI拖拽到ScreenShotUI处:
在这里插入图片描述
运行可以看到图片保存到本地了:
在这里插入图片描述

需要注意的点

  • 创建表格和插入图片用到的两个DLL文件:Aspose.Words和System.Drawing
    在这里插入图片描述
    需在.net3.5平台下运行,且要注意编辑器是32位还是64位,dll设置里勾选对应的we如果不是,得在设置里修改:

在这里插入图片描述

  • 15
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周周的Unity小屋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值