java使用freeMark生成word,转word为pdf,利用pdfjs实现预览word

最近公司项目要用到一个预览word功能,查阅了好多资料,发现都有许多限制,想用插件但是不兼容各种浏览器,然后想到转成pdf预览。生成word文档我用的freemark。

1.首先用wps创建一个word模板,模板里的动态参数可以用 ${xxx} 来表示(注:这是free mark的写法),定义好模板然后另存为xml格式,将xml模板的后缀改为 .ftl,这就是一个freemark要用到的word模板(注:有时候里边的自定义数据会自动换行,这时候要点进模板看一眼自己的动态数据项有没有被换行,换行就放在一行,删掉其他的就可以了)。
2.在pom.xml里引入需要的包,还有一点需要注意,我这里pox引入的是aspose-words-jdk1614.9.0,maven仓库没有这个版本,需要单独下载下来引导maven仓库:aspose-words-jdk1614.9.0,或者你们也可以去maven仓库找一下其他版本看能用不,我这里没做测试:

	<dependency>
		<groupId>org.freemarker</groupId>
		<artifactId>freemarker</artifactId>
		<version>2.3.20</version>
	</dependency>
	<!--aspose doc转pdf-->
	<dependency>
		<groupId>com.aspose.words</groupId>
		<artifactId>aspose-words-jdk16</artifactId>
		<version>14.9.0</version>
	</dependency>

3.创建word

@PostMapping("/createWord")
    @ResponseBody
    public String createWord() {
        /** 用于组装word页面需要的数据 */
        Map<String, Object> dataMap = new HashMap<String, Object>();
        /** 组装数据 */
        //部门
        dataMap.put("dept","建设管理处、建设管理中心");
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
        //日报生成日期
        dataMap.put("date", sdf.format(new Date()));
        //营业线要点施工情况
        dataMap.put("content","11月7日,营业线要点24项,均为III级施工。");
        //主要工程项目日完成实物工作(表格)
        List<Map<String, Object>> list2 = new ArrayList<Map<String, Object>>();
        for (int b = 0; b <= 7; b++) {
            Map<String, Object> map2 = new HashMap<String, Object>();
            map2.put("project","1.京路工程:");
            if(b<2){
                map2.put("construction_project","路基");
            }else{
                map2.put("construction_project","路基"+b);
            }
            map2.put("construction_project1","土石方"+b+":");
            map2.put("company","单位"+b);
            map2.put("design", "设计" + b);
            map2.put("same_day_complet", "今日完成" + b);
            map2.put("open_tired_finish", "开累完成" + b);
            map2.put("percent_complete", "完成百分比" + b);
            map2.put("remark", "备注" + b);
            list2.add(map2);
        }
        dataMap.put("list2",list2);
        /** 文件名称,唯一字符串 */
        Random r = new Random();
        SimpleDateFormat sdf1 = new SimpleDateFormat("yyyyMMdd");
        StringBuffer sb = new StringBuffer();
        sb.append(sdf1.format(new Date()));
        sb.append("_");
        sb.append(r.nextInt(100));
        //文件路径
        filePath = "E:/";
        //文件唯一名称
        fileOnlyName = "建设日报_" + sb ;
        //文件名称
        fileName = "建设日报.doc";
        /** 生成word */
        WordUtil.createWord(dataMap, "freeMark/freeMark.ftl", filePath, fileOnlyName+ ".doc");
        return filePath+fileOnlyName;
    }

package com.xxx.common.utils.createword;

import freemarker.template.Configuration;
import freemarker.template.Template;

import java.io.*;
import java.util.Map;

public class WordUtil {

    /**
     * 生成word文件
     * @param dataMap word中需要展示的动态数据,用map集合来保存
     * @param templateName word模板名称,例如:test.ftl
     * @param filePath 文件生成的目标路径,例如:D:/wordFile/
     * @param fileName 生成的文件名称,例如:test.doc
     */
    @SuppressWarnings("unchecked")
    public static void createWord(Map dataMap,String templateName,String filePath,String fileName){
        try {
            //创建配置实例
            Configuration configuration = new Configuration();

            //设置编码
            configuration.setDefaultEncoding("UTF-8");

            //ftl模板文件
            configuration.setClassForTemplateLoading(WordUtil.class,"/");

            //获取模板
            Template template = configuration.getTemplate(templateName);

            //输出文件
            File outFile = new File(filePath+File.separator+fileName);

            //如果输出目标文件夹不存在,则创建
            if (!outFile.getParentFile().exists()){
                outFile.getParentFile().mkdirs();
            }

            //将模板和数据模型合并生成文件
            Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile),"UTF-8"));


            //生成文件
            template.process(dataMap, out);

            //关闭流
            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

前端调用该方法,会在设置的目录盘下生成一个word文件。
4.接下来就要将word文件转为pdf了,将生成word文件的名字带路径传到下边方法中,不要带文档的后缀(eg:d:\webrct\xxx)文件为xxx.doc:

@PostMapping("/doc2pdf")
    @ResponseBody
    public void doc2pdf(String fileName, HttpServletResponse response) {
        File pdfFile = null;
        OutputStream outputStream = null;
        BufferedInputStream bufferedInputStream = null;
        String docPath = fileName + ".doc";
        String pdfPath = fileName + ".pdf";
        try {
            pdfFile = Doc2PdfUtil.doc2Pdf(docPath, pdfPath);
            outputStream = response.getOutputStream();
            response.setContentType("application/pdf;charset=UTF-8");
            bufferedInputStream = new BufferedInputStream(new FileInputStream(pdfFile));
            byte buffBytes[] = new byte[1024];
            outputStream = response.getOutputStream();
            int read = 0;
            while ((read = bufferedInputStream.read(buffBytes)) != -1) {
                outputStream.write(buffBytes, 0, read);
            }
        } catch (ConnectException e) {
            logger.info("****调用Doc2PdfUtil doc转pdf失败****");
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.flush();
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bufferedInputStream != null) {
                try {
                    bufferedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
package com.shineyoo.common.utils.wordtopdf;

import com.aspose.words.Document;
import com.aspose.words.FontSettings;
import com.aspose.words.License;
import com.aspose.words.SaveFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;

public class Doc2PdfUtil {
    private static Logger logger = LoggerFactory.getLogger(Doc2PdfUtil.class);

    /**
     * doc转pdf
     *
     * @param docPath doc文件路径,包含.doc
     * @param pdfPath pdf文件路径,包含.pdf
     * @return
     */
    public static File doc2Pdf(String docPath, String pdfPath) {
        System.out.println("pdfPath = "+pdfPath);
        System.out.println("pdfPath = "+pdfPath);
       File pdfFile = new File(pdfPath);
        //判断是否windows系统,Linux要读取字体,否则pdf字体为方格
        if(!OSinfo.isWindows()){
 //在Linux 里没有中文字体,pdf会出现方格,需要手动将windows目录(C:\Windows\Fonts)的字体包考到linux的字体目录下,然后用这个方法指定读取一下字体
FontSettings.getDefaultInstance().setFontsFolder(File.separator + "usr"
                    + File.separator + "share" + File.separator + "fonts" +File.separator + "Fonts", true);
        }
        try {
            String s = "<License><Data><Products><Product>Aspose.Total for Java</Product><Product>Aspose.Words for Java</Product></Products><EditionType>Enterprise</EditionType><SubscriptionExpiry>20991231</SubscriptionExpiry><LicenseExpiry>20991231</LicenseExpiry><SerialNumber>8bfe198c-7f0c-4ef8-8ff0-acc3237bf0d7</SerialNumber></Data><Signature>sNLLKGMUdF0r8O1kKilWAGdgfs2BvJb/2Xp8p5iuDVfZXmhppo+d0Ran1P9TKdjV4ABwAgKXxJ3jcQTqE/2IRfqwnPf8itN8aFZlV3TJPYeD3yWE7IT55Gz6EijUpC7aKeoohTb4w2fpox58wWoF3SNp6sK6jDfiAUGEHYJ9pjU=</Signature></License>";
            ByteArrayInputStream is = new ByteArrayInputStream(s.getBytes());
            License license = new License();
            license.setLicense(is);
            Document document = new Document(docPath);
            FileOutputStream fileOutputStream = new FileOutputStream(pdfFile);
            System.out.println("pdf文件:"+fileOutputStream);
            document.save(fileOutputStream, SaveFormat.PDF);
        } catch (Exception e) {
            logger.info("****aspose doc转pdf异常");
            e.printStackTrace();
        }
        return pdfFile;
    }
}

5.下载pdfjs文件包,引入项目中,可以去官网,也可以去这里:pdfjs文件包
这是我引入的文件
因为需要给wiewer.html传参数,为了方便我将里边的viewer.html拿出来了,传参js的位置一定要放在页面中这个上,因为要先获取到参数,传到viewer.js的DEFAULT_URL这个字段里,参数就是生成pdf的全路径,里边有个js方法会转换成pdf.js能直接解析的Uint8Array类型,见pdf.js-4068。下面附上我的页面:

PDF.js viewer
Thumbnails Document Outline Attachments
  <div id="mainContainer">
    <div class="findbar hidden doorHanger hiddenSmallView" id="findbar">
      <label for="findInput" class="toolbarLabel" data-l10n-id="find_label">Find:</label>
      <input id="findInput" class="toolbarField" tabindex="91">
      <div class="splitToolbarButton">
        <button class="toolbarButton findPrevious" title="" id="findPrevious" tabindex="92" data-l10n-id="find_previous">
          <span data-l10n-id="find_previous_label">Previous</span>
        </button>
        <div class="splitToolbarButtonSeparator"></div>
        <button class="toolbarButton findNext" title="" id="findNext" tabindex="93" data-l10n-id="find_next">
          <span data-l10n-id="find_next_label">Next</span>
        </button>
      </div>
      <input type="checkbox" id="findHighlightAll" class="toolbarField" tabindex="94">
      <label for="findHighlightAll" class="toolbarLabel" data-l10n-id="find_highlight">Highlight all</label>
      <input type="checkbox" id="findMatchCase" class="toolbarField" tabindex="95">
      <label for="findMatchCase" class="toolbarLabel" data-l10n-id="find_match_case_label">Match case</label>
      <span id="findResultsCount" class="toolbarLabel hidden"></span>
      <span id="findMsg" class="toolbarLabel"></span>
    </div>  <!-- findbar -->

    <div id="secondaryToolbar" class="secondaryToolbar hidden doorHangerRight">
      <div id="secondaryToolbarButtonContainer">
        <button id="secondaryPresentationMode" class="secondaryToolbarButton presentationMode visibleLargeView" title="Switch to Presentation Mode" tabindex="51" data-l10n-id="presentation_mode">
          <span data-l10n-id="presentation_mode_label">Presentation Mode</span>
        </button>

        <button id="secondaryOpenFile" class="secondaryToolbarButton openFile visibleLargeView" title="Open File" tabindex="52" data-l10n-id="open_file">
          <span data-l10n-id="open_file_label">Open</span>
        </button>

        <button id="secondaryPrint" class="secondaryToolbarButton print visibleMediumView" title="Print" tabindex="53" data-l10n-id="print">
          <span data-l10n-id="print_label">Print</span>
        </button>

        <button id="secondaryDownload" class="secondaryToolbarButton download visibleMediumView" title="Download" tabindex="54" data-l10n-id="download">
          <span data-l10n-id="download_label">Download</span>
        </button>

        <a href="#" id="secondaryViewBookmark" class="secondaryToolbarButton bookmark visibleSmallView" title="Current view (copy or open in new window)" tabindex="55" data-l10n-id="bookmark">
          <span data-l10n-id="bookmark_label">Current View</span>
        </a>

        <div class="horizontalToolbarSeparator visibleLargeView"></div>

        <button id="firstPage" class="secondaryToolbarButton firstPage" title="Go to First Page" tabindex="56" data-l10n-id="first_page">
          <span data-l10n-id="first_page_label">Go to First Page</span>
        </button>
        <button id="lastPage" class="secondaryToolbarButton lastPage" title="Go to Last Page" tabindex="57" data-l10n-id="last_page">
          <span data-l10n-id="last_page_label">Go to Last Page</span>
        </button>

        <div class="horizontalToolbarSeparator"></div>

        <button id="pageRotateCw" class="secondaryToolbarButton rotateCw" title="Rotate Clockwise" tabindex="58" data-l10n-id="page_rotate_cw">
          <span data-l10n-id="page_rotate_cw_label">Rotate Clockwise</span>
        </button>
        <button id="pageRotateCcw" class="secondaryToolbarButton rotateCcw" title="Rotate Counterclockwise" tabindex="59" data-l10n-id="page_rotate_ccw">
          <span data-l10n-id="page_rotate_ccw_label">Rotate Counterclockwise</span>
        </button>

        <div class="horizontalToolbarSeparator"></div>

        <button id="toggleHandTool" class="secondaryToolbarButton handTool" title="Enable hand tool" tabindex="60" data-l10n-id="hand_tool_enable">
          <span data-l10n-id="hand_tool_enable_label">Enable hand tool</span>
        </button>

        <div class="horizontalToolbarSeparator"></div>

        <button id="documentProperties" class="secondaryToolbarButton documentProperties" title="Document Properties…" tabindex="61" data-l10n-id="document_properties">
          <span data-l10n-id="document_properties_label">Document Properties…</span>
        </button>
      </div>
    </div>  <!-- secondaryToolbar -->

    <div class="toolbar">
      <div id="toolbarContainer">
        <div id="toolbarViewer">
          <div id="toolbarViewerLeft">
            <button id="sidebarToggle" class="toolbarButton" title="Toggle Sidebar" tabindex="11" data-l10n-id="toggle_sidebar">
              <span data-l10n-id="toggle_sidebar_label">Toggle Sidebar</span>
            </button>
            <div class="toolbarButtonSpacer"></div>
            <button id="viewFind" class="toolbarButton group hiddenSmallView" title="Find in Document" tabindex="12" data-l10n-id="findbar">
               <span data-l10n-id="findbar_label">Find</span>
            </button>
            <div class="splitToolbarButton">
              <button class="toolbarButton pageUp" title="Previous Page" id="previous" tabindex="13" data-l10n-id="previous">
                <span data-l10n-id="previous_label">Previous</span>
              </button>
              <div class="splitToolbarButtonSeparator"></div>
              <button class="toolbarButton pageDown" title="Next Page" id="next" tabindex="14" data-l10n-id="next">
                <span data-l10n-id="next_label">Next</span>
              </button>
            </div>
            <label id="pageNumberLabel" class="toolbarLabel" for="pageNumber" data-l10n-id="page_label">Page: </label>
            <input type="number" id="pageNumber" class="toolbarField pageNumber" value="1" size="4" min="1" tabindex="15">
            <span id="numPages" class="toolbarLabel"></span>
          </div>
          <div id="toolbarViewerRight">
            <button id="presentationMode" class="toolbarButton presentationMode hiddenLargeView" title="Switch to Presentation Mode" tabindex="31" data-l10n-id="presentation_mode">
              <span data-l10n-id="presentation_mode_label">Presentation Mode</span>
            </button>

            <button id="openFile" class="toolbarButton openFile hiddenLargeView" title="Open File" tabindex="32" data-l10n-id="open_file">
              <span data-l10n-id="open_file_label">Open</span>
            </button>

            <button id="print" class="toolbarButton print hiddenMediumView" title="Print" tabindex="33" data-l10n-id="print">
              <span data-l10n-id="print_label">Print</span>
            </button>

            <button id="download" class="toolbarButton download hiddenMediumView" title="Download" tabindex="34" data-l10n-id="download">
              <span data-l10n-id="download_label">Download</span>
            </button>
            <a href="#" id="viewBookmark" class="toolbarButton bookmark hiddenSmallView" title="Current view (copy or open in new window)" tabindex="35" data-l10n-id="bookmark">
              <span data-l10n-id="bookmark_label">Current View</span>
            </a>

            <div class="verticalToolbarSeparator hiddenSmallView"></div>

            <button id="secondaryToolbarToggle" class="toolbarButton" title="Tools" tabindex="36" data-l10n-id="tools">
              <span data-l10n-id="tools_label">Tools</span>
            </button>
          </div>
          <div class="outerCenter">
            <div class="innerCenter" id="toolbarViewerMiddle">
              <div class="splitToolbarButton">
                <button id="zoomOut" class="toolbarButton zoomOut" title="Zoom Out" tabindex="21" data-l10n-id="zoom_out">
                  <span data-l10n-id="zoom_out_label">Zoom Out</span>
                </button>
                <div class="splitToolbarButtonSeparator"></div>
                <button id="zoomIn" class="toolbarButton zoomIn" title="Zoom In" tabindex="22" data-l10n-id="zoom_in">
                  <span data-l10n-id="zoom_in_label">Zoom In</span>
                 </button>
              </div>
              <span id="scaleSelectContainer" class="dropdownToolbarButton">
                 <select id="scaleSelect" title="Zoom" tabindex="23" data-l10n-id="zoom">
                  <option id="pageAutoOption" title="" value="auto" selected="selected" data-l10n-id="page_scale_auto">Automatic Zoom</option>
                  <option id="pageActualOption" title="" value="page-actual" data-l10n-id="page_scale_actual">Actual Size</option>
                  <option id="pageFitOption" title="" value="page-fit" data-l10n-id="page_scale_fit">Fit Page</option>
                  <option id="pageWidthOption" title="" value="page-width" data-l10n-id="page_scale_width">Full Width</option>
                  <option id="customScaleOption" title="" value="custom" hidden="true"></option>
                  <option title="" value="0.5" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 50 }'>50%</option>
                  <option title="" value="0.75" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 75 }'>75%</option>
                  <option title="" value="1" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 100 }'>100%</option>
                  <option title="" value="1.25" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 125 }'>125%</option>
                  <option title="" value="1.5" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 150 }'>150%</option>
                  <option title="" value="2" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 200 }'>200%</option>
                  <option title="" value="3" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 300 }'>300%</option>
                  <option title="" value="4" data-l10n-id="page_scale_percent" data-l10n-args='{ "scale": 400 }'>400%</option>
                </select>
              </span>
            </div>
          </div>
        </div>
        <div id="loadingBar">
          <div class="progress">
            <div class="glimmer">
            </div>
          </div>
        </div>
      </div>
    </div>

    <menu type="context" id="viewerContextMenu">
      <menuitem id="contextFirstPage" label="First Page"
                data-l10n-id="first_page"></menuitem>
      <menuitem id="contextLastPage" label="Last Page"
                data-l10n-id="last_page"></menuitem>
      <menuitem id="contextPageRotateCw" label="Rotate Clockwise"
                data-l10n-id="page_rotate_cw"></menuitem>
      <menuitem id="contextPageRotateCcw" label="Rotate Counter-Clockwise"
                data-l10n-id="page_rotate_ccw"></menuitem>
    </menu>

    <div id="viewerContainer" tabindex="0">
      <div id="viewer" class="pdfViewer"></div>
    </div>

    <div id="errorWrapper" hidden='true'>
      <div id="errorMessageLeft">
        <span id="errorMessage"></span>
        <button id="errorShowMore" data-l10n-id="error_more_info">
          More Information
        </button>
        <button id="errorShowLess" data-l10n-id="error_less_info" hidden='true'>
          Less Information
        </button>
      </div>
      <div id="errorMessageRight">
        <button id="errorClose" data-l10n-id="error_close">
          Close
        </button>
      </div>
      <div class="clearBoth"></div>
      <textarea id="errorMoreInfo" hidden='true' readonly="readonly"></textarea>
    </div>
  </div> <!-- mainContainer -->

  <div id="overlayContainer" class="hidden">
    <div id="passwordOverlay" class="container hidden">
      <div class="dialog">
        <div class="row">
          <p id="passwordText" data-l10n-id="password_label">Enter the password to open this PDF file:</p>
        </div>
        <div class="row">
          <!-- The type="password" attribute is set via script, to prevent warnings in Firefox for all http:// documents. -->
          <input id="password" class="toolbarField">
        </div>
        <div class="buttonRow">
          <button id="passwordCancel" class="overlayButton"><span data-l10n-id="password_cancel">Cancel</span></button>
          <button id="passwordSubmit" class="overlayButton"><span data-l10n-id="password_ok">OK</span></button>
        </div>
      </div>
    </div>
    <div id="documentPropertiesOverlay" class="container hidden">
      <div class="dialog">
        <div class="row">
          <span data-l10n-id="document_properties_file_name">File name:</span> <p id="fileNameField">-</p>
        </div>
        <div class="row">
          <span data-l10n-id="document_properties_file_size">File size:</span> <p id="fileSizeField">-</p>
        </div>
        <div class="separator"></div>
        <div class="row">
          <span data-l10n-id="document_properties_title">Title:</span> <p id="titleField">-</p>
        </div>
        <div class="row">
          <span data-l10n-id="document_properties_author">Author:</span> <p id="authorField">-</p>
        </div>
        <div class="row">
          <span data-l10n-id="document_properties_subject">Subject:</span> <p id="subjectField">-</p>
        </div>
        <div class="row">
          <span data-l10n-id="document_properties_keywords">Keywords:</span> <p id="keywordsField">-</p>
        </div>
        <div class="row">
          <span data-l10n-id="document_properties_creation_date">Creation Date:</span> <p id="creationDateField">-</p>
        </div>
        <div class="row">
          <span data-l10n-id="document_properties_modification_date">Modification Date:</span> <p id="modificationDateField">-</p>
        </div>
        <div class="row">
          <span data-l10n-id="document_properties_creator">Creator:</span> <p id="creatorField">-</p>
        </div>
        <div class="separator"></div>
        <div class="row">
          <span data-l10n-id="document_properties_producer">PDF Producer:</span> <p id="producerField">-</p>
        </div>
        <div class="row">
          <span data-l10n-id="document_properties_version">PDF Version:</span> <p id="versionField">-</p>
        </div>
        <div class="row">
          <span data-l10n-id="document_properties_page_count">Page Count:</span> <p id="pageCountField">-</p>
        </div>
        <div class="buttonRow">
          <button id="documentPropertiesClose" class="overlayButton"><span data-l10n-id="document_properties_close">Close</span></button>
        </div>
      </div>
    </div>
  </div>  <!-- overlayContainer -->

</div> <!-- outerContainer -->
<div id="printContainer"></div>

display: block;
text-align: center;
background-color: rgba(0, 0, 0, 0.5);
}
#mozPrintCallback-shim[hidden] {
display: none;
}
@media print {
#mozPrintCallback-shim {
display: none;
}
}

#mozPrintCallback-shim .mozPrintCallback-dialog-box {
display: inline-block;
margin: -50px auto 0;
position: relative;
top: 45%;
left: 0;
min-width: 220px;
max-width: 400px;

padding: 9px;

border: 1px solid hsla(0, 0%, 0%, .5);
border-radius: 2px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);

background-color: #474747;

color: hsl(0, 0%, 85%);
font-size: 16px;
line-height: 20px;
}
#mozPrintCallback-shim .progress-row {
clear: both;
padding: 1em 0;
}
#mozPrintCallback-shim progress {
width: 100%;
}
#mozPrintCallback-shim .relative-progress {
clear: both;
float: right;
}
#mozPrintCallback-shim .progress-actions {
clear: both;
}

Preparing document for printing...
0%

将参数直接传到这个页面,获取到参数viewer.js里会有方法渲染这个页面。最终展示出预览的pdf。
预览的文件小菜鸟一枚,写的不好,有什么疑问可以留言咨询

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值