带有JcropHTML5图片上传器

HTML5 Image uploader with Jcrop
HTML5 Image uploader with Jcrop

HTML5 Image uploader with Jcrop We have received several inquiries for the last time from our readers with a question – how to upload photos to website. I think that this is an interesting question, and, I decided to lift the veil of this question. But, I think that the basic file upload is a bit boring thing, so, I decided to add an important feature – Cropping. It should be more attractive. Moreover, we are going to use HTML5 FileReader in order to perform cropping with Jcrop (jquery library) at client size. That will get rid of unnecessary steps. In the result – we should get 3-step process: select file -> crop -> upload. During selecting a file, we will check for the file type and size (in order to avoid huge files). Finally, when everything is ready and we have uploaded the cropped image – we will accept (upload) this file into our website (into certain folder). Please pay attention, that GD library is required to process images. If you are ready – let’s start.

带有JcropHTML5图片上传器我们最近一次收到读者的询问,涉及一个问题–如何将照片上传到网站。 我认为这是一个有趣的问题,因此,我决定揭开这个问题的面纱。 但是,我认为基本文件上传有点无聊,因此,我决定添加一个重要功能-裁剪。 它应该更具吸引力。 此外,我们将使用HTML5 FileReader以便在客户端大小下使用Jcrop(jquery库)进行裁剪。 这将摆脱不必要的步骤。 结果–我们应该获得3个步骤:选择文件->裁剪->上传。 在选择文件期间,我们将检查文件类型和大小(以避免出现大文件)。 最后,当一切准备就绪并且我们已经上传了裁剪的图像时,我们将接受(上传)此文件到我们的网站(到特定文件夹中)。 请注意,需要使用GD库来处理图像。 如果您准备好了,那就开始吧。

It is the very time to test our demo and download the sources:

现在是时候测试我们的演示并下载源代码了:

现场演示

[sociallocker]

[社交储物柜]

打包下载

[/sociallocker]

[/ sociallocker]

步骤1. HTML (Step 1. HTML)

Our first step is html markup. first, we have to put styles and scripts in the HEAD section:

我们的第一步是html标记。 首先,我们必须在HEAD部分中放置样式和脚本:


<!-- add styles -->
<link href="css/main.css" rel="stylesheet" type="text/css" />
<link href="css/jquery.Jcrop.min.css" rel="stylesheet" type="text/css" />
<!-- add scripts -->
<script src="js/jquery.min.js"></script>
<script src="js/jquery.Jcrop.min.js"></script>
<script src="js/script.js"></script>

<!-- add styles -->
<link href="css/main.css" rel="stylesheet" type="text/css" />
<link href="css/jquery.Jcrop.min.css" rel="stylesheet" type="text/css" />
<!-- add scripts -->
<script src="js/jquery.min.js"></script>
<script src="js/jquery.Jcrop.min.js"></script>
<script src="js/script.js"></script>

And now, in the BODY section we can put our form:

现在,在“身体”部分,我们可以输入表格:


<div class="bbody">
    <!-- upload form -->
    <form id="upload_form" enctype="multipart/form-data" method="post" action="upload.php" onsubmit="return checkForm()">
        <!-- hidden crop params -->
        <input type="hidden" id="x1" name="x1" />
        <input type="hidden" id="y1" name="y1" />
        <input type="hidden" id="x2" name="x2" />
        <input type="hidden" id="y2" name="y2" />
        <h2>Step1: Please select image file</h2>
        <div><input type="file" name="image_file" id="image_file" onchange="fileSelectHandler()" /></div>
        <div class="error"></div>
        <div class="step2">
            <h2>Step2: Please select a crop region</h2>
            <img id="preview" />
            <div class="info">
                <label>File size</label> <input type="text" id="filesize" name="filesize" />
                <label>Type</label> <input type="text" id="filetype" name="filetype" />
                <label>Image dimension</label> <input type="text" id="filedim" name="filedim" />
                <label>W</label> <input type="text" id="w" name="w" />
                <label>H</label> <input type="text" id="h" name="h" />
            </div>
            <input type="submit" value="Upload" />
        </div>
    </form>
</div>

<div class="bbody">
    <!-- upload form -->
    <form id="upload_form" enctype="multipart/form-data" method="post" action="upload.php" onsubmit="return checkForm()">
        <!-- hidden crop params -->
        <input type="hidden" id="x1" name="x1" />
        <input type="hidden" id="y1" name="y1" />
        <input type="hidden" id="x2" name="x2" />
        <input type="hidden" id="y2" name="y2" />
        <h2>Step1: Please select image file</h2>
        <div><input type="file" name="image_file" id="image_file" onchange="fileSelectHandler()" /></div>
        <div class="error"></div>
        <div class="step2">
            <h2>Step2: Please select a crop region</h2>
            <img id="preview" />
            <div class="info">
                <label>File size</label> <input type="text" id="filesize" name="filesize" />
                <label>Type</label> <input type="text" id="filetype" name="filetype" />
                <label>Image dimension</label> <input type="text" id="filedim" name="filedim" />
                <label>W</label> <input type="text" id="w" name="w" />
                <label>H</label> <input type="text" id="h" name="h" />
            </div>
            <input type="submit" value="Upload" />
        </div>
    </form>
</div>

I hope that all is clear at this step – this is usual upload form, with hidden and visible fields, once we have selected an image, we will see second step (crop). Once we have cropped necessary area, we can Upload our result.

我希望这一步一切都清楚–这是通常的上传表单,具有隐藏和可见字段,一旦我们选择了图像,我们将看到第二步(裁剪)。 裁剪必要的区域后,我们可以上传结果。

步骤2. CSS (Step 2. CSS)

Now, I would like to give you CSS styles to stylize our form:

现在,我想给您CSS样式来样式化我们的表单:

css / main.css (css/main.css)

.bheader {
    background-color: #DDDDDD;
    border-radius: 10px 10px 0 0;
    padding: 10px 0;
    text-align: center;
}
.bbody {
    color: #000;
    overflow: hidden;
    padding-bottom: 20px;
    text-align: center;
    background: -moz-linear-gradient(#ffffff, #f2f2f2);
    background: -ms-linear-gradient(#ffffff, #f2f2f2);
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f2f2f2));
    background: -webkit-linear-gradient(#ffffff, #f2f2f2);
    background: -o-linear-gradient(#ffffff, #f2f2f2);
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2');
    -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2')";
    background: linear-gradient(#ffffff, #f2f2f2);
}
.bbody h2, .info, .error {
    margin: 10px 0;
}
.step2, .error {
    display: none;
}
.error {
    font-size: 18px;
    font-weight: bold;
    color: red;
}
.info {
    font-size: 14px;
}
label {
    margin: 0 5px;
}
input {
    border: 1px solid #CCCCCC;
    border-radius: 10px;
    padding: 4px 8px;
    text-align: center;
    width: 70px;
}
.jcrop-holder {
    display: inline-block;
}
input[type=submit] {
    background: #e3e3e3;
    border: 1px solid #bbb;
    border-radius: 3px;
    -webkit-box-shadow: inset 0 0 1px 1px #f6f6f6;
    box-shadow: inset 0 0 1px 1px #f6f6f6;
    color: #333;
    font: bold 12px/1 "helvetica neue", helvetica, arial, sans-serif;
    padding: 8px 0 9px;
    text-align: center;
    text-shadow: 0 1px 0 #fff;
    width: 150px;
}
input[type=submit]:hover {
    background: #d9d9d9;
    -webkit-box-shadow: inset 0 0 1px 1px #eaeaea;
    box-shadow: inset 0 0 1px 1px #eaeaea;
    color: #222;
    cursor: pointer;
}
input[type=submit]:active {
    background: #d0d0d0;
    -webkit-box-shadow: inset 0 0 1px 1px #e3e3e3;
    box-shadow: inset 0 0 1px 1px #e3e3e3;
    color: #000;
}

.bheader {
    background-color: #DDDDDD;
    border-radius: 10px 10px 0 0;
    padding: 10px 0;
    text-align: center;
}
.bbody {
    color: #000;
    overflow: hidden;
    padding-bottom: 20px;
    text-align: center;
    background: -moz-linear-gradient(#ffffff, #f2f2f2);
    background: -ms-linear-gradient(#ffffff, #f2f2f2);
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f2f2f2));
    background: -webkit-linear-gradient(#ffffff, #f2f2f2);
    background: -o-linear-gradient(#ffffff, #f2f2f2);
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2');
    -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2')";
    background: linear-gradient(#ffffff, #f2f2f2);
}
.bbody h2, .info, .error {
    margin: 10px 0;
}
.step2, .error {
    display: none;
}
.error {
    font-size: 18px;
    font-weight: bold;
    color: red;
}
.info {
    font-size: 14px;
}
label {
    margin: 0 5px;
}
input {
    border: 1px solid #CCCCCC;
    border-radius: 10px;
    padding: 4px 8px;
    text-align: center;
    width: 70px;
}
.jcrop-holder {
    display: inline-block;
}
input[type=submit] {
    background: #e3e3e3;
    border: 1px solid #bbb;
    border-radius: 3px;
    -webkit-box-shadow: inset 0 0 1px 1px #f6f6f6;
    box-shadow: inset 0 0 1px 1px #f6f6f6;
    color: #333;
    font: bold 12px/1 "helvetica neue", helvetica, arial, sans-serif;
    padding: 8px 0 9px;
    text-align: center;
    text-shadow: 0 1px 0 #fff;
    width: 150px;
}
input[type=submit]:hover {
    background: #d9d9d9;
    -webkit-box-shadow: inset 0 0 1px 1px #eaeaea;
    box-shadow: inset 0 0 1px 1px #eaeaea;
    color: #222;
    cursor: pointer;
}
input[type=submit]:active {
    background: #d0d0d0;
    -webkit-box-shadow: inset 0 0 1px 1px #e3e3e3;
    box-shadow: inset 0 0 1px 1px #e3e3e3;
    color: #000;
}

步骤3. JS (Step 3. JS)

Our next step – is javascript. Please review the result code (my comments are below the code):

我们的下一步-是javascript。 请查看结果代码(我的评论在代码下方):

js / script.js (js/script.js)

// convert bytes into friendly format
function bytesToSize(bytes) {
    var sizes = ['Bytes', 'KB', 'MB'];
    if (bytes == 0) return 'n/a';
    var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
    return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
};
// check for selected crop region
function checkForm() {
    if (parseInt($('#w').val())) return true;
    $('.error').html('Please select a crop region and then press Upload').show();
    return false;
};
// update info by cropping (onChange and onSelect events handler)
function updateInfo(e) {
    $('#x1').val(e.x);
    $('#y1').val(e.y);
    $('#x2').val(e.x2);
    $('#y2').val(e.y2);
    $('#w').val(e.w);
    $('#h').val(e.h);
};
// clear info by cropping (onRelease event handler)
function clearInfo() {
    $('.info #w').val('');
    $('.info #h').val('');
};
// Create variables (in this scope) to hold the Jcrop API and image size
var jcrop_api, boundx, boundy;
function fileSelectHandler() {
    // get selected file
    var oFile = $('#image_file')[0].files[0];
    // hide all errors
    $('.error').hide();
    // check for image type (jpg and png are allowed)
    var rFilter = /^(image\/jpeg|image\/png)$/i;
    if (! rFilter.test(oFile.type)) {
        $('.error').html('Please select a valid image file (jpg and png are allowed)').show();
        return;
    }
    // check for file size
    if (oFile.size > 250 * 1024) {
        $('.error').html('You have selected too big file, please select a one smaller image file').show();
        return;
    }
    // preview element
    var oImage = document.getElementById('preview');
    // prepare HTML5 FileReader
    var oReader = new FileReader();
        oReader.onload = function(e) {
        // e.target.result contains the DataURL which we can use as a source of the image
        oImage.src = e.target.result;
        oImage.onload = function () { // onload event handler
            // display step 2
            $('.step2').fadeIn(500);
            // display some basic image info
            var sResultFileSize = bytesToSize(oFile.size);
            $('#filesize').val(sResultFileSize);
            $('#filetype').val(oFile.type);
            $('#filedim').val(oImage.naturalWidth + ' x ' + oImage.naturalHeight);
            // destroy Jcrop if it is existed
            if (typeof jcrop_api != 'undefined') {
                jcrop_api.destroy();
                jcrop_api = null;
                $('#preview').width(oImage.naturalWidth);
                $('#preview').height(oImage.naturalHeight);
            }
            setTimeout(function(){
                // initialize Jcrop
                $('#preview').Jcrop({
                    minSize: [32, 32], // min crop size
                    aspectRatio : 1, // keep aspect ratio 1:1
                    bgFade: true, // use fade effect
                    bgOpacity: .3, // fade opacity
                    onChange: updateInfo,
                    onSelect: updateInfo,
                    onRelease: clearInfo
                }, function(){
                    // use the Jcrop API to get the real image size
                    var bounds = this.getBounds();
                    boundx = bounds[0];
                    boundy = bounds[1];
                    // Store the Jcrop API in the jcrop_api variable
                    jcrop_api = this;
                });
            },3000);
        };
    };
    // read selected file as DataURL
    oReader.readAsDataURL(oFile);
}

// convert bytes into friendly format
function bytesToSize(bytes) {
    var sizes = ['Bytes', 'KB', 'MB'];
    if (bytes == 0) return 'n/a';
    var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
    return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
};
// check for selected crop region
function checkForm() {
    if (parseInt($('#w').val())) return true;
    $('.error').html('Please select a crop region and then press Upload').show();
    return false;
};
// update info by cropping (onChange and onSelect events handler)
function updateInfo(e) {
    $('#x1').val(e.x);
    $('#y1').val(e.y);
    $('#x2').val(e.x2);
    $('#y2').val(e.y2);
    $('#w').val(e.w);
    $('#h').val(e.h);
};
// clear info by cropping (onRelease event handler)
function clearInfo() {
    $('.info #w').val('');
    $('.info #h').val('');
};
// Create variables (in this scope) to hold the Jcrop API and image size
var jcrop_api, boundx, boundy;
function fileSelectHandler() {
    // get selected file
    var oFile = $('#image_file')[0].files[0];
    // hide all errors
    $('.error').hide();
    // check for image type (jpg and png are allowed)
    var rFilter = /^(image\/jpeg|image\/png)$/i;
    if (! rFilter.test(oFile.type)) {
        $('.error').html('Please select a valid image file (jpg and png are allowed)').show();
        return;
    }
    // check for file size
    if (oFile.size > 250 * 1024) {
        $('.error').html('You have selected too big file, please select a one smaller image file').show();
        return;
    }
    // preview element
    var oImage = document.getElementById('preview');
    // prepare HTML5 FileReader
    var oReader = new FileReader();
        oReader.onload = function(e) {
        // e.target.result contains the DataURL which we can use as a source of the image
        oImage.src = e.target.result;
        oImage.onload = function () { // onload event handler
            // display step 2
            $('.step2').fadeIn(500);
            // display some basic image info
            var sResultFileSize = bytesToSize(oFile.size);
            $('#filesize').val(sResultFileSize);
            $('#filetype').val(oFile.type);
            $('#filedim').val(oImage.naturalWidth + ' x ' + oImage.naturalHeight);
            // destroy Jcrop if it is existed
            if (typeof jcrop_api != 'undefined') {
                jcrop_api.destroy();
                jcrop_api = null;
                $('#preview').width(oImage.naturalWidth);
                $('#preview').height(oImage.naturalHeight);
            }
            setTimeout(function(){
                // initialize Jcrop
                $('#preview').Jcrop({
                    minSize: [32, 32], // min crop size
                    aspectRatio : 1, // keep aspect ratio 1:1
                    bgFade: true, // use fade effect
                    bgOpacity: .3, // fade opacity
                    onChange: updateInfo,
                    onSelect: updateInfo,
                    onRelease: clearInfo
                }, function(){
                    // use the Jcrop API to get the real image size
                    var bounds = this.getBounds();
                    boundx = bounds[0];
                    boundy = bounds[1];
                    // Store the Jcrop API in the jcrop_api variable
                    jcrop_api = this;
                });
            },3000);
        };
    };
    // read selected file as DataURL
    oReader.readAsDataURL(oFile);
}

There are several common functions in the beginning: bytesToSize, checkForm, updateInfo and clearInfo. They are pretty easy. The next function (fileSelectHandler) is more complex, basically, this is the main function. When we have selected a file (I suppose – image file), we will check this file for Type and Size. You can see here a filter for image formats: png and jpg. Plus, we don’t need very large images, I think that 250kb is more than enough. Then, if everything is ok, we can read our selected file using FileReader::readAsDataURL (html5 function). And, once it has loaded, we can continue: we should display step2 with Preview and info section, and then – we have to initialize (or – reinitialize) Jcrop for our Preview image. This is how it works. Once we have cropped the image, we can click ‘Upload’ button in order to send result to the server.

开头有几个常用功能:bytesToSize,checkForm,updateInfo和clearInfo。 他们很容易。 下一个函数(fileSelectHandler)更复杂,基本上,这是主要功能。 选择一个文件(我想是–图像文件)后,我们将检查该文件的类型和大小。 您可以在此处看到图像格式的过滤器:png和jpg。 另外,我们不需要非常大的图像,我认为250kb绰绰有余。 然后,如果一切正常,我们可以使用FileReader :: readAsDataURL(html5函数)读取所选文件。 并且,一旦加载完成,我们就可以继续:我们应该显示带有“预览”和“信息”部分的step2,然后–我们必须为预览图像初始化(或–重新初始化)Jcrop。 这就是它的工作方式。 裁剪图像后,可以单击“上传”按钮以将结果发送到服务器。

步骤4. PHP (Step 4. PHP)

In this step – we have to accept (and upload) our result photo. I prepared next useful PHP function for you:

在此步骤中,我们必须接受(并上传)我们的结果照片。 我为您准备了下一个有用PHP函数:

upload.php (upload.php)

function uploadImageFile() { // Note: GD library is required for this function
    if ($_SERVER['REQUEST_METHOD'] == 'POST') {
        $iWidth = $iHeight = 200; // desired image result dimensions
        $iJpgQuality = 90;
        if ($_FILES) {
            // if no errors and size less than 250kb
            if (! $_FILES['image_file']['error'] && $_FILES['image_file']['size'] < 250 * 1024) {
                if (is_uploaded_file($_FILES['image_file']['tmp_name'])) {
                    // new unique filename
                    $sTempFileName = 'cache/' . md5(time().rand());
                    // move uploaded file into cache folder
                    move_uploaded_file($_FILES['image_file']['tmp_name'], $sTempFileName);
                    // change file permission to 644
                    @chmod($sTempFileName, 0644);
                    if (file_exists($sTempFileName) && filesize($sTempFileName) > 0) {
                        $aSize = getimagesize($sTempFileName); // try to obtain image info
                        if (!$aSize) {
                            @unlink($sTempFileName);
                            return;
                        }
                        // check for image type
                        switch($aSize[2]) {
                            case IMAGETYPE_JPEG:
                                $sExt = '.jpg';
                                // create a new image from file
                                $vImg = @imagecreatefromjpeg($sTempFileName);
                                break;
                            case IMAGETYPE_PNG:
                                $sExt = '.png';
                                // create a new image from file
                                $vImg = @imagecreatefrompng($sTempFileName);
                                break;
                            default:
                                @unlink($sTempFileName);
                                return;
                        }
                        // create a new true color image
                        $vDstImg = @imagecreatetruecolor( $iWidth, $iHeight );
                        // copy and resize part of an image with resampling
                        imagecopyresampled($vDstImg, $vImg, 0, 0, (int)$_POST['x1'], (int)$_POST['y1'], $iWidth, $iHeight, (int)$_POST['w'], (int)$_POST['h']);
                        // define a result image filename
                        $sResultFileName = $sTempFileName . $sExt;
                        // output image to file
                        imagejpeg($vDstImg, $sResultFileName, $iJpgQuality);
                        @unlink($sTempFileName);
                        return $sResultFileName;
                    }
                }
            }
        }
    }
}
$sImage = uploadImageFile();
echo '<img src="'.$sImage.'" />';

function uploadImageFile() { // Note: GD library is required for this function
    if ($_SERVER['REQUEST_METHOD'] == 'POST') {
        $iWidth = $iHeight = 200; // desired image result dimensions
        $iJpgQuality = 90;
        if ($_FILES) {
            // if no errors and size less than 250kb
            if (! $_FILES['image_file']['error'] && $_FILES['image_file']['size'] < 250 * 1024) {
                if (is_uploaded_file($_FILES['image_file']['tmp_name'])) {
                    // new unique filename
                    $sTempFileName = 'cache/' . md5(time().rand());
                    // move uploaded file into cache folder
                    move_uploaded_file($_FILES['image_file']['tmp_name'], $sTempFileName);
                    // change file permission to 644
                    @chmod($sTempFileName, 0644);
                    if (file_exists($sTempFileName) && filesize($sTempFileName) > 0) {
                        $aSize = getimagesize($sTempFileName); // try to obtain image info
                        if (!$aSize) {
                            @unlink($sTempFileName);
                            return;
                        }
                        // check for image type
                        switch($aSize[2]) {
                            case IMAGETYPE_JPEG:
                                $sExt = '.jpg';
                                // create a new image from file
                                $vImg = @imagecreatefromjpeg($sTempFileName);
                                break;
                            case IMAGETYPE_PNG:
                                $sExt = '.png';
                                // create a new image from file
                                $vImg = @imagecreatefrompng($sTempFileName);
                                break;
                            default:
                                @unlink($sTempFileName);
                                return;
                        }
                        // create a new true color image
                        $vDstImg = @imagecreatetruecolor( $iWidth, $iHeight );
                        // copy and resize part of an image with resampling
                        imagecopyresampled($vDstImg, $vImg, 0, 0, (int)$_POST['x1'], (int)$_POST['y1'], $iWidth, $iHeight, (int)$_POST['w'], (int)$_POST['h']);
                        // define a result image filename
                        $sResultFileName = $sTempFileName . $sExt;
                        // output image to file
                        imagejpeg($vDstImg, $sResultFileName, $iJpgQuality);
                        @unlink($sTempFileName);
                        return $sResultFileName;
                    }
                }
            }
        }
    }
}
$sImage = uploadImageFile();
echo '<img src="'.$sImage.'" />';

As you see – we have to check for image size and format at the server’s side too. In the result – we will get double protection (at user side and server side) from unwanted files. Once we have uploaded the image (using move_uploaded_file) – we can crop it (using GD’s functions: imagecreatefromjpeg, imagecreatetruecolor and imagecopyresampled), and – turn result into image file using ‘imagejpeg’ function. Please pay attention – that in the result we will get a small image (which is onle 200×200), so, beside cropping, we also resize the image. I selected next desired size for all incoming photos: 200×200 (this is a good format for .. profile’s avatars as example). Finally – we can display this image on the screen. That’s all.

如您所见–我们也必须在服务器端检查图像大小和格式。 结果–我们将获得对有害文件的双重保护(在用户端和服务器端)。 上载图像后(使用move_uploaded_file)–我们可以对其进行裁剪(使用GD的功能:imagecreatefromjpeg,imagecreatetruecolor和imagecopyresampled),以及–使用“ imagejpeg”功能将结果转换为图像文件。 请注意–结果中我们将得到一个较小的图像(仅200×200),因此,除了裁剪之外,我们还调整了图像的大小。 我为所有传入的照片选择了下一个所需的尺寸:200×200(这是..个人资料头像的一种很好的格式)。 最后–我们可以在屏幕上显示此图像。 就这样。

现场演示

结论 (Conclusion)

We have just created own HTML5 Image uploader with Jcrop. I hope that you like it. It would be nice of you to share our materials with your friends. Good luck and welcome back!

我们刚刚使用Jcrop创建了自己HTML5图片上传器。 我希望你喜欢。 希望与您的朋友分享我们的资料。 祝你好运,欢迎回来!

翻译自: https://www.script-tutorials.com/html5-image-uploader-with-jcrop/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值