热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

WebUploader+SpringMVC实现文件上传功能

WebUploader是由Baidu团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件。这篇文章主要介绍了WebUploader+SpringMVC实现文件上传功能,需要的朋友可以参考下

WebUploader是由Baidu团队开发的一个简单的以HTML5为主,FLASH为辅的现代文件上传组件。在现代的浏览器里面能充分发挥html5的优势,同时又不摒弃主流IE浏览器,沿用原来的FLASH运行时,兼容IE6+,iOS 6+, Android 4+。两套运行时,同样的调用方式,可供用户任意选用。 采用大文件分片并发上传,极大的提高了文件上传效率。

官方文档及更多示例请参考: http://fex.baidu.com/webuploader/

不扯废话了,由于我需要的只是上传图片功能,官网上边还说“WebUploader只包含文件上传的底层实现,不包括UI部分,所以交互方面可以自由发挥。”但是我又看到官网的例子不错,就把demo的js和css扒了下来.

这里写图片描述

这里写图片描述

前台相关代码:

jsp:

<%@ page language="java" cOntentType="text/html; charset=UTF-8"
 pageEncoding="UTF-8"%>















 

Demo

您可以尝试文件拖拽,使用QQ截屏工具,然后激活窗口后粘贴,或者点击添加图片按钮,来体验此demo.

或将照片拖到这里,单次最多可选300张

0%
开始上传

uploader_demo.js

jQuery(function() {
 var $ = jQuery, // just in case. Make sure it's not an other libaray.
  $wrap = $('#uploader'),  // 图片容器
  $queue = $('
    ') .appendTo( $wrap.find('.queueList') ), // 状态栏,包括进度和控制按钮 $statusBa $wrap.find('.statusBar'), // 文件总体选择信息。 $info = $statusBar.find('.info'), // 上传按钮 $upload = $wrap.find('.uploadBtn'), // 没选择文件之前的内容。 $placeHolder = $wrap.find('.placeholder'), // 总体进度条 $progress = $statusBar.find('.progress').hide(), // 添加的文件数量 fileCount = 0, // 添加的文件总大小 fileSize = 0, // 优化retina, 在retina下这个值是2 ratio = window.devicePixelRatio || 1, // 缩略图大小 thumbnailWidth = 110 * ratio, thumbnailHeight = 110 * ratio, // 可能有pedding, ready, uploading, confirm, done. state = 'pedding', // 所有文件的进度信息,key为file id percentages = {}, supportTransition = (function(){ var s = document.createElement('p').style, r = 'transition' in s || 'WebkitTransition' in s || 'MozTransition' in s || 'msTransition' in s || 'OTransition' in s; s = null; return r; })(), // WebUploader实例 uploader; if ( !WebUploader.Uploader.support() ) { alert( 'Web Uploader 不支持您的浏览器!如果你使用的是IE浏览器,请尝试升级 flash 播放器'); throw new Error( 'WebUploader does not support the browser you are using.' ); } // 实例化 uploader = WebUploader.create({ pick: { id: '#filePicker', label: '点击选择图片' }, dnd: '#uploader .queueList', paste: document.body, accept: { title: 'Images', extensions: 'gif,jpg,jpeg,bmp,png', mimeTypes: 'image/*' }, // swf文件路径,就是控件下的swf文件地址 swf: 'js/plugins/webuploader/Uploader.swf', disableGlobalDnd: true, chunked: true, // 文件接收服务端,写你要执行的方法就行 **server: 'http://localhost:8080/test/upload.do&#63;method=upload'**, fileNumLimit: 300, fileSizeLimit: 5 * 1024 * 1024, // 200 M fileSingleSizeLimit: 1 * 1024 * 1024 // 50 M }); // 添加“添加文件”的按钮, uploader.addButton({ id: '#filePicker2', label: '继续添加' }); // 当有文件添加进来时执行,负责view的创建 function addFile( file ) { var $li = $( '
  • ' + '

    ' + file.name + '

    ' + '

    '+ '

    ' + '
  • ' ), $btns = $('
    ' + '删除' + '向右旋转' + '向左旋转
    ').appendTo( $li ), $prgress = $li.find('p.progress span'), $wrap = $li.find( 'p.imgWrap' ), $info = $('

    '), showError = function( code ) { switch( code ) { case 'exceed_size': text = '文件大小超出'; break; case 'interrupt': text = '上传暂停'; break; default: text = '上传失败,请重试'; break; } $info.text( text ).appendTo( $li ); }; if ( file.getStatus() === 'invalid' ) { showError( file.statusText ); } else { // @todo lazyload $wrap.text( '预览中' ); uploader.makeThumb( file, function( error, src ) { if ( error ) { $wrap.text( '不能预览' ); return; } var img = $(''); $wrap.empty().append( img ); }, thumbnailWidth, thumbnailHeight ); percentages[ file.id ] = [ file.size, 0 ]; file.rotation = 0; } file.on('statuschange', function( cur, prev ) { if ( prev === 'progress' ) { $prgress.hide().width(0); } else if ( prev === 'queued' ) { $li.off( 'mouseenter mouseleave' ); $btns.remove(); } // 成功 if ( cur === 'error' || cur === 'invalid' ) { console.log( file.statusText ); showError( file.statusText ); percentages[ file.id ][ 1 ] = 1; } else if ( cur === 'interrupt' ) { showError( 'interrupt' ); } else if ( cur === 'queued' ) { percentages[ file.id ][ 1 ] = 0; } else if ( cur === 'progress' ) { $info.remove(); $prgress.css('display', 'block'); } else if ( cur === 'complete' ) { $li.append( '' ); } $li.removeClass( 'state-' + prev ).addClass( 'state-' + cur ); }); $li.on( 'mouseenter', function() { $btns.stop().animate({height: 30}); }); $li.on( 'mouseleave', function() { $btns.stop().animate({height: 0}); }); $btns.on( 'click', 'span', function() { var index = $(this).index(), deg; switch ( index ) { case 0: uploader.removeFile( file ); return; case 1: file.rotation += 90; break; case 2: file.rotation -= 90; break; } if ( supportTransition ) { deg = 'rotate(' + file.rotation + 'deg)'; $wrap.css({ '-webkit-transform': deg, '-mos-transform': deg, '-o-transform': deg, 'transform': deg }); } else { $wrap.css( 'filter', 'progid:DXImageTransform.Microsoft.BasicImage(rotation='+ (~~((file.rotation/90)%4 + 4)%4) +')'); // use jquery animate to rotation // $({ // rotation: rotation // }).animate({ // rotation: file.rotation // }, { // easing: 'linear', // step: function( now ) { // now = now * Math.PI / 180; // var cos = Math.cos( now ), // sin = Math.sin( now ); // $wrap.css( 'filter', "progid:DXImageTransform.Microsoft.Matrix(M11=" + cos + ",M12=" + (-sin) + ",M21=" + sin + ",M22=" + cos + ",SizingMethod='auto expand')"); // } // }); } }); $li.appendTo( $queue ); } // 负责view的销毁 function removeFile( file ) { var $li = $('#'+file.id); delete percentages[ file.id ]; updateTotalProgress(); $li.off().find('.file-panel').off().end().remove(); } function updateTotalProgress() { var loaded = 0, total = 0, spans = $progress.children(), percent; $.each( percentages, function( k, v ) { total += v[ 0 ]; loaded += v[ 0 ] * v[ 1 ]; } ); percent = total &#63; loaded / total : 0; spans.eq( 0 ).text( Math.round( percent * 100 ) + '%' ); spans.eq( 1 ).css( 'width', Math.round( percent * 100 ) + '%' ); updateStatus(); } function updateStatus() { var text = '', stats; if ( state === 'ready' ) { text = '选中' + fileCount + '张图片,共' + WebUploader.formatSize( fileSize ) + '。'; } else if ( state === 'confirm' ) { stats = uploader.getStats(); if ( stats.uploadFailNum ) { text = '已成功上传' + stats.successNum+ '张照片至XX相册,'+ stats.uploadFailNum + '张照片上传失败,重新上传失败图片或忽略' } } else { stats = uploader.getStats(); text = '共' + fileCount + '张(' + WebUploader.formatSize( fileSize ) + '),已上传' + stats.successNum + '张'; if ( stats.uploadFailNum ) { text += ',失败' + stats.uploadFailNum + '张'; } } $info.html( text ); } function setState( val ) { var file, stats; if ( val === state ) { return; } $upload.removeClass( 'state-' + state ); $upload.addClass( 'state-' + val ); state = val; switch ( state ) { case 'pedding': $placeHolder.removeClass( 'element-invisible' ); $queue.parent().removeClass('filled'); $queue.hide(); $statusBar.addClass( 'element-invisible' ); uploader.refresh(); break; case 'ready': $placeHolder.addClass( 'element-invisible' ); $( '#filePicker2' ).removeClass( 'element-invisible'); $queue.parent().addClass('filled'); $queue.show(); $statusBar.removeClass('element-invisible'); uploader.refresh(); break; case 'uploading': $( '#filePicker2' ).addClass( 'element-invisible' ); $progress.show(); $upload.text( '暂停上传' ); break; case 'paused': $progress.show(); $upload.text( '继续上传' ); break; case 'confirm': $progress.hide(); $upload.text( '开始上传' ).addClass( 'disabled' ); stats = uploader.getStats(); if ( stats.successNum && !stats.uploadFailNum ) { setState( 'finish' ); return; } break; case 'finish': stats = uploader.getStats(); if ( stats.successNum ) { alert( '上传成功' ); } else { // 没有成功的图片,重设 state = 'done'; location.reload(); } break; } updateStatus(); } uploader.OnUploadProgress= function( file, percentage ) { var $li = $('#'+file.id), $percent = $li.find('.progress span'); $percent.css( 'width', percentage * 100 + '%' ); percentages[ file.id ][ 1 ] = percentage; updateTotalProgress(); }; uploader.OnFileQueued= function( file ) { fileCount++; fileSize += file.size; if ( fileCount === 1 ) { $placeHolder.addClass( 'element-invisible' ); $statusBar.show(); } addFile( file ); setState( 'ready' ); updateTotalProgress(); }; uploader.OnFileDequeued= function( file ) { fileCount--; fileSize -= file.size; if ( !fileCount ) { setState( 'pedding' ); } removeFile( file ); updateTotalProgress(); }; uploader.on( 'all', function( type ) { var stats; switch( type ) { case 'uploadFinished': setState( 'confirm' ); break; case 'startUpload': setState( 'uploading' ); break; case 'stopUpload': setState( 'paused' ); break; } }); uploader.OnError= function( code ) { alert( 'Eroor: ' + code ); }; $upload.on('click', function() { if ( $(this).hasClass( 'disabled' ) ) { return false; } if ( state === 'ready' ) { uploader.upload(); } else if ( state === 'paused' ) { uploader.upload(); } else if ( state === 'uploading' ) { uploader.stop(); } }); $info.on( 'click', '.retry', function() { uploader.retry(); } ); $info.on( 'click', '.ignore', function() { alert( 'todo' ); } ); $upload.addClass( 'state-' + state ); updateTotalProgres});

    uploader_demo.css

    #container {
     color: #838383;
     font-size: 12px;
    }
    #uploader .queueList {
     margin: 20px;
     border: 3px dashed #e6e6e6;
    }
    #uploader .queueList.filled {
     padding: 17px;
     margin: 0;
     border: 3px dashed transparent;
    }
    #uploader .queueList.webuploader-dnd-over {
     border: 3px dashed #999999;
    }
    #uploader p {margin: 0;}
    .element-invisible {
     position: absolute !important;
     clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
     clip: rect(1px,1px,1px,1px);
    }
    #uploader .placeholder {
     min-height: 350px;
     padding-top: 178px;
     text-align: center;
     background: url(../images/image.png) center 93px no-repeat;
     color: #cccccc;
     font-size: 18px;
     position: relative;
    }
    #uploader .placeholder .webuploader-pick {
     font-size: 18px;
     background: #00b7ee;
     border-radius: 3px;
     line-height: 44px;
     padding: 0 30px;
     *width: 120px;
     color: #fff;
     display: inline-block;
     margin: 0 auto 20px auto;
     cursor: pointer;
     box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
    }
    #uploader .placeholder .webuploader-pick-hover {
     background: #00a2d4;
    }
    #uploader .placeholder .flashTip {
     color: #666666;
     font-size: 12px;
     position: absolute;
     width: 100%;
     text-align: center;
     bottom: 20px;
    }
    #uploader .placeholder .flashTip a {
     color: #0785d1;
     text-decoration: none;
    }
    #uploader .placeholder .flashTip a:hover {
     text-decoration: underline;
    }
    #uploader .filelist {
     list-style: none;
     margin: 0;
     padding: 0;
    }
    #uploader .filelist:after {
     content: '';
     display: block;
     width: 0;
     height: 0;
     overflow: hidden;
     clear: both;
    }
    #uploader .filelist li {
     width: 110px;
     height: 110px;
     background: url(../images/bg.png) no-repeat;
     text-align: center;
     margin: 0 8px 20px 0;
     position: relative;
     display: inline;
     float: left;
     overflow: hidden;
     font-size: 12px;
    }
    #uploader .filelist li p.log {
     position: relative;
     top: -45px;
    }
    #uploader .filelist li p.title {
     position: absolute;
     top: 0;
     left: 0;
     width: 100%;
     overflow: hidden;
     white-space: nowrap;
     text-overflow : ellipsis;
     top: 5px;
     text-indent: 5px;
     text-align: left;
    }
    #uploader .filelist li p.progress {
     position: absolute;
     width: 100%;
     bottom: 0;
     left: 0;
     height: 8px;
     overflow: hidden;
     z-index: 50;
     margin: 0;
     border-radius: 0;
     background: none;
     -webkit-box-shadow: 0 0 0;
    }
    #uploader .filelist li p.progress span {
     display: none;
     overflow: hidden;
     width: 0;
     height: 100%;
     background: #1483d8 url(../images/progress.png) repeat-x;
     -webit-transition: width 200ms linear;
     -moz-transition: width 200ms linear;
     -o-transition: width 200ms linear;
     -ms-transition: width 200ms linear;
     transition: width 200ms linear;
     -webkit-animation: progressmove 2s linear infinite;
     -moz-animation: progressmove 2s linear infinite;
     -o-animation: progressmove 2s linear infinite;
     -ms-animation: progressmove 2s linear infinite;
     animation: progressmove 2s linear infinite;
     -webkit-transform: translateZ(0);
    }
    @-webkit-keyframes progressmove {
     0% {
      background-position: 0 0;
     }
     100% {
      background-position: 17px 0;
     }
    }
    @-moz-keyframes progressmove {
     0% {
      background-position: 0 0;
     }
     100% {
      background-position: 17px 0;
     }
    }
    @keyframes progressmove {
     0% {
      background-position: 0 0;
     }
     100% {
      background-position: 17px 0;
     }
    }
    #uploader .filelist li p.imgWrap {
     position: relative;
     z-index: 2;
     line-height: 110px;
     vertical-align: middle;
     overflow: hidden;
     width: 110px;
     height: 110px;
     -webkit-transform-origin: 50% 50%;
     -moz-transform-origin: 50% 50%;
     -o-transform-origin: 50% 50%;
     -ms-transform-origin: 50% 50%;
     transform-origin: 50% 50%;
     -webit-transition: 200ms ease-out;
     -moz-transition: 200ms ease-out;
     -o-transition: 200ms ease-out;
     -ms-transition: 200ms ease-out;
     transition: 200ms ease-out;
    }
    #uploader .filelist li img {
     width: 100%;
    }
    #uploader .filelist li p.error {
     background: #f43838;
     color: #fff;
     position: absolute;
     bottom: 0;
     left: 0;
     height: 28px;
     line-height: 28px;
     width: 100%;
     z-index: 100;
    }
    #uploader .filelist li .success {
     display: block;
     position: absolute;
     left: 0;
     bottom: 0;
     height: 40px;
     width: 100%;
     z-index: 200;
     background: url(../images/success.png) no-repeat right bottom;
    }
    #uploader .filelist div.file-panel {
     position: absolute;
     height: 0;
     filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#80000000', endColorstr='#80000000')\0;
     background: rgba( 0, 0, 0, 0.5 );
     width: 100%;
     top: 0;
     left: 0;
     overflow: hidden;
     z-index: 300;
    }
    #uploader .filelist div.file-panel span {
     width: 24px;
     height: 24px;
     display: inline;
     float: right;
     text-indent: -9999px;
     overflow: hidden;
     background: url(../../img/uploader_icons.png) no-repeat;
     margin: 5px 1px 1px;
     cursor: pointer; 
    }
    #uploader .filelist div.file-panel span.rotateLeft {
     background-position: 0 -24px;
    }
    #uploader .filelist div.file-panel span.rotateLeft:hover {
     background-position: 0 0;
    }
    #uploader .filelist div.file-panel span.rotateRight {
     background-position: -24px -24px;
    }
    #uploader .filelist div.file-panel span.rotateRight:hover {
     background-position: -24px 0;
    }
    #uploader .filelist div.file-panel span.cancel {
     background-position: -48px -24px;
    }
    #uploader .filelist div.file-panel span.cancel:hover {
     background-position: -48px 0;
    }
    #uploader .statusBar {
     height: 63px;
     border-top: 1px solid #dadada;
     padding: 0 20px;
     line-height: 63px;
     vertical-align: middle;
     position: relative;
    }
    #uploader .statusBar .progress {
     border: 1px solid #1483d8;
     width: 198px;
     background: #fff;
     height: 18px;
     position: relative;
     display: inline-block;
     text-align: center;
     line-height: 20px;
     color: #6dbfff;
     position: relative;
     margin: 0 10px 0 0;
    }
    #uploader .statusBar .progress span.percentage {
     width: 0;
     height: 100%;
     left: 0;
     top: 0;
     background: #1483d8;
     position: absolute;
    }
    #uploader .statusBar .progress span.text {
     position: relative;
     z-index: 10;
    }
    #uploader .statusBar .info {
     display: inline-block;
     font-size: 14px;
     color: #666666;
    }
    #uploader .statusBar .btns {
     position: absolute;
     top: 10px;
     right: 20px;
     line-height: 40px;
    }
    #filePicker2 {
     display: inline-block;
     float: left;
    }
    #uploader .statusBar .btns .webuploader-pick,
    #uploader .statusBar .btns .uploadBtn,
    #uploader .statusBar .btns .uploadBtn.state-uploading,
    #uploader .statusBar .btns .uploadBtn.state-paused {
     background: #ffffff;
     border: 1px solid #cfcfcf;
     color: #565656;
     padding: 0 18px;
     display: inline-block;
     border-radius: 3px;
     margin-left: 10px;
     cursor: pointer;
     font-size: 14px;
     float: left;
    }
    #uploader .statusBar .btns .webuploader-pick-hover,
    #uploader .statusBar .btns .uploadBtn:hover,
    #uploader .statusBar .btns .uploadBtn.state-uploading:hover,
    #uploader .statusBar .btns .uploadBtn.state-paused:hover {
     background: #f0f0f0;
    }
    #uploader .statusBar .btns .uploadBtn {
     background: #00b7ee;
     color: #fff;
     border-color: transparent;
    }
    #uploader .statusBar .btns .uploadBtn:hover {
     background: #00a2d4;
    }
    #uploader .statusBar .btns .uploadBtn.disabled {
     pointer-events: none;
     opacity: 0.6;
    }

    后台代码:

    @Controller
    @RequestMapping("/upload")
    public class UpLoaderTestController {
     @RequestMapping(params = "method=uploadPic")
     public String uploadPic(HttpServletRequest request){
      return "uploaderDemo/uploaderTest";
     }
     @RequestMapping(params="method=upload")
     @ResponseBody
     public String uploads(@RequestParam("file")MultipartFile sortPicImg,HttpServletRequest request,HttpServletResponse response) {
      String path = SysConstants.PIC_SERVER_FILE_ROOT_DIR +SysConstants.PIC_PTYPE_DIR ;
      String fileName = System.currentTimeMillis()+"_"+sortPicImg.getOriginalFilename();
      File targetFile = new File(path, fileName);
      if (!targetFile.exists()) {
       targetFile.mkdirs();
      }
      JSONObject json = new JSONObject();
      //保存
      try {
       sortPicImg.transferTo(targetFile);
      } catch (Exception e) {
       e.printStackTrace();
       json.put("msg","error");
       return json.toJSONString();
      }
      json.put("msg","success");
      //json.put("filePath",request.getContextPath() + "/upload/" + fileName);
      File retfile = new File(SysConstants.PIC_SERVER_DNS +SysConstants.PIC_PTYPE_DIR, fileName);
      json.put("filePath",retfile.getPath());
      System.out.println("json="+json.toJSONString());
      return json.toJSONString();
     }
     }

    前台传到后台的图片是一张一张传的,每传一张图片调用一次该server方法,直到传完为止。

    将图片文件上传到图片服务器后,再将该图片地址存储在数据库中。


    推荐阅读
    • JavaScript 实现图片文件转Base64编码的方法
      本文详细介绍了如何使用JavaScript将用户通过文件输入控件选择的图片文件转换为Base64编码字符串,适用于Web前端开发中图片上传前的预处理。 ... [详细]
    • 使用Echarts for Weixin 小程序实现中国地图及区域点击事件
      本文介绍了如何使用Echarts for Weixin在微信小程序中构建中国地图,并实现区域点击事件。包括效果展示、条件准备和逻辑实现的具体步骤。 ... [详细]
    • 树莓派4B:安装基础操作系统指南
      本文将详细介绍如何为树莓派4B安装基础操作系统,包括所需材料、镜像下载、镜像烧录以及更换国内源等步骤。 ... [详细]
    • 本文介绍了编程语言的基本分类,包括机器语言、汇编语言和高级语言的特点及其优缺点。随后详细讲解了Python解释器的安装与配置方法,并探讨了Python变量的定义、使用及内存管理机制。 ... [详细]
    • javascript分页类支持页码格式
      前端时间因为项目需要,要对一个产品下所有的附属图片进行分页显示,没考虑ajax一张张请求,所以干脆一次性全部把图片out,然 ... [详细]
    • Framework7:构建跨平台移动应用的高效框架
      Framework7 是一个开源免费的框架,适用于开发混合移动应用(原生与HTML混合)或iOS&Android风格的Web应用。此外,它还可以作为原型开发工具,帮助开发者快速创建应用原型。 ... [详细]
    • 解决Bootstrap DataTable Ajax请求重复问题
      在最近的一个项目中,我们使用了JQuery DataTable进行数据展示,虽然使用起来非常方便,但在测试过程中发现了一个问题:当查询条件改变时,有时查询结果的数据不正确。通过FireBug调试发现,点击搜索按钮时,会发送两次Ajax请求,一次是原条件的请求,一次是新条件的请求。 ... [详细]
    • POJ2263是一个经典的图论问题,涉及寻找从起点到终点的最大载重路径。本文将详细介绍该问题的背景、解题思路及代码实现。 ... [详细]
    • RTThread线程间通信
      线程中通信在裸机编程中,经常会使用全局变量进行功能间的通信,如某些功能可能由于一些操作而改变全局变量的值,另一个功能对此全局变量进行读取& ... [详细]
    • SDWebImage第三方库学习
      1、基本使用方法异步下载并缓存-(void)sd_setImageWithURL:(nullableNSURL*)urlNS_REFINED_FOR_SWIFT;使用占位图片& ... [详细]
    • Android 属性 allowBackup 的安全风险分析
      在 Android API Level 8 及以上版本中,系统提供了一种机制来备份和恢复应用程序数据。通过设置 allowBackup 属性,开发者可以控制是否允许这种备份和恢复功能。然而,这一功能也带来了潜在的安全风险。 ... [详细]
    • Spring 中策略模式的应用:Resource 接口详解
      本文探讨了在 Spring 框架中如何利用 Resource 接口实现资源访问策略。Resource 接口作为资源访问策略的抽象,通过多种实现类支持不同类型的资源访问。 ... [详细]
    • Java EE 平台集成了多种服务、API 和协议,旨在支持基于 Web 的多层应用程序开发。本文将详细介绍 Java EE 中的 13 种关键技术规范,帮助开发者更好地理解和应用这些技术。 ... [详细]
    • 使用Tkinter构建51Ape无损音乐爬虫UI
      本文介绍了如何使用Python的内置模块Tkinter来构建一个简单的用户界面,用于爬取51Ape网站上的无损音乐百度云链接。虽然Tkinter入门相对简单,但在实际开发过程中由于文档不足可能会带来一些不便。 ... [详细]
    • 本文详细介绍了 Java 网站开发的相关资源和步骤,包括常用网站、开发环境和框架选择。 ... [详细]
    author-avatar
    妩媚的麻醉效应
    这个家伙很懒,什么也没留下!
    PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
    Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有