Yii2-无法上传图片

Yii2- Unable to upload image

我正在研究 yii2。在我看来,我正在尝试上传一张图片。但是我无法上传它。

型号

class MeterAcceptanceHeader extends \yii\db\ActiveRecord
{

public static $status_titles =[
    0 => 'Prepared',
    1 => 'Created',
    2 => 'Printed',
    3 => 'Canceled',
];

/**
 * @inheritdoc
 */
public static function tableName()
{
    return 'meter_acceptance_header';
}

/**
 * @inheritdoc
 */
public function rules()
{
    return [
        [['sub_div', 'prepared_by'], 'required'],
        [['prepared_by', 'updated_by'], 'integer'],
        [['prepared_at', 'updated_at'], 'safe'],
        [['sub_div', 'meter_type', 'status'], 'string', 'max' => 100],
        [['images'], 'string', 'max' => 255],
        [['images'], 'file', 'skipOnEmpty' => true, 'extensions' => 'png,jpg,pdf', 'maxFiles' => 4],
        [['sub_div'], 'exist', 'skipOnError' => true, 'targetClass' => SurveyHescoSubdivision::className(), 'targetAttribute' => ['sub_div' => 'sub_div_code']],
        [['prepared_by'], 'exist', 'skipOnError' => true, 'targetClass' => User::className(), 'targetAttribute' => ['prepared_by' => 'id']],
    ];
}

/**
 * @inheritdoc
 */
public function attributeLabels()
{
    return [
        'id' => 'ID',
        'sub_div' => 'Sub Div',
        'meter_type' => 'Meter Type',
        'prepared_by' => 'Prepared By',
        'prepared_at' => 'Prepared At',
        'updated_at' => 'Updated At',
        'status' => 'Status',
        'updated_by' => 'Updated By',
        'images' => 'Document Snap',
    ];
}

/**
 * @return \yii\db\ActiveQuery
 */
public function getMeterAcceptanceDetails()
{
    return $this->hasMany(MeterAcceptanceDetails::className(), ['accpt_id' => 'id']);
}

/**
 * @return \yii\db\ActiveQuery
 */
public function getSubDiv()
{
    return $this->hasOne(SurveyHescoSubdivision::className(), ['sub_div_code' => 'sub_div']);
}

/**
 * @return \yii\db\ActiveQuery
 */
public function getPrepared()
{
    return $this->hasOne(User::className(), ['id' => 'prepared_by']);
}
}

MeterAcceptanceHeader Table

MeterAcceptanceImages Table

有一个 form1,提示用户从下拉列表中选择 select。

Form1 视图

<div class="meter-acceptance-header-form">
<?php $model->status = common\models\MeterAcceptanceHeader::$status_titles[0]; ?>

<?php $form = ActiveForm::begin(['id'=>'acceptance-form','options' => ['enctype' => 'multipart/form-data']]); ?>

<?= $form->field($model, 'sub_div')->dropDownList([''=>'Please Select'] + \common\models\SurveyHescoSubdivision::toArrayList()) ?>

<?= $form->field($model, 'meter_type')->dropDownList([''=>'Please Select','Single-Phase' => 'Single-Phase', '3-Phase' => '3-Phase', 'L.T.TOU' => 'L.T.TOU']) ?>

<?= $form->field($model, 'status')->textInput(['maxlength' => true,'readonly' => true]) ?>

<div class="form-group">
    <a class="btn btn-default" onclick="window.history.back()" href="javascript:;"><i
                class="fa fa-close"></i>
        Cancel</a>
    <a class="<?= $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary' ?>" onclick="
      $('#acceptance-form').submit();" href="javascript:">
        <?= $model->isNewRecord ? 'Create' : 'Update' ?></a>
</div>

<?php ActiveForm::end(); ?>

点击 Create 按钮后,系统会提示用户进入第二个表单,用户将在该表单中上传图片。

下面是我尝试上传的控制器代码。

public function actionSetpdf($id)
{
    $model = $this->findModel($id);
    $m = 0;
    $accpt_id = $model->id;
    $meter_type = $model->meter_type;
    $ogp_sub_div = $model->sub_div;
    $images=[];
    $ic=0;
    $files_uploaded = false;
    if(Yii::$app->request->isAjax && Yii::$app->request->post())
    {
        $data = explode(',',$_POST['data']);

        foreach($data as $value)
        {

            $m = new MeterAcceptanceDetails;
            $m -> load(Yii::$app->request->post());

            $m->accpt_id = $accpt_id;
            $m->meter_type = $meter_type;
            $m->created_at = date('Y-m-d H:i:s');
            $m->created_by = Yii::$app->user->id;
            $m->meter_id = $value;
            $m->meter_msn = \common\models\Meters::idTomsn($value);
            $m->flag = 1;// 1 means created
            $m->ogp_sub_div = $ogp_sub_div;

            if($m->save())
            { 
                // Here the upload image code starts
                if($ic==0)
                {

                    $model->images = UploadedFile::getInstances($model, 'images');
                    foreach ($model->images as $file)
                    {
                        if (file_exists($file->tempName))
                        {
                            $img_s = new MeterAcceptanceImages;
                            $file_name = rand(0, 1000) . time().date('his') . '.' . $file->extension;
                            $file->saveAs('uploads/meter_acceptance/' . $file_name);
                            $img_s->file_path = $file_name;
                            $img_s->accpt_id = $accpt_id;

                            if ($img_s->save()) {
                                $images[] = $img_s;

                            } else {
                                print_r($img_s->getErrors());
                            }
                        }
                    }
                }else{
                    foreach($images as $image){
                        $img_s = new MeterAcceptanceImages;
                        $img_s->file_path = $image->file_path;
                        $img_s->accpt_id = $accpt_id;
                        $img_s->save();
                    }
                }

                $model->status = MeterAcceptanceHeader::$status_titles[1];
                $model->update();

            }
            else{

                $this->renderAjax('viewcreated');
            }
        }

    }
    else{
        $this->renderAjax('viewcreated');
    }


    return $this->redirect(Url::toRoute(['meteracceptanceheader/viewsetpdf','id' => $model->id,'model' => $this->findModel($id)]));

}

Form2 视图

<div class="map-meters-form" id="doc">
<?php $form = ActiveForm::begin(['id' => 'map-form', 'enableClientValidation' => true, 'enableAjaxValidation' => false,
'options' => ['enctype' => 'multipart/form-data']]) ?>
<section class="content">
<div class="box">
    <div id="chk" class="box-body">
        <?php Pjax::begin(); ?>
        <?= DetailView::widget([
            'model' => $model,
            'attributes' => [

                [
                        'label'=>'Serial #',
                         'value' => function($d)
                         {
                             return $d->id;
                         }
                ],
                [
                    'label' => 'Meter Type',
                    'value' => function ($d) {
                        if(is_object($d))
                            return $d->meter_type;
                        return ' - ';
                    },


                ],
                'sub_div',
                [
                    'label' => 'Sub Division Name',
                    'value' => function ($d) {
                        if(is_object($d))
                            return $d->subDiv->name;
                        return '-';
                    },


                ],
                [
                    'label' => 'Prepared By',
                    'value' => function ($d) {
                        if(is_object($d))
                            return $d->prepared->name;
                    },


                ],
                'prepared_at',

                'status',


            ],
        ]) ?>
        <br>
        <div class="pre-scrollable">
        <?= GridView::widget([
            'dataProvider' => $dataProvider,
            //'ajaxUpdate'       => true,
            'filterModel' => false,
            //'id'=>'gv',

            'columns' => [
                ['class' => 'yii\grid\SerialColumn'],
                ['class' => 'yii\grid\CheckboxColumn', 'checkboxOptions' => function($d) {
                    return ['value' => $d['meter_id']];
                }],
                'Meter_Serial_Number',
                'Meter_Type',
                'Sub_Division_Code',
                'Sub_Division_Name',
            ],
        ]); ?>
        </div>
        <?php Pjax::end(); ?>

        <?= $form->field($model, 'images[]')->fileInput(['multiple' => true, 'accept' => 'image/*'])?>
        <br>
        <form>
            <p>
                <a href="<?= URL::toRoute(['meteracceptanceheader/setpdf', 'id'=>$model->id])?>" name="redirect" class="btn btn-primary" id="myid">Submit</a>

                <br/>

            </p>
        </form>
    </div>
  </div>
  </section>
  <?php ActiveForm::end(); ?>
  </div>
  <?php
  $url = Url::toRoute(['/meteracceptanceheader/setpdf','id'=>$model->id]);
  $script = <<< JS
  $(document).ready(function () {  

  $(document).on('pjax:end', function() {
  $("#chk").find("input:checkbox").prop("checked", true);
  });
  $("#chk").find("input:checkbox").prop("checked", true);

  $('#myid').on('click',function(e) {


   e.preventDefault();    
   var strValue = "";        
    $('input[name="selection[]"]:checked').each(function() {

    if(strValue!=="")
        {
        strValue = strValue + " , " + this.value;

        }
    else 
       strValue = this.value;     

  });

  $.ajax({
     url: '$url',
     type: 'POST',
     dataType: 'json',
     data: {data:strValue},         
     success: function(data) {
        alert(data);
     }
  });
  }) 
  });
  JS;
  $this->registerJs($script, \yii\web\View::POS_END);
  ?>

这将允许 select 生成图像。现在,当我尝试单击提交按钮时,出现以下错误

PHP Notice 'yii\base\ErrorException' with message 'Undefined offset: 0'

in E:\xampp\htdocs\inventory-web\vendor\yiisoft\yii2\db\Command.php:330

通过调试控制器代码,我发现错误出现在 foreach ($model->images as $file) 处,因为 print_r($model->images) return Array() 为空。

通过 print_r($model) 我得到了

common\models\MeterAcceptanceHeader Object ( [_attributes:yii\db\BaseActiveRecord:private] => Array ( [id] => 1 [sub_div] => 37111 [meter_type] => L.T.TOU [prepared_by] => 12 [prepared_at] => 2018-08-20 12:41:27 [updated_at] => [status] => Prepared [updated_by] => [images] => Array ( ) ) [_oldAttributes:yii\db\BaseActiveRecord:private] => Array ( [id] => 1 [sub_div] => 37111 [meter_type] => L.T.TOU [prepared_by] => 12 [prepared_at] => 2018-08-20 12:41:27 [updated_at] => [status] => Prepared [updated_by] => [images] => ) [_related:yii\db\BaseActiveRecord:private] => Array ( ) [_errors:yii\base\Model:private] => [_validators:yii\base\Model:private] => [_scenario:yii\base\Model:private] => default [_events:yii\base\Component:private] => Array ( ) [_behaviors:yii\base\Component:private] => Array ( ) )

我在其他模块中也使用了相同的过程并且它工作正常。

我怎样才能摆脱这个问题?

更新 1

<?php

use yii\helpers\Html;
use yii\grid\GridView;
use yii\helpers\Url;
use app\models\User;
use yii\widgets\DetailView;
use yii\widgets\ActiveForm;
use yii\widgets\Pjax;
use kartik\select2\Select2;
use kartik\file\FileInput;


/* @var $this yii\web\View */
/* @var $dataProvider yii\data\ActiveDataProvider */
$this->title = $model->id;
$this->title = 'Meter Acceptance Form';
$this->params['breadcrumbs'][] = $this->title;
?>
<section class="content-header">
   <h1>Meter Acceptance</h1>
</section>
<div class="map-meters-form" id="doc">

<section class="content">
    <div class="box">
        <div id="chk" class="box-body">
            <?php Pjax::begin(); ?>
            <?=
            DetailView::widget([
                'model' => $model,
                'attributes' => [
                    [
                        'label' => 'Serial #',
                        'value' => function($d){
                            return $d->id;
                        }
                    ],
                    [
                        'label' => 'Meter Type',
                        'value' => function ($d){
                            if( is_object($d) )
                                return $d->meter_type;
                            return ' - ';
                        },
                    ],
                    'sub_div',
                    [
                        'label' => 'Sub Division Name',
                        'value' => function ($d){
                            if( is_object($d) )
                                return $d->subDiv->name;
                            return '-';
                        },
                    ],
                    [
                        'label' => 'Prepared By',
                        'value' => function ($d){
                            if( is_object($d) )
                                return $d->prepared->name;
                        },
                    ],
                    'prepared_at',
                    'status',
                ],
            ])
            ?>
            <br>
            <div class="pre-scrollable">
                <?=
                GridView::widget([
                    'dataProvider' => $dataProvider,
                    //'ajaxUpdate'       => true,
                    'filterModel' => false,
                    //'id'=>'gv',
                    'columns' => [
                        ['class' => 'yii\grid\SerialColumn'],
                        ['class' => 'yii\grid\CheckboxColumn', 'checkboxOptions' => function($d){
                            return ['value' => $d['meter_id']];
                        }],
                        'Meter_Serial_Number',
                        'Meter_Type',
                        'Sub_Division_Code',
                        'Sub_Division_Name',
                    ],
                ]);
                ?>
            </div>
            <?php Pjax::end(); ?>
            <?php
            $form = ActiveForm::begin(['id' => 'map-form', 'enableClientValidation' => true, 'enableAjaxValidation' => false,
                'options' => ['enctype' => 'multipart/form-data']])
            ?>
            <?=$form->field($model, 'images[]')->fileInput(['multiple' => true, 'accept' => 'image/*']) ?>
            <br>
            <p>
                <a href="<?=URL::toRoute(['meteracceptanceheader/setpdf', 'id' => $model->id]) ?>" name="redirect" class="btn btn-primary" id="myid">Submit</a>

                <br/>

            </p>
            <?php ActiveForm::end(); ?>
        </div>
    </div>
</section>
</div>
<?php
$url = Url::toRoute(['/meteracceptanceheader/setpdf','id'=>$model->id]);
$script = <<< JS
$(document).ready(function () {  

$(document).on('pjax:end', function() {
       $("#chk").find("input:checkbox").prop("checked", true);
});
      $("#chk").find("input:checkbox").prop("checked", true);

   $('#myid').on('click',function(e) {

  e.preventDefault();    

 //START Append form data
  var data = new FormData();

  var files= $('input[name="MeterAcceptanceHeader[images][]"]')[0].files;

  //append files
  $.each(files,function(index,file){
      data.append("MeterAcceptanceHeader[images][]",file,file.name);
  });


 var strValue = "";        
    $('input[name="selection[]"]:checked').each(function() {

    if(strValue!=="")
        {
        strValue = strValue + " , " + this.value;

        }
    else 
       strValue = this.value;     

});
    //alert(strValue);

    //append your query string to the form data too
  data.append('data',strValue);

  //END append form data
  $.ajax({
     url: '$url',
     type: 'POST',
     dataType: 'json',
      contentType: false,
      processData: false,
     data: {data:strValue},         
     success: function(data) {
        alert(data);
     }
     });

     }) 
     });
     JS;
     $this->registerJs($script, \yii\web\View::POS_END);
     ?>

非常感谢任何帮助。

当您将 click 绑定到 #myid 这是锚按钮

<a href="<?= URL::toRoute(['meteracceptanceheader/setpdf', 'id'=>$model->id])?>" name="redirect" class="btn btn-primary" id="myid">Submit</a>

如果您尝试通过 ajax 发送图像,则需要使用 FormData 界面。

The FormData interface provides a way to easily construct a set of key/value pairs representing form fields and their values, which can then be easily sent using the XMLHttpRequest.send() method. It uses the same format a form would use if the encoding type were set to "multipart/form-data".

但在我介绍如何操作之前,您需要研究一些与 Form2 视图相关的其他问题。

  • 您正在嵌套 2 个表单,这在技术上是错误的,您不能这样做,请参阅 Why

  • 最重要的是,您为什么要为提交按钮创建一个单独的表单?

    看到上面这一行

    <?php $form = ActiveForm::begin(['id' => 'map-form', 'enableClientValidation' => true, 'enableAjaxValidation' => false,
    'options' => ['enctype' => 'multipart/form-data']]) ?>
    

    这是您的第一个表单和文件输入字段的起始位置

    <?= $form->field($model, 'images[]')->fileInput(['multiple' => true, 'accept' => 'image/*'])?>

    在此表单内,在下一行中有我在开头提到的锚按钮,但您将其包装在一个单独的表单中,并且在关闭 ActiveForm[ 之前也是如此=42=]

       <form>
            <p>
                <a href="<?= URL::toRoute(['meteracceptanceheader/setpdf', 'id'=>$model->id])?>" name="redirect" class="btn btn-primary" id="myid">Submit</a>
    
                <br/>
    
            </p>
        </form>
    

    相反,您将通过调用 <?php ActiveForm::end(); ?> 关闭此表单后的 ActiveForm。正如您在前面的 中看到的那样,当您将 GridView 包装在表单中时,您遇到了 GridVew 的过滤器输入的奇怪行为,并且您在这里重复了同样的错误也嵌套了 2 个表单。

我会建议你先做什么

  • 删除您仅为锚按钮创建的表单,因为如果您想通过 ajax 通过单击锚按钮提交图像,则不需要它,只需将锚按钮保持在主 ActiveForm。并将 ActiveForm::begin() 移动到 fileInput() 之前和 Pjax::end() 之后。

话虽如此,您现在应该让用户 FormData 通过 ajax 上传图片,为此您必须在您的文件中添加这些选项 contentType: falseprocessData: false ajax 调用并使用 FormData.append() 将输入文件附加到 FormData

所以您的点击功能 javascript 将如下所示,我假设用于图片上传的模型是 MeterAcceptanceImages

$('#myid').on('click',function(e) {
      event.preventDefault();

      //START Append form data
      let data = new FormData();

      let files= $("input[name='MeterAcceptanceImages[images][]']")[0].files;

      //append files
      $.each(files,function(index,file){
          data.append('MeterAcceptanceImages[images][]',file,file.name);
      });

      var strValue = "";
      $('input[name="selection[]"]:checked').each(function() {
           if(strValue!==""){
               strValue = strValue + " , " + this.value;
           }else{
              strValue = this.value;     
           }
      });

      //append your query string to the form data too
      data.append('data',strValue);

      //END append form data

      $.ajax({
          url: '$url',
          type: 'POST',
          dataType: 'json',
          contentType: false,
          processData: false,
          data: data,
          success: function(data) {
             alert(data);
          }
      });
});

总的来说,您的视图 Form2.php 应该如下所示

<div class="map-meters-form" id="doc">

    <section class="content">
        <div class="box">
            <div id="chk" class="box-body">
                <?php Pjax::begin(); ?>
                <?=
                DetailView::widget([
                    'model' => $model,
                    'attributes' => [
                        [
                            'label' => 'Serial #',
                            'value' => function($d){
                                return $d->id;
                            }
                        ],
                        [
                            'label' => 'Meter Type',
                            'value' => function ($d){
                                if( is_object($d) )
                                    return $d->meter_type;
                                return ' - ';
                            },
                        ],
                        'sub_div',
                        [
                            'label' => 'Sub Division Name',
                            'value' => function ($d){
                                if( is_object($d) )
                                    return $d->subDiv->name;
                                return '-';
                            },
                        ],
                        [
                            'label' => 'Prepared By',
                            'value' => function ($d){
                                if( is_object($d) )
                                    return $d->prepared->name;
                            },
                        ],
                        'prepared_at',
                        'status',
                    ],
                ])
                ?>
                <br>
                <div class="pre-scrollable">
                    <?=
                    GridView::widget([
                        'dataProvider' => $dataProvider,
                        //'ajaxUpdate'       => true,
                        'filterModel' => false,
                        //'id'=>'gv',
                        'columns' => [
                            ['class' => 'yii\grid\SerialColumn'],
                            ['class' => 'yii\grid\CheckboxColumn', 'checkboxOptions' => function($d){
                                    return ['value' => $d['meter_id']];
                                }],
                            'Meter_Serial_Number',
                            'Meter_Type',
                            'Sub_Division_Code',
                            'Sub_Division_Name',
                        ],
                    ]);
                    ?>
                </div>
                <?php Pjax::end(); ?>
                <?php
                $form = ActiveForm::begin(['id' => 'map-form', 'enableClientValidation' => true, 'enableAjaxValidation' => false,
                            'options' => ['enctype' => 'multipart/form-data']])
                ?>
                <?=$form->field($model, 'images[]')->fileInput(['multiple' => true, 'accept' => 'image/*']) ?>
                <br>
                <p>
                    <a href="<?=URL::toRoute(['meteracceptanceheader/setpdf', 'id' => $model->id]) ?>" name="redirect" class="btn btn-primary" id="myid">Submit</a>

                    <br/>

                </p>
                <?php ActiveForm::end(); ?>
            </div>
        </div>
    </section>
</div>
<?php
$url = Url::toRoute(['/meteracceptanceheader/setpdf', 'id' => $model->id]);
$script = <<< JS
  $(document).ready(function () {  

  $(document).on('pjax:end', function() {
    $("#chk").find("input:checkbox").prop("checked", true);
  });
  $("#chk").find("input:checkbox").prop("checked", true);

$('#myid').on('click',function(e) {
      event.preventDefault();

      //START Append form data
      let data = new FormData();

      let files= $("input[name='MeterAcceptanceImages[images][]']")[0].files;

      //append files
      $.each(files,function(index,file){
          data.append('MeterAcceptanceImages[images][]',file,file.name);
      });

      var strValue = "";
      $('input[name="selection[]"]:checked').each(function() {
           if(strValue!==""){
               strValue = strValue + " , " + this.value;
           }else{
              strValue = this.value;     
           }
      });

      //append your query string to the form data too
      data.append('data',strValue);

      //END append form data

      $.ajax({
          url: '$url',
          type: 'POST',
          dataType: 'json',
          contentType: false,
          processData: false,
          data: data,
          success: function(data) {
             alert(data);
          }
      });
});

  });
JS;
$this->registerJs($script, \yii\web\View::POS_END);
?>

现在,如果您尝试 print_r(UploadedFile::getInstances('images')) 应该会显示您选择并提交上传的所有图像。要在上传 ajax 调用时出现错误进行故障排除,您可以查看我之前发布的与 ajax 文件上传相关的