WebView加载html实现网页上传本地文件(图片,拍照,语音等)

前言:

这里有两个方案,第一个使用Andorid客户端和JavaScript互相调用方法来实现,这种方法极力不推荐,它会增加服务端和客户端的开发成本。

第二种就是继承WebViewChromeClient了,WebChromeClient是Html/Js和Android客户端进行交互的一个中间件,其将webview中js所产生的事件封装,然后传递到Android客户端。Google这样做的其中一个很重要的原因就是安全问题。

一,使用Android本地和JS方法互相调用完成文件上传与选择(会增客户端与服务端开发成本,不推荐)

这里我仅仅演示了Andorid客户端和Javascript如何互相调用

1.Html代码

<!doctype html>
<html><head><meta charset="UTF-8"><title>Untitled Document</title></head>
<script type="text/javascript">

    function javaNoParam(){
       Android.showToast();
    }

    function javaWithParam(message){
       Android.showToast(message);
    }

    function jsNoParam(){
           alert("来自Java调用,无参")
    }

    function jsWithParam(message){
        alert("来自Java调用,有参数:"+message)
    }

</script>
<body>
<p> <input type="button" name="button" id="button" value="调用Java无参函数" onClick="javaNoParam()"></p>
<p> <input type="button" name="button2" id="button2" value="调用Java有参函数" onClick="javaWithParam(‘有参数‘)"></p>
<p>&nbsp;</p>
</body>
</html>

2.启动WebView对JavaScript的支持 ,默认不支持。

WebSettings setting = webview.getSettings();setting.setJavaScriptEnable(true);

3.写一个客户端接口供JS端调用

    public class WebAppInterface {

        private Context context;

        public WebAppInterface(Context context) {
            this.context = context;
        }

        public void showToast() {
            Toast.makeText(context, "js端调用,无参数", Toast.LENGTH_SHORT).show();
        }

        public void showToast(String message) {
            Toast.makeText(context, "js端调用,有参数:" + message, Toast.LENGTH_SHORT).show();
        }

    }

4.将WebAppInterface接口设置到WebView中

webview.addJavascriptInterface(new WebAppInterface(this), "Android");

第二个参数是个代号,供JS端调用,有点像JS和客户端碰头的接头暗号:

 function javaWithParam(message){
       Android.showToast(message);   //需要在addJavascriptInterface(new WebAppInterface(this), "Android")中设定的保持一致

}

设置完以上的部分,就可到达js调用客户端代码的目的

4.Android客户端远程调用JavaScript方法

 webview.loadUrl("javascript:jsNoParam()");
 webview.loadUrl("javascript:jsWithParam(‘" + "Hello!" + "‘)");

其中jsNoParam()和jsWithParam(param)都是javacript中的方法

上面的全部步骤即可实现Andorid客户端和JavaScript的简单调,但是这样如果应用到实际开发中会增加服务端和客户端的开发成本,每个接口都需要服务端和客户端一起协商开发,这样的在开发模式中耦合性很差,有没有一种东西技能满足web端与客户端交互又能达到开发模式上解耦合?

当然是有,要不然google那帮高帅富们岂不是废了。

二,继承WebChromeCilent,重写WebChromeClient的onFileChooser方法:

1.现提供一个简单的版本,仅仅实现选择文件上传功能

1.Html

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>WebView Test</title>
</head>

<script type="text/javascript">

  function  alertSomething(){
      alert("你好")
      }

function delete_confirm() <!--调用方法-->
{
    event.returnValue = confirm("确定 or 取消");
}

</script>
<body>
<input type="button" name="button" id="button" value="js提示对话框" onClick="alertSomething()"></p>
<input type="button" name="button2" id="button2" value="js确定or取消对话框" onClick="delete_confirm()"></p>

<input type="file" value="" class=‘zj-up-btn pa‘ name="uploadfile" id="uploadfile" onchange="form.submit()" /></p>

</body>
</html>

Java:继承WebChromeClient重写onFileChooser方法

public class FileSelectionWebActivity extends FragmentActivity {

    private static final int FILE_SELECT_CODE = 0;

    private WebView webView;
    private ValueCallback<Uri> mUploadMessage;

    @Override
    protected void onCreate(Bundle arg0) {
        super.onCreate(arg0);
        setContentView(R.layout.selection_file_web_activity);
        initWebView();

    }

    @SuppressLint("SetJavaScriptEnabled")
    private void initWebView() {
        webView = (WebView) findViewById(R.id.fileSelectionWebview);
        WebSettings webSettings = webView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webSettings.setBuiltInZoomControls(true);

        webView.loadUrl("file:///android_asset/selectFileHtml/index.html");
        webView.setWebViewClient(new DuomiWebViewClient(this));
        webView.setWebChromeClient(new DuomiWebChromeClient());

    }

    private class DuomiWebChromeClient extends WebChromeClient {

        // For Android 3.0+
        public void openFileChooser(ValueCallback<Uri> uploadMsg) {

            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            startActivityForResult(Intent.createChooser(i, "File Chooser"), FILE_SELECT_CODE);

        }

        // For Android 3.0+
        public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("*/*");
            startActivityForResult(Intent.createChooser(i, "File Browser"), FILE_SELECT_CODE);
        }

        // For Android 4.1
        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            startActivityForResult(Intent.createChooser(i, "File Chooser"), FILE_SELECT_CODE);

        }

    }

    private class DuomiWebViewClient extends WebViewClient {
        private Context context;

        public DuomiWebViewClient(Context context) {
            super();
            this.context = context;
        }

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            view.loadUrl(url);
            return true;
        }

        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            super.onPageStarted(view, url, favicon);
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
        }

    }

    // flipscreen not loading again
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        if (resultCode != RESULT_OK) {
            return;
        }

        switch (requestCode) {
            case FILE_SELECT_CODE : {
                Uri uri = data.getData();
                Log.e("Tag", "Path:" + uri.toString());
                mUploadMessage.onReceiveValue(uri);
                mUploadMessage = null;
            }
                break;
        }
    }

}

上面的代码只是提供了上传文件的功能,有时候当你想上传图时可能需要拍照上传,或者你想上传各种多媒体类型的文件,怎么办?

其实我们手机的浏览器已经有这些功能了,为何不Reading the fucking source code!

下面提供一个复杂的功能,代码是从浏览器中移植过来的:

拥有的功能:

1.客户端弹出服务端JS对话框

2.能够拍照上传

3.支持主流媒体文件选择

废话不多说,贴代码:

public class WebViewActivity extends FragmentActivity {

    private WebView webview;
    private UploadHandler mUploadHandler;

    @Override
    protected void onCreate(Bundle arg0) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        super.onCreate(arg0);
        setContentView(R.layout.activity_webview);

        webview = (WebView) findViewById(R.id.webview);
        webview.setWebChromeClient(new MyChromeViewClient());
        webview.setWebViewClient(new MyWebViewClinet());
   //     webview.setDownloadListener(new MyDownloadListener());

        initWebViewSettings();
        initData();

    }

    @SuppressLint({ "SetJavaScriptEnabled", "NewApi" })
    private void initWebViewSettings() {

        WebSettings settings = webview.getSettings();
        settings.setDefaultFontSize(50);
        settings.setDefaultFixedFontSize(30);

        settings.setJavaScriptEnabled(true);
        settings.setAllowFileAccess(true);
        settings.setDomStorageEnabled(true);
        settings.setLoadWithOverviewMode(true);
        settings.setUseWideViewPort(true);
        settings.setSupportZoom(true);

        // WebView inside Browser doesn‘t want initial focus to be set.
        settings.setNeedInitialFocus(false);
        // Browser supports multiple windows
        settings.setSupportMultipleWindows(true);
        // enable smooth transition for better performance during panning or

    }

    private void initData() {
        Intent intent = getIntent();
        String url = intent.getStringExtra("url");
        webview.loadUrl(url);

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {

        if (requestCode == Controller.FILE_SELECTED) {
            // Chose a file from the file picker.
            if (mUploadHandler != null) {
                mUploadHandler.onResult(resultCode, intent);
            }
        }
        super.onActivityResult(requestCode, resultCode, intent);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {

        if ((keyCode == KeyEvent.KEYCODE_BACK) && webview.canGoBack()) {
            webview.goBack();
            return true;
        }

        return super.onKeyDown(keyCode, event);
    }

    class  MyDownloadListener implements DownloadListener{

        @Override
        public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype,
                long contentLength) {
            // TODO Auto-generated method stub

        }

    }

    class MyChromeViewClient extends WebChromeClient {

        @Override
        public void onCloseWindow(WebView window) {
            WebViewActivity.this.finish();
            super.onCloseWindow(window);
        }

        public void onProgressChanged(WebView view, final int progress) {

        }

        @Override
        public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {

            new AlertDialog.Builder(WebViewActivity.this).setTitle("提示信息").setMessage(message)
                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            result.confirm();
                        }
                    }).setCancelable(false).create().show();
            return true;
        }

        @Override
        public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {

            new AlertDialog.Builder(WebViewActivity.this).setTitle("提示信息").setMessage(message)
                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            result.confirm();
                        }
                    }).setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            result.cancel();
                        }
                    }).setCancelable(false).create().show();
            return true;

        }

        // Android 2.x
        public void openFileChooser(ValueCallback<Uri> uploadMsg) {
            openFileChooser(uploadMsg, "");
        }

        // Android 3.0
        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
            openFileChooser(uploadMsg, "", "filesystem");
        }

        // Android 4.1
        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {

            mUploadHandler = new UploadHandler(new Controller());
            mUploadHandler.openFileChooser(uploadMsg, acceptType, capture);
        }

    }

    class MyWebViewClinet extends WebViewClient {

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {

            return true;
        }

    }

    // copied from android-4.4.3_r1/src/com/android/browser/UploadHandler.java

    class UploadHandler {
        /*
         * The Object used to inform the WebView of the file to upload.
         */
        private ValueCallback<Uri> mUploadMessage;
        private String mCameraFilePath;
        private boolean mHandled;
        private boolean mCaughtActivityNotFoundException;
        private Controller mController;

        public UploadHandler(Controller controller) {
            mController = controller;
        }

        public String getFilePath() {
            return mCameraFilePath;
        }

        boolean handled() {
            return mHandled;
        }

        public void onResult(int resultCode, Intent intent) {
            if (resultCode == Activity.RESULT_CANCELED && mCaughtActivityNotFoundException) {
                // Couldn‘t resolve an activity, we are going to try again so skip
                // this result.
                mCaughtActivityNotFoundException = false;
                return;
            }
            Uri result = (intent == null || resultCode != Activity.RESULT_OK) ? null : intent.getData();

            // As we ask the camera to save the result of the user taking
            // a picture, the camera application does not return anything other
            // than RESULT_OK. So we need to check whether the file we expected
            // was written to disk in the in the case that we
            // did not get an intent returned but did get a RESULT_OK. If it was,
            // we assume that this result has came back from the camera.
            if (result == null && intent == null && resultCode == Activity.RESULT_OK) {
                File cameraFile = new File(mCameraFilePath);
                if (cameraFile.exists()) {
                    result = Uri.fromFile(cameraFile);
                    // Broadcast to the media scanner that we have a new photo
                    // so it will be added into the gallery for the user.
                    mController.getActivity().sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result));
                }
            }
            mUploadMessage.onReceiveValue(result);
            mHandled = true;
            mCaughtActivityNotFoundException = false;
        }

        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
            final String imageMimeType = "image/*";
            final String videoMimeType = "video/*";
            final String audioMimeType = "audio/*";
            final String mediaSourceKey = "capture";
            final String mediaSourceValueCamera = "camera";
            final String mediaSourceValueFileSystem = "filesystem";
            final String mediaSourceValueCamcorder = "camcorder";
            final String mediaSourceValueMicrophone = "microphone";
            // According to the spec, media source can be ‘filesystem‘ or ‘camera‘ or ‘camcorder‘
            // or ‘microphone‘ and the default value should be ‘filesystem‘.
            String mediaSource = mediaSourceValueFileSystem;
            if (mUploadMessage != null) {
                // Already a file picker operation in progress.
                return;
            }
            mUploadMessage = uploadMsg;
            // Parse the accept type.
            String params[] = acceptType.split(";");
            String mimeType = params[0];
            if (capture.length() > 0) {
                mediaSource = capture;
            }
            if (capture.equals(mediaSourceValueFileSystem)) {
                // To maintain backwards compatibility with the previous implementation
                // of the media capture API, if the value of the ‘capture‘ attribute is
                // "filesystem", we should examine the accept-type for a MIME type that
                // may specify a different capture value.
                for (String p : params) {
                    String[] keyValue = p.split("=");
                    if (keyValue.length == 2) {
                        // Process key=value parameters.
                        if (mediaSourceKey.equals(keyValue[0])) {
                            mediaSource = keyValue[1];
                        }
                    }
                }
            }
            //Ensure it is not still set from a previous upload.
            mCameraFilePath = null;
            if (mimeType.equals(imageMimeType)) {
                if (mediaSource.equals(mediaSourceValueCamera)) {
                    // Specified ‘image/*‘ and requested the camera, so go ahead and launch the
                    // camera directly.
                    startActivity(createCameraIntent());
                    return;
                } else {
                    // Specified just ‘image/*‘, capture=filesystem, or an invalid capture parameter.
                    // In all these cases we show a traditional picker filetered on accept type
                    // so launch an intent for both the Camera and image/* OPENABLE.
                    Intent chooser = createChooserIntent(createCameraIntent());
                    chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(imageMimeType));
                    startActivity(chooser);
                    return;
                }
            } else if (mimeType.equals(videoMimeType)) {
                if (mediaSource.equals(mediaSourceValueCamcorder)) {
                    // Specified ‘video/*‘ and requested the camcorder, so go ahead and launch the
                    // camcorder directly.
                    startActivity(createCamcorderIntent());
                    return;
                } else {
                    // Specified just ‘video/*‘, capture=filesystem or an invalid capture parameter.
                    // In all these cases we show an intent for the traditional file picker, filtered
                    // on accept type so launch an intent for both camcorder and video/* OPENABLE.
                    Intent chooser = createChooserIntent(createCamcorderIntent());
                    chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(videoMimeType));
                    startActivity(chooser);
                    return;
                }
            } else if (mimeType.equals(audioMimeType)) {
                if (mediaSource.equals(mediaSourceValueMicrophone)) {
                    // Specified ‘audio/*‘ and requested microphone, so go ahead and launch the sound
                    // recorder.
                    startActivity(createSoundRecorderIntent());
                    return;
                } else {
                    // Specified just ‘audio/*‘,  capture=filesystem of an invalid capture parameter.
                    // In all these cases so go ahead and launch an intent for both the sound
                    // recorder and audio/* OPENABLE.
                    Intent chooser = createChooserIntent(createSoundRecorderIntent());
                    chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(audioMimeType));
                    startActivity(chooser);
                    return;
                }
            }
            // No special handling based on the accept type was necessary, so trigger the default
            // file upload chooser.
            startActivity(createDefaultOpenableIntent());
        }

        private void startActivity(Intent intent) {
            try {
                mController.getActivity().startActivityForResult(intent, Controller.FILE_SELECTED);
            } catch (ActivityNotFoundException e) {
                // No installed app was able to handle the intent that
                // we sent, so fallback to the default file upload control.
                try {
                    mCaughtActivityNotFoundException = true;
                    mController.getActivity().startActivityForResult(createDefaultOpenableIntent(),
                            Controller.FILE_SELECTED);
                } catch (ActivityNotFoundException e2) {
                    // Nothing can return us a file, so file upload is effectively disabled.
                    Toast.makeText(mController.getActivity(), "File uploads are disabled.", Toast.LENGTH_LONG).show();
                }
            }
        }

        private Intent createDefaultOpenableIntent() {
            // Create and return a chooser with the default OPENABLE
            // actions including the camera, camcorder and sound
            // recorder where available.
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("*/*");
            Intent chooser = createChooserIntent(createCameraIntent(), createCamcorderIntent(),
                    createSoundRecorderIntent());
            chooser.putExtra(Intent.EXTRA_INTENT, i);
            return chooser;
        }

        private Intent createChooserIntent(Intent... intents) {
            Intent chooser = new Intent(Intent.ACTION_CHOOSER);
            chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents);
            chooser.putExtra(Intent.EXTRA_TITLE, "Choose file for upload");
            return chooser;
        }

        private Intent createOpenableIntent(String type) {
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType(type);
            return i;
        }

        private Intent createCameraIntent() {
            Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            File externalDataDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
            File cameraDataDir = new File(externalDataDir.getAbsolutePath() + File.separator + "browser-photos");
            cameraDataDir.mkdirs();
            mCameraFilePath = cameraDataDir.getAbsolutePath() + File.separator + System.currentTimeMillis() + ".jpg";
            cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(mCameraFilePath)));
            return cameraIntent;
        }

        private Intent createCamcorderIntent() {
            return new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
        }

        private Intent createSoundRecorderIntent() {
            return new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
        }
    }

    class Controller {

        final static int FILE_SELECTED = 4;

        Activity getActivity() {
            return WebViewActivity.this;
        }
    }

}

有几个类要说明下:

MyChromeViewClient 继承WebChromeClient重写了几个关键方法。其中有三个重载方法openFileChooser,用来兼容不同的Andorid版本,以防出现NoSuchMethodError异常。

另外一个类UploadHandler,起到一个解耦合作用,它相当于WebChromeClient和Web网页端的一个搬运工兼职翻译,解析网页端传递给WebChromeClient的动作,然后将onActivityResult接收用户选择的文件传递给司机ValueCallback。WebChromeClient提供了一个Web网页端和客户端交互的通道,而UploadHandler就是用来搬砖的~。

UploadHandler有个很重要的成员变量:ValueCallback<Uri> mUploadMessage。ValueCallback是WebView留下来的一个回调,就像是WebView的司机一样,当WebChromeClient和UploadHandler合作将文件选择后,ValueCallback开始将文件给WebView,告诉WebView开始干活了,砖头已经运回来了,你可以盖房子了。

     
时间: 2024-09-29 01:42:20

WebView加载html实现网页上传本地文件(图片,拍照,语音等)的相关文章

网页上传整个文件夹

网页上传整个文件夹+断点续传 一.概述 所谓断点续传,其实只是指下载,也就是要从文件已经下载的地方开始继续下载.在以前版本的HTTP协议是不支持断点的,HTTP/1.1开始就支持了.一般断点下载时才用到Range和Content-Range实体头.HTTP协议本身不支持断点上传,需要自己实现. 二.Range 用于请求头中,指定第一个字节的位置和最后一个字节的位置,一般格式: Range:用于客户端到服务端的请求,可以通过改字段指定下载文件的某一段大小及其单位,字节偏移从0开始.典型格式: Ra

如何用一张图片代替 &#39;input:file&#39; 上传本地文件??

今天去面试,碰到了一道题,也许是因为紧张或者喝水喝多了,一时竟然没有转过弯来,回来之后一细想原来这么简单,哭笑不得,特此记录一下! 原题是这样的:  如何用一张图片代替 'input:file' 上传本地文件?? 因为默认的 <input type='file'> 上传文件控件样式特别丑,需要换成自定义的图片,如何实现这个功能?? 也就是,将这个玩意: 换成这样的: 当时我还讲了一下label与input之间的绑定关系,问到这个的时候竟然脑袋短路一时没想到label这玩意儿??label作为一

git 上传本地文件到github

1 git config --global user.name "Your Real Name" 2 git config --global user.email [email protected] git init git add . git commit -m 'Test' git remote add origin [email protected]:XXX/XXX.git 3 git push -u origin master 一些可能遇到的问题解决: 如果输入$ git re

Selenium如何实现上传本地文件

? 1 2 3 4 5 6 7 8 9 public void uploadLocalFileToServer(String uploadFileName){         String AutomationPath = System.getProperty("user.dir");         String filePath=AutomationPath+"\\src\\test\\resources\\testData\\"+uploadFileName;

远程桌面拨号VPS如何上传本地文件

VPS可以运行程序,挂游戏等,就算本机电脑关了,也不会断开,我们经常需要将本地电脑上的文件传到vps上来使用 VPS如何上传本地文件教程 1.开始-运行,输入mstsc,点确定 2.输入购买的账号,点击选项 3.选择本地资源,然后点击详细信息 4.点一下驱动器左边的+,然后在里面选择你要上传的文件是在哪个盘,就选哪个盘,这里以D和E盘为例,选好后点确定 5.回到常规选项卡下,连接你的VPS既可. 原文地址:http://blog.51cto.com/14143213/2336979

两种方法上传本地文件到github(转)

自从使用github以来,一直都是在github网站在线上传文件到仓库中,但是有时因为网络或者电脑的原因上传失败.最重要的原因是我习惯本地编辑,完成以后再一起上传github.看过了几个教程,总结出最适合自己的比较简单的方法. 两种方法上传本地文件到github 1. github在线上传文件夹 在线上传也可以上传完整的文件夹结构,直接拖拽到上传文件页面的框中即可. 1.1点击上传文件 点击上传 1.2 直接拖拽 直接拖拽即可上传文件夹及文件夹里面的文件.如果点击 choose your fil

利用git上传本地文件、文件夹到Github

 利用git上传文件至github是特别常用的,总结以下内容供参考使用. 第一步:下载git工具,[这里是链接](https://git-scm.com/downloads),选择适合自己的版本进行安装. 第二步:安装完成后,找到Git bash,双击打开. 第三步:输入自己的用户名和邮箱(为注册GITHUB账号时的用户名和邮箱) $ git config --global user.name "[email protected]" $ git config --global user

Github 如何上传本地文件

前提 首先你要在github上申请一个账号,网址:https://github.com/ 然后你要下载一个git工具,网址:https://gitforwindows.org/ 第一步:新建仓库 新建仓库步骤省略,最后我们得到一个仓库地址: https://github.com/wangle1218/×××××××××.git 第二步:进入要上传的文件夹(或者直接找到项目右键选择 Git Bash here ),初始化上传文件夹仓库,项目里面会多一个.git文件,它是隐藏文件,不要修改. $ c

使用Kindeditor的多文件(图片)上传时出现上传失败的解决办法/使用Flash上传多文件(图片)上传时上传失败的解决办法

近来用户反映希望我们把在线编辑器中的多图片上传功能实现,因为他们在编辑商品描述时经常会有一次上传多张图片的需求,如果要逐张选择的话效率很低,客户的需求就是我们的追求,很快我们就把完善功能排到了日程表中,要求尽快实现. 我们在项目中使用的在线编辑器是Kindeditor4.1.10,它们的多文件上传插件是使用Flash实现的,原本应该就是能使用的,但为什么老是显示上传失败的,百度了一下前人的经验和教训,出现这种情况,有两种可能:1)上传的目标文件夹没有写权限,导致上传的文件无法进行写操作,所以上传