• Spring4 MVC ContentNegotiatingViewResolver多种输出格式实例


    本文演示支持多种输出格式,这里 Spring4 MVC应用程序使用了 Spring ContentNegotiatingViewResolver 。我们将生成应用程序输出XML,JSON,PDF,XLS和HTML格式,全部采用基于注解配置的。

    ContentNegotiatingViewResolver是 ViewResolver 使用所请求的媒体类型的一个实现(基于文件类型扩展,输出格式URL参数指定类型或接受报头)来选择一个合适的视图一个请求。ContentNegotiatingViewResolver本身并不解决视图,只不表示为其他的 ViewResolver,您可以配置来处理特定的视图(XML,JSON,PDF,XLS,HTML,..)。

    这里需要使用到以下技术:

    • Spring 4.0.6.RELEASE
    • jackson-databind 2.4.1.3
    • jackson-annotations 2.4.1
    • lowagie itext 4.2.1
    • Apache POI 3.10-beta2
    • Maven 3
    • JDK 1.6
    • Tomcat 7.0.54
    • Eclipse JUNO Service Release 2

    我们现在就开始!

    第1步:创建目录结构
    以下将是本实例的最终目录结构:

    我们将使用Spring Java配置(注释)。现在,让我们来添加/更新上述项目结构中提到的内容。
    第2步:用所需的依赖更新 pom.xml
    <?xml version="1.0"?>
    <project
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
    	xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    
    	<modelVersion>4.0.0</modelVersion>
    	<groupId>com.yiibai.springmvc</groupId>
    	<artifactId>ContentNegotiatingViewResolver</artifactId> <packaging>war</packaging>
    	<version>1.0.0</version>
    	<name>Spring4MVCContentNegotiatingViewResolverExample</name>
    
    	<properties>
    		<springframework.version>4.0.6.RELEASE</springframework.version>
    	</properties>
    
    	<dependencies>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-core</artifactId>
    			<version>${springframework.version}</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-web</artifactId>
    			<version>${springframework.version}</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-webmvc</artifactId>
    			<version>${springframework.version}</version>
    		</dependency>
    
    		<!-- Needed for XML View (with JAXB2) -->
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-oxm</artifactId>
    			<version>${springframework.version}</version>
    		</dependency>
    
    		<!-- Needed for JSON View -->
    		<dependency>
    			<groupId>com.fasterxml.jackson.core</groupId>
    			<artifactId>jackson-databind</artifactId>
    			<version>2.4.1.3</version>
    		</dependency>
    		<dependency>
    			<groupId>com.fasterxml.jackson.core</groupId>
    			<artifactId>jackson-annotations</artifactId>
    			<version>2.4.1</version>
    		</dependency>
    
    		<!-- Needed for PDF View -->
    		<dependency>
    			<groupId>com.lowagie</groupId>
    			<artifactId>itext</artifactId>
    			<version>4.2.1</version>
    		</dependency>
    		
    		<!-- Needed for XLS View -->		
    		<dependency>
    			<groupId>org.apache.poi</groupId>
    			<artifactId>poi</artifactId>
    			<version>3.10-beta2</version>
    		</dependency>
    
    		<!-- Servlet dependencies -->
    		<dependency>
    			<groupId>javax.servlet</groupId>
    			<artifactId>javax.servlet-api</artifactId>
    			<version>3.1.0</version>
    		</dependency>
    		<dependency>
    		    <groupId>javax.servlet</groupId>
    		    <artifactId>jstl</artifactId>
    		    <version>1.2</version>
    		</dependency>
    		<dependency>
    			<groupId>javax.servlet.jsp</groupId>
    			<artifactId>javax.servlet.jsp-api</artifactId>
    			<version>2.3.1</version>
    		</dependency>
    
    	</dependencies>
    
    
    	<build>
    		<pluginManagement>
    			<plugins>
    				<plugin>
    					<groupId>org.apache.maven.plugins</groupId>
    					<artifactId>maven-war-plugin</artifactId>
    					<version>2.4</version>
    					<configuration>
    						<warSourceDirectory>src/main/webapp</warSourceDirectory>
    						<warName>ContentNegotiatingViewResolver</warName> <failOnMissingWebXml>false</failOnMissingWebXml>
    					</configuration>
    				</plugin>
    			</plugins>
    		</pluginManagement>
    
    		<finalName>ContentNegotiatingViewResolver</finalName> </build>
    </project>
     

    上面解析 PDF 的依赖库有点问题,可修改为以下测试构建就没有问题:

     <dependency>
                 <groupId>com.lowagie</groupId>
              <artifactId>itext</artifactId>
              <version>2.1.7</version>
              <scope>compile</scope>
            </dependency>

    spring-oxm是为了支持XML输出生成(使用JAXB2)。 jackson-databind &jackson-annotations 提供JSON输出支持。iText的提供PDF生成库,支持PDF输出。 Apache POI将有助于产生XLS输出格式。

    第3步:创建Spring配置文件类

    com.yiibai.springmvc.configuration.AppConfig

    package com.yiibai.springmvc.configuration;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.MediaType;
    import org.springframework.oxm.jaxb.Jaxb2Marshaller;
    import org.springframework.web.accept.ContentNegotiationManager;
    import org.springframework.web.servlet.ViewResolver;
    import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
    import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
    import org.springframework.web.servlet.view.InternalResourceViewResolver;
    import org.springframework.web.servlet.view.JstlView;
    
    import com.yiibai.springmvc.model.Pizza;
    import com.yiibai.springmvc.viewresolver.ExcelViewResolver;
    import com.yiibai.springmvc.viewresolver.JsonViewResolver;
    import com.yiibai.springmvc.viewresolver.Jaxb2MarshallingXmlViewResolver;
    import com.yiibai.springmvc.viewresolver.PdfViewResolver;
    
    @Configuration
    @EnableWebMvc
    @ComponentScan(basePackages = "com.yiibai.springmvc")
    public class AppConfig extends WebMvcConfigurerAdapter {
    
    	/*
    	 * Configure ContentNegotiationManager
    	 */
    	@Override
    	public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    		configurer.ignoreAcceptHeader(true).defaultContentType(
    				MediaType.TEXT_HTML);
    	}
    
    	/*
    	 * Configure ContentNegotiatingViewResolver
    	 */
    	@Bean
    	public ViewResolver contentNegotiatingViewResolver(ContentNegotiationManager manager) {
    		ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
    		resolver.setContentNegotiationManager(manager);
    
    		// Define all possible view resolvers
    		List<ViewResolver> resolvers = new ArrayList<ViewResolver>();
    
    		resolvers.add(jaxb2MarshallingXmlViewResolver());
    		resolvers.add(jsonViewResolver());
    		resolvers.add(jspViewResolver());
    		resolvers.add(pdfViewResolver());
    		resolvers.add(excelViewResolver());
    		
    		resolver.setViewResolvers(resolvers);
    		return resolver;
    	}
    
    	/*
    	 * Configure View resolver to provide XML output Uses JAXB2 marshaller to
    	 * marshall/unmarshall POJO's (with JAXB annotations) to XML
    	 */
    	@Bean
    	public ViewResolver jaxb2MarshallingXmlViewResolver() {
    		Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
    		marshaller.setClassesToBeBound(Pizza.class);
    		return new Jaxb2MarshallingXmlViewResolver(marshaller);
    	}
    
    	/*
    	 * Configure View resolver to provide JSON output using JACKSON library to
    	 * convert object in JSON format.
    	 */
    	@Bean
    	public ViewResolver jsonViewResolver() {
    		return new JsonViewResolver();
    	}
    
    	/*
    	 * Configure View resolver to provide PDF output using lowagie pdf library to
    	 * generate PDF output for an object content
    	 */
    	@Bean
    	public ViewResolver pdfViewResolver() {
    		return new PdfViewResolver();
    	}
    
    	/*
    	 * Configure View resolver to provide XLS output using Apache POI library to
    	 * generate XLS output for an object content
    	 */
    	@Bean
    	public ViewResolver excelViewResolver() {
    		return new ExcelViewResolver();
    	}
    
    	/*
    	 * Configure View resolver to provide HTML output This is the default format
    	 * in absence of any type suffix.
    	 */
    	@Bean
    	public ViewResolver jspViewResolver() {
    		InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
    		viewResolver.setViewClass(JstlView.class);
    		viewResolver.setPrefix("/WEB-INF/views/");
    		viewResolver.setSuffix(".jsp");
    		return viewResolver;
    	}
    
    }
    
    让我们来讨论说明上面的类的详细信息:

    第一步是建立它用于通过委托给ContentNegotiationManager,以确定所请求的媒体类型的请求是 ContentNegotiationStrategy 列表的一个实例。默认情况下PathExtensionContentNegotiationStrategy被查询(使用URL扩展名,例如. .xls, .pdf,.json.),接着ParameterContentNegotiationStrategy(使用请求参数 ‘format=xls’,例如),其次是HeaderContentNegotiationStrategy(使用HTTP接受头)。

    	public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    		configurer.ignoreAcceptHeader(true).defaultContentType(
    				MediaType.TEXT_HTML);
    	}
     

    在我们的例子中,我们将使用URL扩展名来帮助确定媒体类型。此外,我们还设置默认介质类型TEXT_HTML在没有文件扩展名或当文件类型是未知时,这意味着JSP视图解析器将被用于在没有[known] URL扩展中。

    下面是 pizza.jsp 默认使用JSP视图解析器内容
    <%@ page language="java" contentType="text/html; charset=utf-8"  pageEncoding="utf-8"%>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <html>
    <head>
    	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    	<title>Pizza JSP View</title>
    </head>
    <body>
    	<table border="1">
    		<tr>
    		<td>NAME</td>
    		<td>Flavor</td>
    		<td>Toppings</td>
    		</tr>
    		<tr>
    			<td>${pizza.name}</td>
    			<td>${pizza.flavor}</td>
    			<td>
    				<c:forEach var="item" items="${pizza.toppings}">
    					<c:out value="${item}"/>&nbsp; 
    				</c:forEach>
    			</td>
    		</tr>
    	</table>
    </body>
    </html>
    
    下一步是配置 ContentNegotaionViewResolver 本身,
    	public ViewResolver contentNegotiatingViewResolver(ContentNegotiationManager manager) {
    		ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
    		resolver.setContentNegotiationManager(manager);
    
    		// Define all possible view resolvers
    		List<ViewResolver> resolvers = new ArrayList<ViewResolver>();
    
    		resolvers.add(jaxb2MarshallingXmlViewResolver());
    		resolvers.add(jsonViewResolver());
    		resolvers.add(jspViewResolver());
    		resolvers.add(pdfViewResolver());
    		resolvers.add(excelViewResolver());
    		
    		resolver.setViewResolvers(resolvers);
    		return resolver;
    	}
    
    我们需要设置 ContentNegotiationManager由Spring 注入,和为每一个应用程序可能会产生输出格式设置不同的解析器,。
    最后,我们已经创建了不同的视图解析器以对 XML,JSON,PDF,XLS 和 HTML 输出,我们将在节中讨论。
    第4步:创建不同的视图解析器
    现在,让我们创建塔实际视图解析器

    XML View Resolver:

    这个视图解析器依赖于JAXB2编组/解组产生XML输出。domain类需要和JAXB2注释进行注释。

    com.yiibai.springmvc.viewresolver.Jaxb2MarshallingXmlViewResolver

    package com.yiiibai.springmvc.viewresolver;
    
    import java.util.Locale;
    
    import org.springframework.oxm.Marshaller;
    import org.springframework.web.servlet.View;
    import org.springframework.web.servlet.ViewResolver;
    import org.springframework.web.servlet.view.xml.MarshallingView;
    
    public class Jaxb2MarshallingXmlViewResolver implements ViewResolver {
    
    	private Marshaller marshaller;
    
        
        public Jaxb2MarshallingXmlViewResolver(Marshaller marshaller) {
            this.marshaller = marshaller;
        }
        
        
        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            MarshallingView view = new MarshallingView();
            view.setMarshaller(marshaller);
            return view;
        }
    
    }
    
    下面是域对象(标注了标准的XML注释)在我们的例子:

    com.yiibai.springmvc.model.Pizza

    package com.yiibai.springmvc.model;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlRootElement;
    
    @XmlRootElement(name = "pizza")
    public class Pizza {
    	
    	private String name;
    	
    	private String flavor;
    	
    	private List<String> toppings = new ArrayList<String>();
    	
    	public Pizza(){
    		
    	}
    	
    	public Pizza(String name){
    		this.name = name;
    		this.flavor = "spicy";
    		this.toppings.add("Cheese");
    		this.toppings.add("bakon");
    	}
    	
    	@XmlElement
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	@XmlElement
    	public void setFlavor(String flavor) {
    		this.flavor = flavor;
    	}
    
    	public String getFlavor() {
    		return flavor;
    	}
    
    	public List<String> getToppings() {
    		return toppings;
    	}
    	
    	@XmlElement
    	public void setToppings(List<String> toppings) {
    		this.toppings = toppings;
    	}
    	
    }
    

    JSON View Resolver:

    这个视图解析器是使用 Spring MappingJackson2JsonView 为了将 POJO 对象转换成 JSON 视图。

    com.yiibai.springmvc.viewresolver.JsonViewResolver

    package com.yiibai.springmvc.viewresolver;
    
    import java.util.Locale;
    
    import org.springframework.web.servlet.View;
    import org.springframework.web.servlet.ViewResolver;
    import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
    
    public class JsonViewResolver implements ViewResolver{
    
    	@Override
    	public View resolveViewName(String viewName, Locale locale) throws Exception {
    		MappingJackson2JsonView view = new MappingJackson2JsonView();
            view.setPrettyPrint(true);       
            return view;
          }
    
    }
    

    PDF View Resolver:

    这个视图解析器使用lowagie iText库实际生成PDF输出。还要注意的是实际的视图,从Spring AbstractPdfView 内部使用 lowagie iText 库扩展。

    com.yiibai.springmvc.viewresolver.PdfView

    package com.yiibai.springmvc.viewresolver;
    
    import java.awt.Color;
    import java.util.Map;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.web.servlet.view.document.AbstractPdfView;
    
    import com.lowagie.text.Document;
    import com.lowagie.text.Element;
    import com.lowagie.text.pdf.PdfPTable;
    import com.lowagie.text.pdf.PdfWriter;
    import com.yiibai.springmvc.model.Pizza;
    
    public class PdfView extends AbstractPdfView {
    
    	@Override
    	protected void buildPdfDocument(Map<String, Object> model,
    			Document document, PdfWriter writer, HttpServletRequest request,
    			HttpServletResponse response) throws Exception {
    
    		Pizza pizza = (Pizza) model.get("pizza");
    
    		PdfPTable table = new PdfPTable(3);
    		table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_CENTER);
    		table.getDefaultCell().setVerticalAlignment(Element.ALIGN_MIDDLE);
    		table.getDefaultCell().setBackgroundColor(Color.lightGray);
    
    		table.addCell("Name");
    		table.addCell("Flavor");
    		table.addCell("Toppings");
    
    		table.addCell(pizza.getName());
    		table.addCell(pizza.getFlavor());
    
    		StringBuffer toppings = new StringBuffer("");
    		for (String topping : pizza.getToppings()) {
    			toppings.append(topping);
    			toppings.append(" ");
    		}
    		table.addCell(toppings.toString());
    		document.add(table);
    
    	}
    
    }
    

    com.yiibai.springmvc.viewresolver.PdfViewResolver类代码:

    package com.yiibai.springmvc.viewresolver;
    
    import java.util.Locale;
    
    import org.springframework.web.servlet.View;
    import org.springframework.web.servlet.ViewResolver;
    
    public class PdfViewResolver implements ViewResolver{
    
    	@Override
    	public View resolveViewName(String viewName, Locale locale) throws Exception {
    		PdfView view = new PdfView();
    		return view;
          }
    	
    }
    

    XLS View Resolver:

    这个视图解析器是使用Apache POI库实际生成 Microsoft XLS输出。还要注意的是实际的视图,从Spring AbstractExcelView 内部使用 Apache POI库扩展。

    com.yiibai.springmvc.viewresolver.ExcelView

    package com.yiibai.springmvc.viewresolver;
    
    import java.util.Map;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.poi.hssf.usermodel.HSSFWorkbook;
    import org.apache.poi.ss.usermodel.Cell;
    import org.apache.poi.ss.usermodel.CellStyle;
    import org.apache.poi.ss.usermodel.IndexedColors;
    import org.apache.poi.ss.usermodel.Row;
    import org.apache.poi.ss.usermodel.Sheet;
    import org.springframework.web.servlet.view.document.AbstractExcelView;
    
    import com.yiibai.springmvc.model.Pizza;
    
    public class ExcelView extends AbstractExcelView {
    
    	@Override
    	protected void buildExcelDocument(Map<String, Object> model,
    			HSSFWorkbook workbook, HttpServletRequest request,
    			HttpServletResponse response) throws Exception {
    
    		Pizza pizza = (Pizza) model.get("pizza");
    
    		Sheet sheet = workbook.createSheet("sheet 1");
    		CellStyle style = workbook.createCellStyle();
    		style.setFillForegroundColor(IndexedColors.GREY_40_PERCENT.index);
    		style.setFillPattern(CellStyle.SOLID_FOREGROUND);
    		style.setAlignment(CellStyle.ALIGN_CENTER);
    		Row row = null;
    		Cell cell = null;
    		int rowCount = 0;
    		int colCount = 0;
    
    		// Create header cells
    		row = sheet.createRow(rowCount++);
    
    		cell = row.createCell(colCount++);
    		cell.setCellStyle(style);
    		cell.setCellValue("Name");
    
    		cell = row.createCell(colCount++);
    		cell.setCellStyle(style);
    		cell.setCellValue("Flavor");
    
    		cell = row.createCell(colCount++);
    		cell.setCellStyle(style);
    		cell.setCellValue("Toppings");
    
    		// Create data cells
    		row = sheet.createRow(rowCount++);
    		colCount = 0;
    		row.createCell(colCount++).setCellValue(pizza.getName());
    		row.createCell(colCount++).setCellValue(pizza.getFlavor());
    
    		StringBuffer toppings = new StringBuffer("");
    		for (String topping : pizza.getToppings()) {
    			toppings.append(topping);
    			toppings.append(" ");
    		}
    		row.createCell(colCount++).setCellValue(toppings.toString());
    
    		for (int i = 0; i < 3; i++)
    			sheet.autoSizeColumn(i, true);
    
    	}
    
    }
    

    com.yiibai.springmvc.viewresolver.ExcelViewResolver

    package com.yiibai.springmvc.viewresolver;
    
    import java.util.Locale;
    
    import org.springframework.web.servlet.View;
    import org.springframework.web.servlet.ViewResolver;
    
    public class ExcelViewResolver implements ViewResolver{
    
    	@Override
    	public View resolveViewName(String viewName, Locale locale) throws Exception {
    		ExcelView view = new ExcelView();
    		return view;
          }
    	
    }
    
    这是所有 ContentNegotaingViewResolver 需要的配置。
    完成这个例子并让它可以运行,让我们添加缺少 Spring MVC 配置。
    第5步:创建控制器类
    下面是一个简单的基于REST的控制器作为我们的示例。

    com.yiibai.springmvc.controller.AppController

    package com.yiibai.springmvc.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.ModelMap;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    
    import com.yiibai.springmvc.model.Pizza;
    
    @Controller
    public class AppController {
    
    	@RequestMapping(value="/pizzavalley/{pizzaName}", method = RequestMethod.GET)
    	public String getPizza(@PathVariable String pizzaName, ModelMap model) {
     
    		Pizza pizza = new Pizza(pizzaName);
    		model.addAttribute("pizza", pizza);
     
    		return "pizza";
     
    	}
    	
    }
    
    第6步:创建初始化类

    添加一个初始化类实现WebApplicationInitializer如下图所示(在这种情况下,作为替代在 web.xml 中定义的任何 Spring 配置)。在Servlet 3.0的容器启动时,这个类会被加载并实例及其 onStartup方法将通过servlet容器调用。

    com.yiibai.springmvc.configuration.AppInitializer

    package com.yiibai.springmvc.configuration;
    
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRegistration;
    
    import org.springframework.web.WebApplicationInitializer;
    import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
    import org.springframework.web.servlet.DispatcherServlet;
    
    public class AppInitializer implements WebApplicationInitializer {
    
    	public void onStartup(ServletContext container) throws ServletException {
    
    		AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
    		ctx.register(AppConfig.class);
    		ctx.setServletContext(container);
    
    		ServletRegistration.Dynamic servlet = container.addServlet(
    				"dispatcher", new DispatcherServlet(ctx));
    
    		servlet.setLoadOnStartup(1);
    		servlet.addMapping("/");
    	}
    
    }
     

    更新:请注意,上面的类可以写成更加简洁[和它的首选方式],通过扩展 AbstractAnnotationConfigDispatcherServletInitializer 基类,如下所示:

    package com.yiibai.springmvc.configuration;
    
    import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
    
    public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    	@Override
    	protected Class<?>[] getRootConfigClasses() {
    		return new Class[] { AppConfig.class };
    	}
     
    	@Override
    	protected Class<?>[] getServletConfigClasses() {
    		return null;
    	}
     
    	@Override
    	protected String[] getServletMappings() {
    		return new String[] { "/" };
    	}
    
    }
    
    第7步:构建和部署应用程序

    现在构建 war(通过Eclipse或Maven [ mvn clean install])。部署 war 到Servlet3.0容器。

    运行。以下是运行示例触发不同模式输出的快照(注意URL扩展),访问如下URL:http://localhost:8080/ContentNegotiatingViewResolver/pizzavalley/margherita.xml





    到这里,所有教程讲解完毕!

  • 相关阅读:
    JavaScript&DOM
    avalon.js的循环操作在表格中的应用
    merge()
    建立表空间以及用户
    SSI框架下,用jxl实现导出功能
    SQL递归查询实现组织机构树
    vue+webpack实践
    使用canvas绘制一片星空
    在canvas中使用html元素
    CSS3-transform 转换/变换
  • 原文地址:https://www.cnblogs.com/borter/p/9519775.html
Copyright © 2020-2023  润新知