#系统目录结构
com.huatech |--common 通用工具 | |--annotation 自定义注解 | |--constant 常量类 | |--export 导出工具类 | |--filter 过滤器 | |--freemarker.tag 自定义freemarker标签 | |--listener 监听器 | |--util 工具类 | |--controller |--dao |--domain |--model |--service
#导出相关类
com.huatech.common.annotation.EnumField 枚举属性注解,用于页面显示、导出等 com.huatech.common.export.ExportUtil excel 导出工具类 com.huatech.common.export.ExportModel Excel导出需要信息抽象接口 com.huatech.common.freemarker.tag.EnumFieldFormatterDirective 枚举属性freemarker标签 com.huatech.common.util.EnumFieldUtils 枚举属性JSON转换工具类
#实现步骤
1、添加导出相关类;
package com.huatech.common.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 枚举属性注解,用于页面显示、导出等 * 举例: public class SystemConfig { @EnumField(json="{ 1:启用, 0:未启用 }") private int status; ... } * */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface EnumField { String json(); }
package com.huatech.common.export; import java.io.IOException; import java.math.BigDecimal; import java.net.URLEncoder; import java.sql.Timestamp; import java.util.Date; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.math.NumberUtils; import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.poi.ss.usermodel.BorderStyle; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import com.github.pagehelper.PageHelper; import com.huatech.common.constant.ExportConstant; import com.huatech.common.util.BeanMapUtils; import com.huatech.common.util.EnumFieldUtils; /** * excel 导出工具类 * @author lh * @version 3.0 * @since 2017-5-22 * */ public class ExportUtil { private ExportUtil() { } public static void exportExcel(ExportModel exportModel, final HttpServletRequest request, final HttpServletResponse response) throws IOException { if(exportModel == null ){ throw new IllegalArgumentException("export model can not be null!"); } String exportTitle = request.getParameter(ExportConstant.PARAM_EXPORT_TITLE); String[] headers = request.getParameter(ExportConstant.PARAM_HEARDERS).split(","); String[] fields = request.getParameter(ExportConstant.PARAM_FIELDS).split(","); //create workbook Workbook wb = ExportUtil.createWorkbook(); //总记录数 int count = exportModel.getCount(); if(count == 0){//查询结果为空 Sheet sheet = wb.createSheet(exportTitle + "_1"); sheet = wb.getSheetAt(0); sheet.setDisplayGridlines(false);// 设置表标题是否有表格边框 createHeader(sheet, exportTitle, headers); }else{ for (int i = 0, j = getTotalPage(count); i < j; i++) { PageHelper.startPage(i+1, ExportConstant.DEFAULT_EXPORT_PAGE_SIZE, false); ExportUtil.exportExcel(exportTitle, headers, fields, i * ExportConstant.DEFAULT_EXPORT_PAGE_SIZE, wb, exportModel.getPageRecords()); } } ExportUtil.responseWorkbook(exportTitle, wb, request, response); } private static void exportExcel(String title, String[] headers, String[] fields, int startRow, Workbook wb, List<?> data) throws IOException { if(headers == null || headers.length == 0){ throw new RuntimeException("export headers must not be null"); } Sheet sheet = null; if(CollectionUtils.isEmpty(data)){ if(startRow == 0){ sheet = wb.createSheet(title + "_1"); sheet = wb.getSheetAt(0); sheet.setDisplayGridlines(false);// 设置表标题是否有表格边框 createHeader(sheet, title, headers); } return ; } startRow = startRow>0?startRow+2:startRow; int index = startRow, pageRowNo = startRow, columnCount = headers.length; // 行号、页码、列数 //枚举类型 Map<String, String> jsonMap = EnumFieldUtils.json2Map(data.get(0)); for (Object obj : data) { int sheetIndex = index/ExportConstant.SHEET_MAX_COUNT; if (index % ExportConstant.SHEET_MAX_COUNT == 0) { sheet = wb.createSheet(title + "_" + (sheetIndex + 1)); sheet = wb.getSheetAt(sheetIndex); sheet.setDisplayGridlines(false);// 设置表标题是否有表格边框 pageRowNo = 2; createHeader(sheet, title, headers); }else{ sheet = wb.getSheetAt(sheetIndex); } index++; @SuppressWarnings("unchecked") Map<String, Object> map = obj instanceof Map ? (Map<String, Object>) obj : BeanMapUtils.bean2Map(obj); Row nRow = sheet.createRow(pageRowNo++); // 新建行对象 for (int j = 0; j < columnCount; j++) { Cell cell = nRow.createCell(j); String field = fields[j]; Object val = map.get(field); if(jsonMap != null && jsonMap.containsKey(field)){ Object valObj = map.get(field); val = jsonMap.get(String.format(EnumFieldUtils.FIELD_MAP_KEY, field, isNull(valObj))); if (val == null) { val = isNull(valObj); } } setCellValue(sheet, cell, val); } } } /** * responseWorkbook * @param title * @param wb * @param request * @param response * @throws IOException */ private static void responseWorkbook(String title, Workbook wb,HttpServletRequest request, HttpServletResponse response)throws IOException{ String sFileName = title + ".xlsx"; // 火狐浏览器导出excel乱码 String agent = request.getHeader("User-Agent"); // 判断是否火狐浏览器 boolean isFirefox = agent != null && agent.contains("Firefox"); if (isFirefox) { sFileName = new String(sFileName.getBytes("UTF-8"), "ISO-8859-1"); } else { sFileName = URLEncoder.encode(sFileName, "UTF8"); } response.setHeader("Content-Disposition", "attachment; filename=".concat(sFileName)); response.setHeader("Connection", "close"); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); //response.setHeader("Content-Type", "application/vnd.ms-excel"); wb.write(response.getOutputStream()); } /** * 设置单元格的值 * @param cell * @param cellVal */ private static void setCellValue(Sheet sheet, Cell cell, Object cellVal){ if(cellVal == null || String.class.equals(cellVal.getClass())){ cell.setCellValue(filterHref(cellVal)); }else if(Integer.class.equals(cellVal.getClass()) || int.class.equals(cellVal.getClass())){ cell.setCellValue(Integer.valueOf(cellVal.toString())); }else if(Long.class.equals(cellVal.getClass()) || long.class.equals(cellVal.getClass())){ cell.setCellValue(Long.valueOf(cellVal.toString())); }else if(Double.class.equals(cellVal.getClass()) || double.class.equals(cellVal.getClass())){ cell.setCellValue(Double.valueOf(cellVal.toString())); }else if(Float.class.equals(cellVal.getClass()) || float.class.equals(cellVal.getClass())){ cell.setCellValue(Float.valueOf(cellVal.toString())); }else if(BigDecimal.class.equals(cellVal.getClass())){ cell.setCellValue(NumberUtils.toDouble(cellVal.toString())); }else if(Date.class.equals(cellVal.getClass())){ cell.setCellValue(formatDateTime((Date)cellVal)); }else if(Timestamp.class.equals(cellVal.getClass())){ cell.setCellValue(formatDateTime((Timestamp)cellVal)); }else{ cell.setCellValue(isNull(cellVal)); } cell.setCellStyle(sheet.getWorkbook().getCellStyleAt(3)); } /** * 取得总页码数 * @param count * @return */ private static int getTotalPage(int count){ int pageSize = ExportConstant.DEFAULT_EXPORT_PAGE_SIZE; return (count % pageSize == 0) ? (count / pageSize) : (count / pageSize + 1); } /** * 将对象转为字符串 * * @param o * @return */ public static String isNull(Object o) { if (o == null) { return ""; } String str; if (o instanceof String) { str = (String) o; } else { str = o.toString(); } return str.trim(); } /** * 得到日期时间字符串,转换格式(yyyy-MM-dd HH:mm:ss) */ public static String formatDateTime(Date date) { return DateFormatUtils.format(date, "yyyy-MM-dd HH:mm:ss"); } /** * 将对象转为字符串,去除超链接 * @param o * @return */ public static String filterHref(Object o) { if (o == null) { return ""; } String str; if (o instanceof String) { str = (String) o; } else { str = o.toString(); } return str.contains("href")?filterHtml(str):str; } /** * 过滤html标签 * @param input * @return */ public static String filterHtml(final String input){ String result = input; Pattern phtml = Pattern.compile("<[^>]+>", Pattern.CASE_INSENSITIVE); Matcher mhtml = phtml.matcher(result); result = mhtml.replaceAll(""); return result; } /** * 创建表头 * @param sheet * @param headers */ private static void createHeader(Sheet sheet, String title, String[] headers){ //设置标题 Row tRow = sheet.createRow(0); Cell hc = tRow.createCell(0); hc.setCellValue(new XSSFRichTextString(title)); sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, headers.length - 1));// 合并标题行:起始行号,终止行号, 起始列号,终止列号 hc.setCellStyle(sheet.getWorkbook().getCellStyleAt(1)); //设置表头 Row nRow = sheet.createRow(1); for (int i = 0; i < headers.length; i++) { Cell cell = nRow.createCell(i); cell.setCellValue(headers[i]); cell.setCellStyle(sheet.getWorkbook().getCellStyleAt(2)); } } /** * 创建Workbook * @return */ private static Workbook createWorkbook(){ Workbook wb = new SXSSFWorkbook(100); CellStyle hcs = wb.createCellStyle(); hcs.setBorderBottom(BorderStyle.THIN); hcs.setBorderLeft(BorderStyle.THIN); hcs.setBorderRight(BorderStyle.THIN); hcs.setBorderTop(BorderStyle.THIN); hcs.setAlignment(HorizontalAlignment.CENTER); Font hfont = wb.createFont(); hfont.setFontName("宋体"); hfont.setFontHeightInPoints((short) 16);// 设置字体大小 hfont.setBold(true);// 加粗 hcs.setFont(hfont); CellStyle tcs = wb.createCellStyle(); tcs.setBorderBottom(BorderStyle.THIN); tcs.setBorderLeft(BorderStyle.THIN); tcs.setBorderRight(BorderStyle.THIN); tcs.setBorderTop(BorderStyle.THIN); Font tfont = wb.createFont(); tfont.setFontName("宋体"); tfont.setFontHeightInPoints((short) 12);// 设置字体大小 tfont.setBold(true);// 加粗 tcs.setFont(tfont); CellStyle cs = wb.createCellStyle(); cs.setBorderBottom(BorderStyle.THIN); cs.setBorderLeft(BorderStyle.THIN); cs.setBorderRight(BorderStyle.THIN); cs.setBorderTop(BorderStyle.THIN); Font font = wb.createFont(); font.setFontName("宋体"); font.setFontHeightInPoints((short) 12);// 设置字体大小 return wb; } public static void main(String[] args) { Workbook wb = ExportUtil.createWorkbook(); System.out.println(wb); } }
package com.huatech.common.export; import java.util.List; /** * Excel导出需要信息抽象接口 * * @author lh * */ public interface ExportModel { /** * 导出数据总记录数 * @return 数据总条数 */ int getCount(); /** * 查询指定页的数据 * * @return 页面数据 */ List<?> getPageRecords(); }
package com.huatech.common.freemarker.tag; import java.io.IOException; import java.io.Writer; import java.lang.reflect.Field; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.huatech.common.annotation.EnumField; import freemarker.core.Environment; import freemarker.template.TemplateDirectiveBody; import freemarker.template.TemplateDirectiveModel; import freemarker.template.TemplateException; import freemarker.template.TemplateModel; /** * 数据字典格式化自定义标签 在freemarker配置XML中定义为: * * <pre> * <property name="freemarkerVariables"> <map> <entry key="fieldFormatter" value-ref="enumFieldFormatterDirective" /> </map> </property> * </pre> * * 在页面使用例子: * * <pre> * <script type="text/javascript"> * <@fieldFormatter field="status" bean="com.huatech.domain.SystemConfig"/> * ........ * * //easyui columns formatter值为自定义标签中的fieldName+"Formatter" * {name:'status',label:'状态',sortable:false,formatter:statusFormatter} * * </script> * </pre> * * @author lh * @version 2.0 * @since 2017-05-22 * */ public class EnumFieldFormatterDirective implements TemplateDirectiveModel { private static final Logger LOGGER = LoggerFactory.getLogger(EnumFieldFormatterDirective.class); private String fieldName; @Override public void execute(Environment env, @SuppressWarnings("rawtypes") Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException { String beanName = params.get("bean").toString(); fieldName = params.get("field").toString(); if (StringUtils.isBlank(fieldName) || StringUtils.isBlank(beanName)) { return; } // 查询字段指定分类所有条目 Class<?> clazz; try { clazz = Class.forName(beanName); Field fd = clazz.getDeclaredField(fieldName); EnumField enumField = fd.getAnnotation(EnumField.class); if (enumField != null) { Writer out = env.getOut(); StringBuilder buffer = new StringBuilder(); buffer.append(getJsonData( enumField.json())); buffer.append(createFormatterFunction()); out.write(buffer.toString()); } }catch (NoSuchFieldException | SecurityException e1) { LOGGER.error(e1.getMessage(), e1); } catch (ClassNotFoundException e1) { LOGGER.error(e1.getMessage(), e1); } } private String getJsonData(String json) { StringBuilder buffer = new StringBuilder(128); String objectName = fieldName + "Data_"; buffer.append("var " + objectName + " ={ \n"); String[] array = json.substring(1, json.length() - 1).split(","); for (String string : array) { String[] kv = string.split("\\:"); buffer.append("\t\"").append(kv[0].trim()).append("\" : \"").append(kv[1].trim()).append("\",\n"); } buffer.deleteCharAt(buffer.length() - 2); buffer.append("\n } ; \n"); return buffer.toString(); } private String createFormatterFunction() { StringBuilder buffer = new StringBuilder(50); String objectName = fieldName + "Data_"; buffer.append("function ").append(fieldName).append("Formatter(cellvalue,options,rowData){\n"); // 默认值为空字符串 buffer.append("\t if(cellvalue == null){\n"); buffer.append("\t\t return \" \"\n"); buffer.append("\t }\n"); buffer.append("\t var label= ").append(objectName).append("[cellvalue];\n"); buffer.append("\t if(!label) { \n"); buffer.append("\t\t return cellvalue ; \n"); buffer.append("\t } \n"); buffer.append("\t return label;\n "); buffer.append("}\n"); return buffer.toString(); } }
package com.huatech.common.util; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.huatech.common.annotation.EnumField; /** * 枚举属性JSON转换工具类 * * @author lh * @version 2.0 * @since 2017-05-19 */ public final class EnumFieldUtils { private static ConcurrentHashMap<String/* className */, Map<String/*fieldName_fieldValue*/, String/* fieldJsonValue */>> enumJsonCache = new ConcurrentHashMap<>(); public static final String FIELD_MAP_KEY = "%s_%s"; private EnumFieldUtils() { } public static Map<String, String> json2Map(Object obj) { if (obj != null) { Class<?> clazz = obj.getClass(); //类名称作为enumJsonCache的key String className = clazz.getName(); //取缓存 if (enumJsonCache.containsKey(className)) { return enumJsonCache.get(className); } synchronized (enumJsonCache) { //再次读取缓存 if (enumJsonCache.containsKey(className)) { return enumJsonCache.get(className); } Map<String, String> jsonMap = new HashMap<>(); do { // 得到类中的所有属性集合 Field[] fs = clazz.getDeclaredFields(); for (Field field : fs) { EnumField enumField = field.getAnnotation(EnumField.class); if (enumField != null) { jsonMap.putAll(convert(field.getName(), enumField.json())); } } clazz = clazz.getSuperclass(); } while (clazz != Object.class); enumJsonCache.put(className, jsonMap); return jsonMap; } } return new HashMap<>(); } /** * json转为Map对象 * * @param json * @return */ private static Map<String, String> convert(String fieldName, String json) { Map<String, String> map = new HashMap<>(); if (json == null || "".equals(json)) { return map; } map.put(fieldName, fieldName); String[] array = json.substring(1, json.length() - 1).split(","); for (String string : array) { String[] kv = string.split("\\:"); map.put(String.format(FIELD_MAP_KEY, fieldName, trim(kv[0])), trim(kv[1])); } return map; } /** * 去除字符串前后空格 * * @param str * @return */ public static String trim(String str) { if (str == null) { return ""; } return str.trim(); } }
2、在spring配置文件中配置枚举属性freemarker标签,本示例配置文件为spring-controller.xml
<bean id="enumFieldFormatterDirective" class="com.huatech.common.freemarker.tag.EnumFieldFormatterDirective"/> <property name="freemarkerVariables"> <map> <entry key="fieldFormatter" value-ref="enumFieldFormatterDirective" /> </map> </property>
3、页面使用枚举属性标签
<script type="text/javascript"> <@fieldFormatter field="status" bean="com.huatech.domain.SystemConfig"/> ... //easyui columns formatter值为自定义标签中的fieldName+"Formatter" {name:'status',label:'状态',sortable:false,formatter:statusFormatter} </script>
4、Controller层Excel导出
/** * 系统配置Excel导出 * @param model * @param request * @param response * @throws Exception */ @RequestMapping(value = "/modules/system/config/export" , method = RequestMethod.GET) public void exportSystemConfig(SystemConfigModel model, HttpServletRequest request, HttpServletResponse response)throws Exception { ExportUtil.exportExcel(new ExportModel() { private Map<String, Object> bean2Map(){ return BeanMapUtils.bean2Map(model); } @Override public int getCount() { return systemConfigService.getCount(bean2Map()); } @Override public List<?> getPageRecords() { return systemConfigService.findList(bean2Map()); } }, request, response); }
#导出功能示例
系统配置管理页面:http://localhost:[port]/modules/system/config/manage.html
[port]改为项目部署端口
相关推荐
嵌入式八股文面试题库资料知识宝典-华为的面试试题.zip
训练导控系统设计.pdf
嵌入式八股文面试题库资料知识宝典-网络编程.zip
人脸转正GAN模型的高效压缩.pdf
少儿编程scratch项目源代码文件案例素材-几何冲刺 转瞬即逝.zip
少儿编程scratch项目源代码文件案例素材-鸡蛋.zip
嵌入式系统_USB设备枚举与HID通信_CH559单片机USB主机键盘鼠标复合设备控制_基于CH559单片机的USB主机模式设备枚举与键盘鼠标数据收发系统支持复合设备识别与HID
嵌入式八股文面试题库资料知识宝典-linux常见面试题.zip
面向智慧工地的压力机在线数据的预警应用开发.pdf
基于Unity3D的鱼类运动行为可视化研究.pdf
少儿编程scratch项目源代码文件案例素材-霍格沃茨魔法学校.zip
少儿编程scratch项目源代码文件案例素材-金币冲刺.zip
内容概要:本文深入探讨了HarmonyOS编译构建子系统的作用及其技术细节。作为鸿蒙操作系统背后的关键技术之一,编译构建子系统通过GN和Ninja工具实现了高效的源代码到机器代码的转换,确保了系统的稳定性和性能优化。该系统不仅支持多系统版本构建、芯片厂商定制,还具备强大的调试与维护能力。其高效编译速度、灵活性和可扩展性使其在华为设备和其他智能终端中发挥了重要作用。文章还比较了HarmonyOS编译构建子系统与安卓和iOS编译系统的异同,并展望了其未来的发展趋势和技术演进方向。; 适合人群:对操作系统底层技术感兴趣的开发者、工程师和技术爱好者。; 使用场景及目标:①了解HarmonyOS编译构建子系统的基本概念和工作原理;②掌握其在不同设备上的应用和优化策略;③对比HarmonyOS与安卓、iOS编译系统的差异;④探索其未来发展方向和技术演进路径。; 其他说明:本文详细介绍了HarmonyOS编译构建子系统的架构设计、核心功能和实际应用案例,强调了其在万物互联时代的重要性和潜力。阅读时建议重点关注编译构建子系统的独特优势及其对鸿蒙生态系统的深远影响。
嵌入式八股文面试题库资料知识宝典-奇虎360 2015校园招聘C++研发工程师笔试题.zip
嵌入式八股文面试题库资料知识宝典-腾讯2014校园招聘C语言笔试题(附答案).zip
双种群变异策略改进RWCE算法优化换热网络.pdf
内容概要:本文详细介绍了基于瞬时无功功率理论的三电平有源电力滤波器(APF)仿真研究。主要内容涵盖并联型APF的工作原理、三相三电平NPC结构、谐波检测方法(ipiq)、双闭环控制策略(电压外环+电流内环PI控制)以及SVPWM矢量调制技术。仿真结果显示,在APF投入前后,电网电流THD从21.9%降至3.77%,显著提高了电能质量。 适用人群:从事电力系统研究、电力电子技术开发的专业人士,尤其是对有源电力滤波器及其仿真感兴趣的工程师和技术人员。 使用场景及目标:适用于需要解决电力系统中谐波污染和无功补偿问题的研究项目。目标是通过仿真验证APF的有效性和可行性,优化电力系统的电能质量。 其他说明:文中提到的仿真模型涉及多个关键模块,如三相交流电压模块、非线性负载、信号采集模块、LC滤波器模块等,这些模块的设计和协同工作对于实现良好的谐波抑制和无功补偿至关重要。
内容概要:本文探讨了在工业自动化和物联网交汇背景下,构建OPC DA转MQTT网关软件的需求及其具体实现方法。文中详细介绍了如何利用Python编程语言及相关库(如OpenOPC用于读取OPC DA数据,paho-mqtt用于MQTT消息传递),完成从OPC DA数据解析、格式转换到最终通过MQTT协议发布数据的关键步骤。此外,还讨论了针对不良网络环境下数据传输优化措施以及后续测试验证过程。 适合人群:从事工业自动化系统集成、物联网项目开发的技术人员,特别是那些希望提升跨协议数据交换能力的专业人士。 使用场景及目标:适用于需要在不同通信协议间建立高效稳定的数据通道的应用场合,比如制造业生产线监控、远程设备管理等。主要目的是克服传统有线网络限制,实现在不稳定无线网络条件下仍能保持良好性能的数据传输。 其他说明:文中提供了具体的代码片段帮助理解整个流程,并强调了实际部署过程中可能遇到的问题及解决方案。
基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档~ 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于C#实现的检测小说章节的重复、缺失、广告等功能+源码+项目文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档
少儿编程scratch项目源代码文件案例素材-火柴人终极之战.zip