php - Yii2-无法上传图片

我正在研究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表

enter image description here

MeterAcceptanceImages表

enter image description here

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

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);
  ?>

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

PHP通知“yii \ base \ ErrorException”,消息为“未定义的偏移量:0”


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

通过调试控制器代码,我发现错误出现在 foreach ($model->images as $file)上,因为 print_r($model->images)返回 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时,当您在步骤2中单击“提交”按钮以上传图像时,您正在尝试通过Ajax调用提交图像

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

而且,如果您尝试通过ajax发送图像,则需要使用 FormData接口。

FormData接口提供了一种轻松构造一组
表示表单字段及其值的键/值对,可以
然后可以使用XMLHttpRequest.send()方法轻松发送。它用
如果将编码类型设置为,则表单将使用相同的格式
“多部分/表单数据”。

但是,在我解决该问题之前,您需要研究与 Form2视图相关的其他问题。
  • 您正在嵌套这两种形式,这在技术上是错误的,您无法看到 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之前也是如此
       <form>
            <p>
                <a href="<?= URL::toRoute(['meteracceptanceheader/setpdf', 'id'=>$model->id])?>" name="redirect" class="btn btn-primary" id="myid">Submit</a>
    
                <br/>
    
            </p>
        </form>
    

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

  • 我会建议你先做什么
  • 删除您仅为锚按钮创建的表单,因为如果您想通过单击锚点通过ajax提交图像,则不需要它,只需将锚保留在主ActiveForm中即可。然后将ActiveForm::begin()移到fileInput()之前和Pjax::end()之后。

  • 话虽如此,您现在应该使用 FormData通过ajax上载图像,并这样做,您必须在ajax调用中添加这些选项 contentType: falseprocessData: false,并使用 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文件上传相关的 answer